vendor/symfony/dependency-injection/Dumper/PhpDumper.php line 139

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\DependencyInjection\Dumper;
  11. use Composer\Autoload\ClassLoader;
  12. use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader;
  13. use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
  14. use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
  15. use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
  16. use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
  17. use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
  18. use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
  19. use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass;
  20. use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass;
  21. use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphNode;
  22. use Symfony\Component\DependencyInjection\Container;
  23. use Symfony\Component\DependencyInjection\ContainerBuilder;
  24. use Symfony\Component\DependencyInjection\ContainerInterface;
  25. use Symfony\Component\DependencyInjection\Definition;
  26. use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
  27. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  28. use Symfony\Component\DependencyInjection\Exception\LogicException;
  29. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  30. use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
  31. use Symfony\Component\DependencyInjection\ExpressionLanguage;
  32. use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface;
  33. use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
  34. use Symfony\Component\DependencyInjection\Loader\FileLoader;
  35. use Symfony\Component\DependencyInjection\Parameter;
  36. use Symfony\Component\DependencyInjection\Reference;
  37. use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
  38. use Symfony\Component\DependencyInjection\TypedReference;
  39. use Symfony\Component\DependencyInjection\Variable;
  40. use Symfony\Component\ErrorHandler\DebugClassLoader;
  41. use Symfony\Component\ExpressionLanguage\Expression;
  42. use Symfony\Component\HttpKernel\Kernel;
  43. /**
  44. * PhpDumper dumps a service container as a PHP class.
  45. *
  46. * @author Fabien Potencier <fabien@symfony.com>
  47. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  48. */
  49. class PhpDumper extends Dumper
  50. {
  51. /**
  52. * Characters that might appear in the generated variable name as first character.
  53. */
  54. public const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz';
  55. /**
  56. * Characters that might appear in the generated variable name as any but the first character.
  57. */
  58. public const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_';
  59. /**
  60. * @var \SplObjectStorage<Definition, Variable>|null
  61. */
  62. private $definitionVariables;
  63. private $referenceVariables;
  64. private $variableCount;
  65. private $inlinedDefinitions;
  66. private $serviceCalls;
  67. private $reservedVariables = ['instance', 'class', 'this', 'container'];
  68. private $expressionLanguage;
  69. private $targetDirRegex;
  70. private $targetDirMaxMatches;
  71. private $docStar;
  72. private $serviceIdToMethodNameMap;
  73. private $usedMethodNames;
  74. private $namespace;
  75. private $asFiles;
  76. private $hotPathTag;
  77. private $preloadTags;
  78. private $inlineFactories;
  79. private $inlineRequires;
  80. private $inlinedRequires = [];
  81. private $circularReferences = [];
  82. private $singleUsePrivateIds = [];
  83. private $preload = [];
  84. private $addThrow = false;
  85. private $addGetService = false;
  86. private $locatedIds = [];
  87. private $serviceLocatorTag;
  88. private $exportedVariables = [];
  89. private $dynamicParameters = [];
  90. private $baseClass;
  91. /**
  92. * @var DumperInterface
  93. */
  94. private $proxyDumper;
  95. private $hasProxyDumper = false;
  96. /**
  97. * {@inheritdoc}
  98. */
  99. public function __construct(ContainerBuilder $container)
  100. {
  101. if (!$container->isCompiled()) {
  102. throw new LogicException('Cannot dump an uncompiled container.');
  103. }
  104. parent::__construct($container);
  105. }
  106. /**
  107. * Sets the dumper to be used when dumping proxies in the generated container.
  108. */
  109. public function setProxyDumper(DumperInterface $proxyDumper)
  110. {
  111. $this->proxyDumper = $proxyDumper;
  112. $this->hasProxyDumper = !$proxyDumper instanceof NullDumper;
  113. }
  114. /**
  115. * Dumps the service container as a PHP class.
  116. *
  117. * Available options:
  118. *
  119. * * class: The class name
  120. * * base_class: The base class name
  121. * * namespace: The class namespace
  122. * * as_files: To split the container in several files
  123. *
  124. * @return string|array A PHP class representing the service container or an array of PHP files if the "as_files" option is set
  125. *
  126. * @throws EnvParameterException When an env var exists but has not been dumped
  127. */
  128. public function dump(array $options = [])
  129. {
  130. $this->locatedIds = [];
  131. $this->targetDirRegex = null;
  132. $this->inlinedRequires = [];
  133. $this->exportedVariables = [];
  134. $this->dynamicParameters = [];
  135. $options = array_merge([
  136. 'class' => 'ProjectServiceContainer',
  137. 'base_class' => 'Container',
  138. 'namespace' => '',
  139. 'as_files' => false,
  140. 'debug' => true,
  141. 'hot_path_tag' => 'container.hot_path',
  142. 'preload_tags' => ['container.preload', 'container.no_preload'],
  143. 'inline_factories_parameter' => 'container.dumper.inline_factories',
  144. 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
  145. 'preload_classes' => [],
  146. 'service_locator_tag' => 'container.service_locator',
  147. 'build_time' => time(),
  148. ], $options);
  149. $this->addThrow = $this->addGetService = false;
  150. $this->namespace = $options['namespace'];
  151. $this->asFiles = $options['as_files'];
  152. $this->hotPathTag = $options['hot_path_tag'];
  153. $this->preloadTags = $options['preload_tags'];
  154. $this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']);
  155. $this->inlineRequires = $options['inline_class_loader_parameter'] && ($this->container->hasParameter($options['inline_class_loader_parameter']) ? $this->container->getParameter($options['inline_class_loader_parameter']) : (\PHP_VERSION_ID < 70400 || $options['debug']));
  156. $this->serviceLocatorTag = $options['service_locator_tag'];
  157. if (!str_starts_with($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) {
  158. $baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass);
  159. $this->baseClass = $baseClass;
  160. } elseif ('Container' === $baseClass) {
  161. $this->baseClass = Container::class;
  162. } else {
  163. $this->baseClass = $baseClass;
  164. }
  165. $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass);
  166. if (!$this->hasProxyDumper) {
  167. (new AnalyzeServiceReferencesPass(true, false))->process($this->container);
  168. try {
  169. (new CheckCircularReferencesPass())->process($this->container);
  170. } catch (ServiceCircularReferenceException $e) {
  171. $path = $e->getPath();
  172. end($path);
  173. $path[key($path)] .= '". Try running "composer require symfony/proxy-manager-bridge';
  174. throw new ServiceCircularReferenceException($e->getServiceId(), $path);
  175. }
  176. }
  177. $this->analyzeReferences();
  178. $this->docStar = $options['debug'] ? '*' : '';
  179. if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) {
  180. // Build a regexp where the first root dirs are mandatory,
  181. // but every other sub-dir is optional up to the full path in $dir
  182. // Mandate at least 1 root dir and not more than 5 optional dirs.
  183. $dir = explode(\DIRECTORY_SEPARATOR, realpath($dir));
  184. $i = \count($dir);
  185. if (2 + (int) ('\\' === \DIRECTORY_SEPARATOR) <= $i) {
  186. $regex = '';
  187. $lastOptionalDir = $i > 8 ? $i - 5 : (2 + (int) ('\\' === \DIRECTORY_SEPARATOR));
  188. $this->targetDirMaxMatches = $i - $lastOptionalDir;
  189. while (--$i >= $lastOptionalDir) {
  190. $regex = sprintf('(%s%s)?', preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#'), $regex);
  191. }
  192. do {
  193. $regex = preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#').$regex;
  194. } while (0 < --$i);
  195. $this->targetDirRegex = '#(^|file://|[:;, \|\r\n])'.preg_quote($dir[0], '#').$regex.'#';
  196. }
  197. }
  198. $proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null;
  199. if ($options['preload_classes']) {
  200. $this->preload = array_combine($options['preload_classes'], $options['preload_classes']);
  201. }
  202. $code = $this->addDefaultParametersMethod();
  203. $code =
  204. $this->startClass($options['class'], $baseClass, $this->inlineFactories && $proxyClasses).
  205. $this->addServices($services).
  206. $this->addDeprecatedAliases().
  207. $code
  208. ;
  209. $proxyClasses = $proxyClasses ?? $this->generateProxyClasses();
  210. if ($this->addGetService) {
  211. $code = preg_replace(
  212. "/(\r?\n\r?\n public function __construct.+?\\{\r?\n)/s",
  213. "\n protected \$getService;$1 \$this->getService = \\Closure::fromCallable([\$this, 'getService']);\n",
  214. $code,
  215. 1
  216. );
  217. }
  218. if ($this->asFiles) {
  219. $fileTemplate = <<<EOF
  220. <?php
  221. use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
  222. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  223. /*{$this->docStar}
  224. * @internal This class has been auto-generated by the Symfony Dependency Injection Component.
  225. */
  226. class %s extends {$options['class']}
  227. {%s}
  228. EOF;
  229. $files = [];
  230. $preloadedFiles = [];
  231. $ids = $this->container->getRemovedIds();
  232. foreach ($this->container->getDefinitions() as $id => $definition) {
  233. if (!$definition->isPublic()) {
  234. $ids[$id] = true;
  235. }
  236. }
  237. if ($ids = array_keys($ids)) {
  238. sort($ids);
  239. $c = "<?php\n\nreturn [\n";
  240. foreach ($ids as $id) {
  241. $c .= ' '.$this->doExport($id)." => true,\n";
  242. }
  243. $files['removed-ids.php'] = $c."];\n";
  244. }
  245. if (!$this->inlineFactories) {
  246. foreach ($this->generateServiceFiles($services) as $file => [$c, $preload]) {
  247. $files[$file] = sprintf($fileTemplate, substr($file, 0, -4), $c);
  248. if ($preload) {
  249. $preloadedFiles[$file] = $file;
  250. }
  251. }
  252. foreach ($proxyClasses as $file => $c) {
  253. $files[$file] = "<?php\n".$c;
  254. $preloadedFiles[$file] = $file;
  255. }
  256. }
  257. $code .= $this->endClass();
  258. if ($this->inlineFactories && $proxyClasses) {
  259. $files['proxy-classes.php'] = "<?php\n\n";
  260. foreach ($proxyClasses as $c) {
  261. $files['proxy-classes.php'] .= $c;
  262. }
  263. }
  264. $files[$options['class'].'.php'] = $code;
  265. $hash = ucfirst(strtr(ContainerBuilder::hash($files), '._', 'xx'));
  266. $code = [];
  267. foreach ($files as $file => $c) {
  268. $code["Container{$hash}/{$file}"] = substr_replace($c, "<?php\n\nnamespace Container{$hash};\n", 0, 6);
  269. if (isset($preloadedFiles[$file])) {
  270. $preloadedFiles[$file] = "Container{$hash}/{$file}";
  271. }
  272. }
  273. $namespaceLine = $this->namespace ? "\nnamespace {$this->namespace};\n" : '';
  274. $time = $options['build_time'];
  275. $id = hash('crc32', $hash.$time);
  276. $this->asFiles = false;
  277. if ($this->preload && null !== $autoloadFile = $this->getAutoloadFile()) {
  278. $autoloadFile = trim($this->export($autoloadFile), '()\\');
  279. $preloadedFiles = array_reverse($preloadedFiles);
  280. if ('' !== $preloadedFiles = implode("';\nrequire __DIR__.'/", $preloadedFiles)) {
  281. $preloadedFiles = "require __DIR__.'/$preloadedFiles';\n";
  282. }
  283. $code[$options['class'].'.preload.php'] = <<<EOF
  284. <?php
  285. // This file has been auto-generated by the Symfony Dependency Injection Component
  286. // You can reference it in the "opcache.preload" php.ini setting on PHP >= 7.4 when preloading is desired
  287. use Symfony\Component\DependencyInjection\Dumper\Preloader;
  288. if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
  289. return;
  290. }
  291. require $autoloadFile;
  292. (require __DIR__.'/{$options['class']}.php')->set(\\Container{$hash}\\{$options['class']}::class, null);
  293. $preloadedFiles
  294. \$classes = [];
  295. EOF;
  296. foreach ($this->preload as $class) {
  297. if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], true)) {
  298. continue;
  299. }
  300. if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || ((new \ReflectionClass($class))->isUserDefined() && !\in_array($class, ['Attribute', 'JsonException', 'ReturnTypeWillChange', 'Stringable', 'UnhandledMatchError', 'ValueError'], true))) {
  301. $code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class);
  302. }
  303. }
  304. $code[$options['class'].'.preload.php'] .= <<<'EOF'
  305. $preloaded = Preloader::preload($classes);
  306. EOF;
  307. }
  308. $code[$options['class'].'.php'] = <<<EOF
  309. <?php
  310. {$namespaceLine}
  311. // This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
  312. if (\\class_exists(\\Container{$hash}\\{$options['class']}::class, false)) {
  313. // no-op
  314. } elseif (!include __DIR__.'/Container{$hash}/{$options['class']}.php') {
  315. touch(__DIR__.'/Container{$hash}.legacy');
  316. return;
  317. }
  318. if (!\\class_exists({$options['class']}::class, false)) {
  319. \\class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false);
  320. }
  321. return new \\Container{$hash}\\{$options['class']}([
  322. 'container.build_hash' => '$hash',
  323. 'container.build_id' => '$id',
  324. 'container.build_time' => $time,
  325. ], __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}');
  326. EOF;
  327. } else {
  328. $code .= $this->endClass();
  329. foreach ($proxyClasses as $c) {
  330. $code .= $c;
  331. }
  332. }
  333. $this->targetDirRegex = null;
  334. $this->inlinedRequires = [];
  335. $this->circularReferences = [];
  336. $this->locatedIds = [];
  337. $this->exportedVariables = [];
  338. $this->dynamicParameters = [];
  339. $this->preload = [];
  340. $unusedEnvs = [];
  341. foreach ($this->container->getEnvCounters() as $env => $use) {
  342. if (!$use) {
  343. $unusedEnvs[] = $env;
  344. }
  345. }
  346. if ($unusedEnvs) {
  347. throw new EnvParameterException($unusedEnvs, null, 'Environment variables "%s" are never used. Please, check your container\'s configuration.');
  348. }
  349. return $code;
  350. }
  351. /**
  352. * Retrieves the currently set proxy dumper or instantiates one.
  353. */
  354. private function getProxyDumper(): DumperInterface
  355. {
  356. if (!$this->proxyDumper) {
  357. $this->proxyDumper = new NullDumper();
  358. }
  359. return $this->proxyDumper;
  360. }
  361. private function analyzeReferences()
  362. {
  363. (new AnalyzeServiceReferencesPass(false, $this->hasProxyDumper))->process($this->container);
  364. $checkedNodes = [];
  365. $this->circularReferences = [];
  366. $this->singleUsePrivateIds = [];
  367. foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) {
  368. if (!$node->getValue() instanceof Definition) {
  369. continue;
  370. }
  371. if ($this->isSingleUsePrivateNode($node)) {
  372. $this->singleUsePrivateIds[$id] = $id;
  373. }
  374. $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes);
  375. }
  376. $this->container->getCompiler()->getServiceReferenceGraph()->clear();
  377. $this->singleUsePrivateIds = array_diff_key($this->singleUsePrivateIds, $this->circularReferences);
  378. }
  379. private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array &$loops = [], array $path = [], bool $byConstructor = true): void
  380. {
  381. $path[$sourceId] = $byConstructor;
  382. $checkedNodes[$sourceId] = true;
  383. foreach ($edges as $edge) {
  384. $node = $edge->getDestNode();
  385. $id = $node->getId();
  386. if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isWeak()) {
  387. continue;
  388. }
  389. if (isset($path[$id])) {
  390. $loop = null;
  391. $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy();
  392. $pathInLoop = [$id, []];
  393. foreach ($path as $k => $pathByConstructor) {
  394. if (null !== $loop) {
  395. $loop[] = $k;
  396. $pathInLoop[1][$k] = $pathByConstructor;
  397. $loops[$k][] = &$pathInLoop;
  398. $loopByConstructor = $loopByConstructor && $pathByConstructor;
  399. } elseif ($k === $id) {
  400. $loop = [];
  401. }
  402. }
  403. $this->addCircularReferences($id, $loop, $loopByConstructor);
  404. } elseif (!isset($checkedNodes[$id])) {
  405. $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor() && !$edge->isLazy());
  406. } elseif (isset($loops[$id])) {
  407. // we already had detected loops for this edge
  408. // let's check if we have a common ancestor in one of the detected loops
  409. foreach ($loops[$id] as [$first, $loopPath]) {
  410. if (!isset($path[$first])) {
  411. continue;
  412. }
  413. // We have a common ancestor, let's fill the current path
  414. $fillPath = null;
  415. foreach ($loopPath as $k => $pathByConstructor) {
  416. if (null !== $fillPath) {
  417. $fillPath[$k] = $pathByConstructor;
  418. } elseif ($k === $id) {
  419. $fillPath = $path;
  420. $fillPath[$k] = $pathByConstructor;
  421. }
  422. }
  423. // we can now build the loop
  424. $loop = null;
  425. $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy();
  426. foreach ($fillPath as $k => $pathByConstructor) {
  427. if (null !== $loop) {
  428. $loop[] = $k;
  429. $loopByConstructor = $loopByConstructor && $pathByConstructor;
  430. } elseif ($k === $first) {
  431. $loop = [];
  432. }
  433. }
  434. $this->addCircularReferences($first, $loop, $loopByConstructor);
  435. break;
  436. }
  437. }
  438. }
  439. unset($path[$sourceId]);
  440. }
  441. private function addCircularReferences(string $sourceId, array $currentPath, bool $byConstructor)
  442. {
  443. $currentId = $sourceId;
  444. $currentPath = array_reverse($currentPath);
  445. $currentPath[] = $currentId;
  446. foreach ($currentPath as $parentId) {
  447. if (empty($this->circularReferences[$parentId][$currentId])) {
  448. $this->circularReferences[$parentId][$currentId] = $byConstructor;
  449. }
  450. $currentId = $parentId;
  451. }
  452. }
  453. private function collectLineage(string $class, array &$lineage)
  454. {
  455. if (isset($lineage[$class])) {
  456. return;
  457. }
  458. if (!$r = $this->container->getReflectionClass($class, false)) {
  459. return;
  460. }
  461. if (is_a($class, $this->baseClass, true)) {
  462. return;
  463. }
  464. $file = $r->getFileName();
  465. if (str_ends_with($file, ') : eval()\'d code')) {
  466. $file = substr($file, 0, strrpos($file, '(', -17));
  467. }
  468. if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) {
  469. return;
  470. }
  471. $lineage[$class] = substr($exportedFile, 1, -1);
  472. if ($parent = $r->getParentClass()) {
  473. $this->collectLineage($parent->name, $lineage);
  474. }
  475. foreach ($r->getInterfaces() as $parent) {
  476. $this->collectLineage($parent->name, $lineage);
  477. }
  478. foreach ($r->getTraits() as $parent) {
  479. $this->collectLineage($parent->name, $lineage);
  480. }
  481. unset($lineage[$class]);
  482. $lineage[$class] = substr($exportedFile, 1, -1);
  483. }
  484. private function generateProxyClasses(): array
  485. {
  486. $proxyClasses = [];
  487. $alreadyGenerated = [];
  488. $definitions = $this->container->getDefinitions();
  489. $strip = '' === $this->docStar && method_exists(Kernel::class, 'stripComments');
  490. $proxyDumper = $this->getProxyDumper();
  491. ksort($definitions);
  492. foreach ($definitions as $definition) {
  493. if (!$proxyDumper->isProxyCandidate($definition)) {
  494. continue;
  495. }
  496. if (isset($alreadyGenerated[$class = $definition->getClass()])) {
  497. continue;
  498. }
  499. $alreadyGenerated[$class] = true;
  500. // register class' reflector for resource tracking
  501. $this->container->getReflectionClass($class);
  502. if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition)) {
  503. continue;
  504. }
  505. if ($this->inlineRequires) {
  506. $lineage = [];
  507. $this->collectLineage($class, $lineage);
  508. $code = '';
  509. foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
  510. if ($this->inlineFactories) {
  511. $this->inlinedRequires[$file] = true;
  512. }
  513. $code .= sprintf("include_once %s;\n", $file);
  514. }
  515. $proxyCode = $code.$proxyCode;
  516. }
  517. if ($strip) {
  518. $proxyCode = "<?php\n".$proxyCode;
  519. $proxyCode = substr(Kernel::stripComments($proxyCode), 5);
  520. }
  521. $proxyClass = explode(' ', $this->inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode, 3)[1];
  522. if ($this->asFiles || $this->namespace) {
  523. $proxyCode .= "\nif (!\\class_exists('$proxyClass', false)) {\n \\class_alias(__NAMESPACE__.'\\\\$proxyClass', '$proxyClass', false);\n}\n";
  524. }
  525. $proxyClasses[$proxyClass.'.php'] = $proxyCode;
  526. }
  527. return $proxyClasses;
  528. }
  529. private function addServiceInclude(string $cId, Definition $definition): string
  530. {
  531. $code = '';
  532. if ($this->inlineRequires && (!$this->isHotPath($definition) || $this->getProxyDumper()->isProxyCandidate($definition))) {
  533. $lineage = [];
  534. foreach ($this->inlinedDefinitions as $def) {
  535. if (!$def->isDeprecated()) {
  536. foreach ($this->getClasses($def, $cId) as $class) {
  537. $this->collectLineage($class, $lineage);
  538. }
  539. }
  540. }
  541. foreach ($this->serviceCalls as $id => [$callCount, $behavior]) {
  542. if ('service_container' !== $id && $id !== $cId
  543. && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior
  544. && $this->container->has($id)
  545. && $this->isTrivialInstance($def = $this->container->findDefinition($id))
  546. ) {
  547. foreach ($this->getClasses($def, $cId) as $class) {
  548. $this->collectLineage($class, $lineage);
  549. }
  550. }
  551. }
  552. foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
  553. $code .= sprintf(" include_once %s;\n", $file);
  554. }
  555. }
  556. foreach ($this->inlinedDefinitions as $def) {
  557. if ($file = $def->getFile()) {
  558. $file = $this->dumpValue($file);
  559. $file = '(' === $file[0] ? substr($file, 1, -1) : $file;
  560. $code .= sprintf(" include_once %s;\n", $file);
  561. }
  562. }
  563. if ('' !== $code) {
  564. $code .= "\n";
  565. }
  566. return $code;
  567. }
  568. /**
  569. * @throws InvalidArgumentException
  570. * @throws RuntimeException
  571. */
  572. private function addServiceInstance(string $id, Definition $definition, bool $isSimpleInstance): string
  573. {
  574. $class = $this->dumpValue($definition->getClass());
  575. if (str_starts_with($class, "'") && !str_contains($class, '$') && !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
  576. throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
  577. }
  578. $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
  579. $instantiation = '';
  580. $lastWitherIndex = null;
  581. foreach ($definition->getMethodCalls() as $k => $call) {
  582. if ($call[2] ?? false) {
  583. $lastWitherIndex = $k;
  584. }
  585. }
  586. if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) {
  587. $instantiation = sprintf('$this->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance');
  588. } elseif (!$isSimpleInstance) {
  589. $instantiation = '$instance';
  590. }
  591. $return = '';
  592. if ($isSimpleInstance) {
  593. $return = 'return ';
  594. } else {
  595. $instantiation .= ' = ';
  596. }
  597. return $this->addNewInstance($definition, ' '.$return.$instantiation, $id);
  598. }
  599. private function isTrivialInstance(Definition $definition): bool
  600. {
  601. if ($definition->hasErrors()) {
  602. return true;
  603. }
  604. if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) {
  605. return false;
  606. }
  607. if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < \count($definition->getArguments())) {
  608. return false;
  609. }
  610. foreach ($definition->getArguments() as $arg) {
  611. if (!$arg || $arg instanceof Parameter) {
  612. continue;
  613. }
  614. if (\is_array($arg) && 3 >= \count($arg)) {
  615. foreach ($arg as $k => $v) {
  616. if ($this->dumpValue($k) !== $this->dumpValue($k, false)) {
  617. return false;
  618. }
  619. if (!$v || $v instanceof Parameter) {
  620. continue;
  621. }
  622. if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) {
  623. continue;
  624. }
  625. if (!\is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) {
  626. return false;
  627. }
  628. }
  629. } elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) {
  630. continue;
  631. } elseif (!\is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) {
  632. return false;
  633. }
  634. }
  635. return true;
  636. }
  637. private function addServiceMethodCalls(Definition $definition, string $variableName, ?string $sharedNonLazyId): string
  638. {
  639. $lastWitherIndex = null;
  640. foreach ($definition->getMethodCalls() as $k => $call) {
  641. if ($call[2] ?? false) {
  642. $lastWitherIndex = $k;
  643. }
  644. }
  645. $calls = '';
  646. foreach ($definition->getMethodCalls() as $k => $call) {
  647. $arguments = [];
  648. foreach ($call[1] as $i => $value) {
  649. $arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value);
  650. }
  651. $witherAssignation = '';
  652. if ($call[2] ?? false) {
  653. if (null !== $sharedNonLazyId && $lastWitherIndex === $k && 'instance' === $variableName) {
  654. $witherAssignation = sprintf('$this->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId);
  655. }
  656. $witherAssignation .= sprintf('$%s = ', $variableName);
  657. }
  658. $calls .= $this->wrapServiceConditionals($call[1], sprintf(" %s\$%s->%s(%s);\n", $witherAssignation, $variableName, $call[0], implode(', ', $arguments)));
  659. }
  660. return $calls;
  661. }
  662. private function addServiceProperties(Definition $definition, string $variableName = 'instance'): string
  663. {
  664. $code = '';
  665. foreach ($definition->getProperties() as $name => $value) {
  666. $code .= sprintf(" \$%s->%s = %s;\n", $variableName, $name, $this->dumpValue($value));
  667. }
  668. return $code;
  669. }
  670. private function addServiceConfigurator(Definition $definition, string $variableName = 'instance'): string
  671. {
  672. if (!$callable = $definition->getConfigurator()) {
  673. return '';
  674. }
  675. if (\is_array($callable)) {
  676. if ($callable[0] instanceof Reference
  677. || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))
  678. ) {
  679. return sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
  680. }
  681. $class = $this->dumpValue($callable[0]);
  682. // If the class is a string we can optimize away
  683. if (str_starts_with($class, "'") && !str_contains($class, '$')) {
  684. return sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName);
  685. }
  686. if (str_starts_with($class, 'new ')) {
  687. return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
  688. }
  689. return sprintf(" [%s, '%s'](\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
  690. }
  691. return sprintf(" %s(\$%s);\n", $callable, $variableName);
  692. }
  693. private function addService(string $id, Definition $definition): array
  694. {
  695. $this->definitionVariables = new \SplObjectStorage();
  696. $this->referenceVariables = [];
  697. $this->variableCount = 0;
  698. $this->referenceVariables[$id] = new Variable('instance');
  699. $return = [];
  700. if ($class = $definition->getClass()) {
  701. $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
  702. $return[] = sprintf(str_starts_with($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\'));
  703. } elseif ($definition->getFactory()) {
  704. $factory = $definition->getFactory();
  705. if (\is_string($factory)) {
  706. $return[] = sprintf('@return object An instance returned by %s()', $factory);
  707. } elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) {
  708. $class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0];
  709. $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class);
  710. $return[] = sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]);
  711. }
  712. }
  713. if ($definition->isDeprecated()) {
  714. if ($return && str_starts_with($return[\count($return) - 1], '@return')) {
  715. $return[] = '';
  716. }
  717. $deprecation = $definition->getDeprecation($id);
  718. $return[] = sprintf('@deprecated %s', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']);
  719. }
  720. $return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return));
  721. $return = $this->container->resolveEnvPlaceholders($return);
  722. $shared = $definition->isShared() ? ' shared' : '';
  723. $public = $definition->isPublic() ? 'public' : 'private';
  724. $autowired = $definition->isAutowired() ? ' autowired' : '';
  725. $asFile = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition);
  726. $methodName = $this->generateMethodName($id);
  727. if ($asFile || $definition->isLazy()) {
  728. $lazyInitialization = '$lazyLoad = true';
  729. } else {
  730. $lazyInitialization = '';
  731. }
  732. $code = <<<EOF
  733. /*{$this->docStar}
  734. * Gets the $public '$id'$shared$autowired service.
  735. *
  736. * $return
  737. EOF;
  738. $code = str_replace('*/', ' ', $code).<<<EOF
  739. */
  740. protected function {$methodName}($lazyInitialization)
  741. {
  742. EOF;
  743. if ($asFile) {
  744. $file = $methodName.'.php';
  745. $code = str_replace("protected function {$methodName}(", 'public static function do($container, ', $code);
  746. } else {
  747. $file = null;
  748. }
  749. if ($definition->hasErrors() && $e = $definition->getErrors()) {
  750. $this->addThrow = true;
  751. $code .= sprintf(" \$this->throw(%s);\n", $this->export(reset($e)));
  752. } else {
  753. $this->serviceCalls = [];
  754. $this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls);
  755. if ($definition->isDeprecated()) {
  756. $deprecation = $definition->getDeprecation($id);
  757. $code .= sprintf(" trigger_deprecation(%s, %s, %s);\n\n", $this->export($deprecation['package']), $this->export($deprecation['version']), $this->export($deprecation['message']));
  758. } elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) {
  759. foreach ($this->inlinedDefinitions as $def) {
  760. foreach ($this->getClasses($def, $id) as $class) {
  761. $this->preload[$class] = $class;
  762. }
  763. }
  764. }
  765. if (!$definition->isShared()) {
  766. $factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id));
  767. }
  768. if ($isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition)) {
  769. if (!$definition->isShared()) {
  770. $code .= sprintf(' %s = %1$s ?? ', $factory);
  771. if ($asFile) {
  772. $code .= "function () {\n";
  773. $code .= " return self::do(\$container);\n";
  774. $code .= " };\n\n";
  775. } else {
  776. $code .= sprintf("\\Closure::fromCallable([\$this, '%s']);\n\n", $methodName);
  777. }
  778. }
  779. $factoryCode = $asFile ? 'self::do($container, false)' : sprintf('$this->%s(false)', $methodName);
  780. $factoryCode = $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode);
  781. $code .= $asFile ? preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $factoryCode) : $factoryCode;
  782. }
  783. $c = $this->addServiceInclude($id, $definition);
  784. if ('' !== $c && $isProxyCandidate && !$definition->isShared()) {
  785. $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c)));
  786. $code .= " static \$include = true;\n\n";
  787. $code .= " if (\$include) {\n";
  788. $code .= $c;
  789. $code .= " \$include = false;\n";
  790. $code .= " }\n\n";
  791. } else {
  792. $code .= $c;
  793. }
  794. $c = $this->addInlineService($id, $definition);
  795. if (!$isProxyCandidate && !$definition->isShared()) {
  796. $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c)));
  797. $lazyloadInitialization = $definition->isLazy() ? '$lazyLoad = true' : '';
  798. $c = sprintf(" %s = function (%s) {\n%s };\n\n return %1\$s();\n", $factory, $lazyloadInitialization, $c);
  799. }
  800. $code .= $c;
  801. }
  802. if ($asFile) {
  803. $code = str_replace('$this', '$container', $code);
  804. $code = preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $code);
  805. }
  806. $code .= " }\n";
  807. $this->definitionVariables = $this->inlinedDefinitions = null;
  808. $this->referenceVariables = $this->serviceCalls = null;
  809. return [$file, $code];
  810. }
  811. private function addInlineVariables(string $id, Definition $definition, array $arguments, bool $forConstructor): string
  812. {
  813. $code = '';
  814. foreach ($arguments as $argument) {
  815. if (\is_array($argument)) {
  816. $code .= $this->addInlineVariables($id, $definition, $argument, $forConstructor);
  817. } elseif ($argument instanceof Reference) {
  818. $code .= $this->addInlineReference($id, $definition, $argument, $forConstructor);
  819. } elseif ($argument instanceof Definition) {
  820. $code .= $this->addInlineService($id, $definition, $argument, $forConstructor);
  821. }
  822. }
  823. return $code;
  824. }
  825. private function addInlineReference(string $id, Definition $definition, string $targetId, bool $forConstructor): string
  826. {
  827. while ($this->container->hasAlias($targetId)) {
  828. $targetId = (string) $this->container->getAlias($targetId);
  829. }
  830. [$callCount, $behavior] = $this->serviceCalls[$targetId];
  831. if ($id === $targetId) {
  832. return $this->addInlineService($id, $definition, $definition);
  833. }
  834. if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) {
  835. return '';
  836. }
  837. if ($this->container->hasDefinition($targetId) && ($def = $this->container->getDefinition($targetId)) && !$def->isShared()) {
  838. return '';
  839. }
  840. $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]) && !($this->hasProxyDumper && $definition->isLazy());
  841. if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) {
  842. $code = $this->addInlineService($id, $definition, $definition);
  843. } else {
  844. $code = '';
  845. }
  846. if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) {
  847. return $code;
  848. }
  849. $name = $this->getNextVariableName();
  850. $this->referenceVariables[$targetId] = new Variable($name);
  851. $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null;
  852. $code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference));
  853. if (!$hasSelfRef || !$forConstructor) {
  854. return $code;
  855. }
  856. $code .= sprintf(<<<'EOTXT'
  857. if (isset($this->%s[%s])) {
  858. return $this->%1$s[%2$s];
  859. }
  860. EOTXT
  861. ,
  862. $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates',
  863. $this->doExport($id)
  864. );
  865. return $code;
  866. }
  867. private function addInlineService(string $id, Definition $definition, ?Definition $inlineDef = null, bool $forConstructor = true): string
  868. {
  869. $code = '';
  870. if ($isSimpleInstance = $isRootInstance = null === $inlineDef) {
  871. foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) {
  872. if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId] && !($this->hasProxyDumper && $definition->isLazy())) {
  873. $code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor);
  874. }
  875. }
  876. }
  877. if (isset($this->definitionVariables[$inlineDef = $inlineDef ?: $definition])) {
  878. return $code;
  879. }
  880. $arguments = [$inlineDef->getArguments(), $inlineDef->getFactory()];
  881. $code .= $this->addInlineVariables($id, $definition, $arguments, $forConstructor);
  882. if ($arguments = array_filter([$inlineDef->getProperties(), $inlineDef->getMethodCalls(), $inlineDef->getConfigurator()])) {
  883. $isSimpleInstance = false;
  884. } elseif ($definition !== $inlineDef && 2 > $this->inlinedDefinitions[$inlineDef]) {
  885. return $code;
  886. }
  887. if (isset($this->definitionVariables[$inlineDef])) {
  888. $isSimpleInstance = false;
  889. } else {
  890. $name = $definition === $inlineDef ? 'instance' : $this->getNextVariableName();
  891. $this->definitionVariables[$inlineDef] = new Variable($name);
  892. $code .= '' !== $code ? "\n" : '';
  893. if ('instance' === $name) {
  894. $code .= $this->addServiceInstance($id, $definition, $isSimpleInstance);
  895. } else {
  896. $code .= $this->addNewInstance($inlineDef, ' $'.$name.' = ', $id);
  897. }
  898. if ('' !== $inline = $this->addInlineVariables($id, $definition, $arguments, false)) {
  899. $code .= "\n".$inline."\n";
  900. } elseif ($arguments && 'instance' === $name) {
  901. $code .= "\n";
  902. }
  903. $code .= $this->addServiceProperties($inlineDef, $name);
  904. $code .= $this->addServiceMethodCalls($inlineDef, $name, !$this->getProxyDumper()->isProxyCandidate($inlineDef) && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null);
  905. $code .= $this->addServiceConfigurator($inlineDef, $name);
  906. }
  907. if ($isRootInstance && !$isSimpleInstance) {
  908. $code .= "\n return \$instance;\n";
  909. }
  910. return $code;
  911. }
  912. private function addServices(?array &$services = null): string
  913. {
  914. $publicServices = $privateServices = '';
  915. $definitions = $this->container->getDefinitions();
  916. ksort($definitions);
  917. foreach ($definitions as $id => $definition) {
  918. if (!$definition->isSynthetic()) {
  919. $services[$id] = $this->addService($id, $definition);
  920. } elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) {
  921. $services[$id] = null;
  922. foreach ($this->getClasses($definition, $id) as $class) {
  923. $this->preload[$class] = $class;
  924. }
  925. }
  926. }
  927. foreach ($definitions as $id => $definition) {
  928. if (!([$file, $code] = $services[$id]) || null !== $file) {
  929. continue;
  930. }
  931. if ($definition->isPublic()) {
  932. $publicServices .= $code;
  933. } elseif (!$this->isTrivialInstance($definition) || isset($this->locatedIds[$id])) {
  934. $privateServices .= $code;
  935. }
  936. }
  937. return $publicServices.$privateServices;
  938. }
  939. private function generateServiceFiles(array $services): iterable
  940. {
  941. $definitions = $this->container->getDefinitions();
  942. ksort($definitions);
  943. foreach ($definitions as $id => $definition) {
  944. if (([$file, $code] = $services[$id]) && null !== $file && ($definition->isPublic() || !$this->isTrivialInstance($definition) || isset($this->locatedIds[$id]))) {
  945. yield $file => [$code, $definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1]) && !$definition->isDeprecated() && !$definition->hasErrors()];
  946. }
  947. }
  948. }
  949. private function addNewInstance(Definition $definition, string $return = '', ?string $id = null): string
  950. {
  951. $tail = $return ? ";\n" : '';
  952. if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) {
  953. $arguments = [];
  954. foreach ($definition->getArgument(0) as $k => $argument) {
  955. $arguments[$k] = $argument->getValues()[0];
  956. }
  957. return $return.$this->dumpValue(new ServiceLocatorArgument($arguments)).$tail;
  958. }
  959. $arguments = [];
  960. foreach ($definition->getArguments() as $i => $value) {
  961. $arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value);
  962. }
  963. if (null !== $definition->getFactory()) {
  964. $callable = $definition->getFactory();
  965. if (\is_array($callable)) {
  966. if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) {
  967. throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a'));
  968. }
  969. if ($callable[0] instanceof Reference
  970. || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) {
  971. return $return.sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
  972. }
  973. $class = $this->dumpValue($callable[0]);
  974. // If the class is a string we can optimize away
  975. if (str_starts_with($class, "'") && !str_contains($class, '$')) {
  976. if ("''" === $class) {
  977. throw new RuntimeException(sprintf('Cannot dump definition: "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline'));
  978. }
  979. return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
  980. }
  981. if (str_starts_with($class, 'new ')) {
  982. return $return.sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
  983. }
  984. return $return.sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
  985. }
  986. return $return.sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail;
  987. }
  988. if (null === $class = $definition->getClass()) {
  989. throw new RuntimeException('Cannot dump definitions which have no class nor factory.');
  990. }
  991. return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail;
  992. }
  993. private function startClass(string $class, string $baseClass, bool $hasProxyClasses): string
  994. {
  995. $namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : '';
  996. $code = <<<EOF
  997. <?php
  998. $namespaceLine
  999. use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
  1000. use Symfony\Component\DependencyInjection\ContainerInterface;
  1001. use Symfony\Component\DependencyInjection\Container;
  1002. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  1003. use Symfony\Component\DependencyInjection\Exception\LogicException;
  1004. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  1005. use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
  1006. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  1007. /*{$this->docStar}
  1008. * @internal This class has been auto-generated by the Symfony Dependency Injection Component.
  1009. */
  1010. class $class extends $baseClass
  1011. {
  1012. protected \$parameters = [];
  1013. public function __construct()
  1014. {
  1015. EOF;
  1016. if ($this->asFiles) {
  1017. $code = str_replace('$parameters = []', "\$containerDir;\n protected \$parameters = [];\n private \$buildParameters", $code);
  1018. $code = str_replace('__construct()', '__construct(array $buildParameters = [], $containerDir = __DIR__)', $code);
  1019. $code .= " \$this->buildParameters = \$buildParameters;\n";
  1020. $code .= " \$this->containerDir = \$containerDir;\n";
  1021. if (null !== $this->targetDirRegex) {
  1022. $code = str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code);
  1023. $code .= ' $this->targetDir = \\dirname($containerDir);'."\n";
  1024. }
  1025. }
  1026. if (Container::class !== $this->baseClass) {
  1027. $r = $this->container->getReflectionClass($this->baseClass, false);
  1028. if (null !== $r
  1029. && (null !== $constructor = $r->getConstructor())
  1030. && 0 === $constructor->getNumberOfRequiredParameters()
  1031. && Container::class !== $constructor->getDeclaringClass()->name
  1032. ) {
  1033. $code .= " parent::__construct();\n";
  1034. $code .= " \$this->parameterBag = null;\n\n";
  1035. }
  1036. }
  1037. if ($this->container->getParameterBag()->all()) {
  1038. $code .= " \$this->parameters = \$this->getDefaultParameters();\n\n";
  1039. }
  1040. $code .= " \$this->services = \$this->privates = [];\n";
  1041. $code .= $this->addSyntheticIds();
  1042. $code .= $this->addMethodMap();
  1043. $code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : '';
  1044. $code .= $this->addAliases();
  1045. $code .= $this->addInlineRequires($hasProxyClasses);
  1046. $code .= <<<EOF
  1047. }
  1048. public function compile(): void
  1049. {
  1050. throw new LogicException('You cannot compile a dumped container that was already compiled.');
  1051. }
  1052. public function isCompiled(): bool
  1053. {
  1054. return true;
  1055. }
  1056. EOF;
  1057. $code .= $this->addRemovedIds();
  1058. if ($this->asFiles && !$this->inlineFactories) {
  1059. $code .= <<<'EOF'
  1060. protected function load($file, $lazyLoad = true)
  1061. {
  1062. if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) {
  1063. return $class::do($this, $lazyLoad);
  1064. }
  1065. if ('.' === $file[-4]) {
  1066. $class = substr($class, 0, -4);
  1067. } else {
  1068. $file .= '.php';
  1069. }
  1070. $service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file;
  1071. return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service;
  1072. }
  1073. EOF;
  1074. }
  1075. $proxyDumper = $this->getProxyDumper();
  1076. foreach ($this->container->getDefinitions() as $definition) {
  1077. if (!$proxyDumper->isProxyCandidate($definition)) {
  1078. continue;
  1079. }
  1080. if ($this->asFiles && !$this->inlineFactories) {
  1081. $proxyLoader = "class_exists(\$class, false) || require __DIR__.'/'.\$class.'.php';\n\n ";
  1082. } else {
  1083. $proxyLoader = '';
  1084. }
  1085. $code .= <<<EOF
  1086. protected function createProxy(\$class, \Closure \$factory)
  1087. {
  1088. {$proxyLoader}return \$factory();
  1089. }
  1090. EOF;
  1091. break;
  1092. }
  1093. return $code;
  1094. }
  1095. private function addSyntheticIds(): string
  1096. {
  1097. $code = '';
  1098. $definitions = $this->container->getDefinitions();
  1099. ksort($definitions);
  1100. foreach ($definitions as $id => $definition) {
  1101. if ($definition->isSynthetic() && 'service_container' !== $id) {
  1102. $code .= ' '.$this->doExport($id)." => true,\n";
  1103. }
  1104. }
  1105. return $code ? " \$this->syntheticIds = [\n{$code} ];\n" : '';
  1106. }
  1107. private function addRemovedIds(): string
  1108. {
  1109. $ids = $this->container->getRemovedIds();
  1110. foreach ($this->container->getDefinitions() as $id => $definition) {
  1111. if (!$definition->isPublic()) {
  1112. $ids[$id] = true;
  1113. }
  1114. }
  1115. if (!$ids) {
  1116. return '';
  1117. }
  1118. if ($this->asFiles) {
  1119. $code = "require \$this->containerDir.\\DIRECTORY_SEPARATOR.'removed-ids.php'";
  1120. } else {
  1121. $code = '';
  1122. $ids = array_keys($ids);
  1123. sort($ids);
  1124. foreach ($ids as $id) {
  1125. if (preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id)) {
  1126. continue;
  1127. }
  1128. $code .= ' '.$this->doExport($id)." => true,\n";
  1129. }
  1130. $code = "[\n{$code} ]";
  1131. }
  1132. return <<<EOF
  1133. public function getRemovedIds(): array
  1134. {
  1135. return {$code};
  1136. }
  1137. EOF;
  1138. }
  1139. private function addMethodMap(): string
  1140. {
  1141. $code = '';
  1142. $definitions = $this->container->getDefinitions();
  1143. ksort($definitions);
  1144. foreach ($definitions as $id => $definition) {
  1145. if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->inlineFactories || $this->isHotPath($definition))) {
  1146. $code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n";
  1147. }
  1148. }
  1149. $aliases = $this->container->getAliases();
  1150. foreach ($aliases as $alias => $id) {
  1151. if (!$id->isDeprecated()) {
  1152. continue;
  1153. }
  1154. $code .= ' '.$this->doExport($alias).' => '.$this->doExport($this->generateMethodName($alias)).",\n";
  1155. }
  1156. return $code ? " \$this->methodMap = [\n{$code} ];\n" : '';
  1157. }
  1158. private function addFileMap(): string
  1159. {
  1160. $code = '';
  1161. $definitions = $this->container->getDefinitions();
  1162. ksort($definitions);
  1163. foreach ($definitions as $id => $definition) {
  1164. if (!$definition->isSynthetic() && $definition->isPublic() && !$this->isHotPath($definition)) {
  1165. $code .= sprintf(" %s => '%s',\n", $this->doExport($id), $this->generateMethodName($id));
  1166. }
  1167. }
  1168. return $code ? " \$this->fileMap = [\n{$code} ];\n" : '';
  1169. }
  1170. private function addAliases(): string
  1171. {
  1172. if (!$aliases = $this->container->getAliases()) {
  1173. return "\n \$this->aliases = [];\n";
  1174. }
  1175. $code = " \$this->aliases = [\n";
  1176. ksort($aliases);
  1177. foreach ($aliases as $alias => $id) {
  1178. if ($id->isDeprecated()) {
  1179. continue;
  1180. }
  1181. $id = (string) $id;
  1182. while (isset($aliases[$id])) {
  1183. $id = (string) $aliases[$id];
  1184. }
  1185. $code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n";
  1186. }
  1187. return $code." ];\n";
  1188. }
  1189. private function addDeprecatedAliases(): string
  1190. {
  1191. $code = '';
  1192. $aliases = $this->container->getAliases();
  1193. foreach ($aliases as $alias => $definition) {
  1194. if (!$definition->isDeprecated()) {
  1195. continue;
  1196. }
  1197. $public = $definition->isPublic() ? 'public' : 'private';
  1198. $id = (string) $definition;
  1199. $methodNameAlias = $this->generateMethodName($alias);
  1200. $idExported = $this->export($id);
  1201. $deprecation = $definition->getDeprecation($alias);
  1202. $packageExported = $this->export($deprecation['package']);
  1203. $versionExported = $this->export($deprecation['version']);
  1204. $messageExported = $this->export($deprecation['message']);
  1205. $code .= <<<EOF
  1206. /*{$this->docStar}
  1207. * Gets the $public '$alias' alias.
  1208. *
  1209. * @return object The "$id" service.
  1210. */
  1211. protected function {$methodNameAlias}()
  1212. {
  1213. trigger_deprecation($packageExported, $versionExported, $messageExported);
  1214. return \$this->get($idExported);
  1215. }
  1216. EOF;
  1217. }
  1218. return $code;
  1219. }
  1220. private function addInlineRequires(bool $hasProxyClasses): string
  1221. {
  1222. $lineage = [];
  1223. $hotPathServices = $this->hotPathTag && $this->inlineRequires ? $this->container->findTaggedServiceIds($this->hotPathTag) : [];
  1224. foreach ($hotPathServices as $id => $tags) {
  1225. $definition = $this->container->getDefinition($id);
  1226. if ($this->getProxyDumper()->isProxyCandidate($definition)) {
  1227. continue;
  1228. }
  1229. $inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]);
  1230. foreach ($inlinedDefinitions as $def) {
  1231. foreach ($this->getClasses($def, $id) as $class) {
  1232. $this->collectLineage($class, $lineage);
  1233. }
  1234. }
  1235. }
  1236. $code = '';
  1237. foreach ($lineage as $file) {
  1238. if (!isset($this->inlinedRequires[$file])) {
  1239. $this->inlinedRequires[$file] = true;
  1240. $code .= sprintf("\n include_once %s;", $file);
  1241. }
  1242. }
  1243. if ($hasProxyClasses) {
  1244. $code .= "\n include_once __DIR__.'/proxy-classes.php';";
  1245. }
  1246. return $code ? sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : '';
  1247. }
  1248. private function addDefaultParametersMethod(): string
  1249. {
  1250. if (!$this->container->getParameterBag()->all()) {
  1251. return '';
  1252. }
  1253. $php = [];
  1254. $dynamicPhp = [];
  1255. foreach ($this->container->getParameterBag()->all() as $key => $value) {
  1256. if ($key !== $resolvedKey = $this->container->resolveEnvPlaceholders($key)) {
  1257. throw new InvalidArgumentException(sprintf('Parameter name cannot use env parameters: "%s".', $resolvedKey));
  1258. }
  1259. $hasEnum = false;
  1260. $export = $this->exportParameters([$value], '', 12, $hasEnum);
  1261. $export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2);
  1262. if ($hasEnum || preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) {
  1263. $dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]);
  1264. $this->dynamicParameters[$key] = true;
  1265. } else {
  1266. $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
  1267. }
  1268. }
  1269. $parameters = sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', 8));
  1270. $code = <<<'EOF'
  1271. /**
  1272. * @return array|bool|float|int|string|\UnitEnum|null
  1273. */
  1274. public function getParameter(string $name)
  1275. {
  1276. if (isset($this->buildParameters[$name])) {
  1277. return $this->buildParameters[$name];
  1278. }
  1279. if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) {
  1280. throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
  1281. }
  1282. if (isset($this->loadedDynamicParameters[$name])) {
  1283. return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
  1284. }
  1285. return $this->parameters[$name];
  1286. }
  1287. public function hasParameter(string $name): bool
  1288. {
  1289. if (isset($this->buildParameters[$name])) {
  1290. return true;
  1291. }
  1292. return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters);
  1293. }
  1294. public function setParameter(string $name, $value): void
  1295. {
  1296. throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
  1297. }
  1298. public function getParameterBag(): ParameterBagInterface
  1299. {
  1300. if (null === $this->parameterBag) {
  1301. $parameters = $this->parameters;
  1302. foreach ($this->loadedDynamicParameters as $name => $loaded) {
  1303. $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
  1304. }
  1305. foreach ($this->buildParameters as $name => $value) {
  1306. $parameters[$name] = $value;
  1307. }
  1308. $this->parameterBag = new FrozenParameterBag($parameters);
  1309. }
  1310. return $this->parameterBag;
  1311. }
  1312. EOF;
  1313. if (!$this->asFiles) {
  1314. $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n\n?/m', '', $code);
  1315. }
  1316. if ($dynamicPhp) {
  1317. $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8);
  1318. $getDynamicParameter = <<<'EOF'
  1319. switch ($name) {
  1320. %s
  1321. default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%%s" must be defined.', $name));
  1322. }
  1323. $this->loadedDynamicParameters[$name] = true;
  1324. return $this->dynamicParameters[$name] = $value;
  1325. EOF;
  1326. $getDynamicParameter = sprintf($getDynamicParameter, implode("\n", $dynamicPhp));
  1327. } else {
  1328. $loadedDynamicParameters = '[]';
  1329. $getDynamicParameter = str_repeat(' ', 8).'throw new InvalidArgumentException(sprintf(\'The dynamic parameter "%s" must be defined.\', $name));';
  1330. }
  1331. $code .= <<<EOF
  1332. private \$loadedDynamicParameters = {$loadedDynamicParameters};
  1333. private \$dynamicParameters = [];
  1334. private function getDynamicParameter(string \$name)
  1335. {
  1336. {$getDynamicParameter}
  1337. }
  1338. protected function getDefaultParameters(): array
  1339. {
  1340. return $parameters;
  1341. }
  1342. EOF;
  1343. return $code;
  1344. }
  1345. /**
  1346. * @throws InvalidArgumentException
  1347. */
  1348. private function exportParameters(array $parameters, string $path = '', int $indent = 12, bool &$hasEnum = false): string
  1349. {
  1350. $php = [];
  1351. foreach ($parameters as $key => $value) {
  1352. if (\is_array($value)) {
  1353. $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4, $hasEnum);
  1354. } elseif ($value instanceof ArgumentInterface) {
  1355. throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', get_debug_type($value), $path.'/'.$key));
  1356. } elseif ($value instanceof Variable) {
  1357. throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key));
  1358. } elseif ($value instanceof Definition) {
  1359. throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain service definitions. Definition for "%s" found in "%s".', $value->getClass(), $path.'/'.$key));
  1360. } elseif ($value instanceof Reference) {
  1361. throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path.'/'.$key));
  1362. } elseif ($value instanceof Expression) {
  1363. throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain expressions. Expression "%s" found in "%s".', $value, $path.'/'.$key));
  1364. } elseif ($value instanceof \UnitEnum) {
  1365. $hasEnum = true;
  1366. $value = sprintf('\%s::%s', \get_class($value), $value->name);
  1367. } else {
  1368. $value = $this->export($value);
  1369. }
  1370. $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), $this->export($key), $value);
  1371. }
  1372. return sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', $indent - 4));
  1373. }
  1374. private function endClass(): string
  1375. {
  1376. if ($this->addThrow) {
  1377. return <<<'EOF'
  1378. protected function throw($message)
  1379. {
  1380. throw new RuntimeException($message);
  1381. }
  1382. }
  1383. EOF;
  1384. }
  1385. return <<<'EOF'
  1386. }
  1387. EOF;
  1388. }
  1389. private function wrapServiceConditionals($value, string $code): string
  1390. {
  1391. if (!$condition = $this->getServiceConditionals($value)) {
  1392. return $code;
  1393. }
  1394. // re-indent the wrapped code
  1395. $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code)));
  1396. return sprintf(" if (%s) {\n%s }\n", $condition, $code);
  1397. }
  1398. private function getServiceConditionals($value): string
  1399. {
  1400. $conditions = [];
  1401. foreach (ContainerBuilder::getInitializedConditionals($value) as $service) {
  1402. if (!$this->container->hasDefinition($service)) {
  1403. return 'false';
  1404. }
  1405. $conditions[] = sprintf('isset($this->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service));
  1406. }
  1407. foreach (ContainerBuilder::getServiceConditionals($value) as $service) {
  1408. if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) {
  1409. continue;
  1410. }
  1411. $conditions[] = sprintf('$this->has(%s)', $this->doExport($service));
  1412. }
  1413. if (!$conditions) {
  1414. return '';
  1415. }
  1416. return implode(' && ', $conditions);
  1417. }
  1418. private function getDefinitionsFromArguments(array $arguments, ?\SplObjectStorage $definitions = null, array &$calls = [], ?bool $byConstructor = null): \SplObjectStorage
  1419. {
  1420. if (null === $definitions) {
  1421. $definitions = new \SplObjectStorage();
  1422. }
  1423. foreach ($arguments as $argument) {
  1424. if (\is_array($argument)) {
  1425. $this->getDefinitionsFromArguments($argument, $definitions, $calls, $byConstructor);
  1426. } elseif ($argument instanceof Reference) {
  1427. $id = (string) $argument;
  1428. while ($this->container->hasAlias($id)) {
  1429. $id = (string) $this->container->getAlias($id);
  1430. }
  1431. if (!isset($calls[$id])) {
  1432. $calls[$id] = [0, $argument->getInvalidBehavior(), $byConstructor];
  1433. } else {
  1434. $calls[$id][1] = min($calls[$id][1], $argument->getInvalidBehavior());
  1435. }
  1436. ++$calls[$id][0];
  1437. } elseif (!$argument instanceof Definition) {
  1438. // no-op
  1439. } elseif (isset($definitions[$argument])) {
  1440. $definitions[$argument] = 1 + $definitions[$argument];
  1441. } else {
  1442. $definitions[$argument] = 1;
  1443. $arguments = [$argument->getArguments(), $argument->getFactory()];
  1444. $this->getDefinitionsFromArguments($arguments, $definitions, $calls, null === $byConstructor || $byConstructor);
  1445. $arguments = [$argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()];
  1446. $this->getDefinitionsFromArguments($arguments, $definitions, $calls, null !== $byConstructor && $byConstructor);
  1447. }
  1448. }
  1449. return $definitions;
  1450. }
  1451. /**
  1452. * @throws RuntimeException
  1453. */
  1454. private function dumpValue($value, bool $interpolate = true): string
  1455. {
  1456. if (\is_array($value)) {
  1457. if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) {
  1458. return $this->dumpValue("%$param%");
  1459. }
  1460. $code = [];
  1461. foreach ($value as $k => $v) {
  1462. $code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
  1463. }
  1464. return sprintf('[%s]', implode(', ', $code));
  1465. } elseif ($value instanceof ArgumentInterface) {
  1466. $scope = [$this->definitionVariables, $this->referenceVariables];
  1467. $this->definitionVariables = $this->referenceVariables = null;
  1468. try {
  1469. if ($value instanceof ServiceClosureArgument) {
  1470. $value = $value->getValues()[0];
  1471. $code = $this->dumpValue($value, $interpolate);
  1472. $returnedType = '';
  1473. if ($value instanceof TypedReference) {
  1474. $returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', str_replace(['|', '&'], ['|\\', '&\\'], $value->getType()));
  1475. }
  1476. $code = sprintf('return %s;', $code);
  1477. return sprintf("function ()%s {\n %s\n }", $returnedType, $code);
  1478. }
  1479. if ($value instanceof IteratorArgument) {
  1480. $operands = [0];
  1481. $code = [];
  1482. $code[] = 'new RewindableGenerator(function () {';
  1483. if (!$values = $value->getValues()) {
  1484. $code[] = ' return new \EmptyIterator();';
  1485. } else {
  1486. $countCode = [];
  1487. $countCode[] = 'function () {';
  1488. foreach ($values as $k => $v) {
  1489. ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0];
  1490. $v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)));
  1491. foreach (explode("\n", $v) as $v) {
  1492. if ($v) {
  1493. $code[] = ' '.$v;
  1494. }
  1495. }
  1496. }
  1497. $countCode[] = sprintf(' return %s;', implode(' + ', $operands));
  1498. $countCode[] = ' }';
  1499. }
  1500. $code[] = sprintf(' }, %s)', \count($operands) > 1 ? implode("\n", $countCode) : $operands[0]);
  1501. return implode("\n", $code);
  1502. }
  1503. if ($value instanceof ServiceLocatorArgument) {
  1504. $serviceMap = '';
  1505. $serviceTypes = '';
  1506. foreach ($value->getValues() as $k => $v) {
  1507. if (!$v) {
  1508. continue;
  1509. }
  1510. $id = (string) $v;
  1511. while ($this->container->hasAlias($id)) {
  1512. $id = (string) $this->container->getAlias($id);
  1513. }
  1514. $definition = $this->container->getDefinition($id);
  1515. $load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : reset($e);
  1516. $serviceMap .= sprintf("\n %s => [%s, %s, %s, %s],",
  1517. $this->export($k),
  1518. $this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false),
  1519. $this->doExport($id),
  1520. $this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id) : null),
  1521. $this->export($load)
  1522. );
  1523. $serviceTypes .= sprintf("\n %s => %s,", $this->export($k), $this->export($v instanceof TypedReference ? $v->getType() : '?'));
  1524. $this->locatedIds[$id] = true;
  1525. }
  1526. $this->addGetService = true;
  1527. return sprintf('new \%s($this->getService, [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : '');
  1528. }
  1529. } finally {
  1530. [$this->definitionVariables, $this->referenceVariables] = $scope;
  1531. }
  1532. } elseif ($value instanceof Definition) {
  1533. if ($value->hasErrors() && $e = $value->getErrors()) {
  1534. $this->addThrow = true;
  1535. return sprintf('$this->throw(%s)', $this->export(reset($e)));
  1536. }
  1537. if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) {
  1538. return $this->dumpValue($this->definitionVariables[$value], $interpolate);
  1539. }
  1540. if ($value->getMethodCalls()) {
  1541. throw new RuntimeException('Cannot dump definitions which have method calls.');
  1542. }
  1543. if ($value->getProperties()) {
  1544. throw new RuntimeException('Cannot dump definitions which have properties.');
  1545. }
  1546. if (null !== $value->getConfigurator()) {
  1547. throw new RuntimeException('Cannot dump definitions which have a configurator.');
  1548. }
  1549. return $this->addNewInstance($value);
  1550. } elseif ($value instanceof Variable) {
  1551. return '$'.$value;
  1552. } elseif ($value instanceof Reference) {
  1553. $id = (string) $value;
  1554. while ($this->container->hasAlias($id)) {
  1555. $id = (string) $this->container->getAlias($id);
  1556. }
  1557. if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) {
  1558. return $this->dumpValue($this->referenceVariables[$id], $interpolate);
  1559. }
  1560. return $this->getServiceCall($id, $value);
  1561. } elseif ($value instanceof Expression) {
  1562. return $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']);
  1563. } elseif ($value instanceof Parameter) {
  1564. return $this->dumpParameter($value);
  1565. } elseif (true === $interpolate && \is_string($value)) {
  1566. if (preg_match('/^%([^%]+)%$/', $value, $match)) {
  1567. // we do this to deal with non string values (Boolean, integer, ...)
  1568. // the preg_replace_callback converts them to strings
  1569. return $this->dumpParameter($match[1]);
  1570. } else {
  1571. $replaceParameters = function ($match) {
  1572. return "'.".$this->dumpParameter($match[2]).".'";
  1573. };
  1574. $code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, $this->export($value)));
  1575. return $code;
  1576. }
  1577. } elseif ($value instanceof \UnitEnum) {
  1578. return sprintf('\%s::%s', \get_class($value), $value->name);
  1579. } elseif ($value instanceof AbstractArgument) {
  1580. throw new RuntimeException($value->getTextWithContext());
  1581. } elseif (\is_object($value) || \is_resource($value)) {
  1582. throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
  1583. }
  1584. return $this->export($value);
  1585. }
  1586. /**
  1587. * Dumps a string to a literal (aka PHP Code) class value.
  1588. *
  1589. * @throws RuntimeException
  1590. */
  1591. private function dumpLiteralClass(string $class): string
  1592. {
  1593. if (str_contains($class, '$')) {
  1594. return sprintf('${($_ = %s) && false ?: "_"}', $class);
  1595. }
  1596. if (!str_starts_with($class, "'") || !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
  1597. throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s).', $class ?: 'n/a'));
  1598. }
  1599. $class = substr(str_replace('\\\\', '\\', $class), 1, -1);
  1600. return str_starts_with($class, '\\') ? $class : '\\'.$class;
  1601. }
  1602. private function dumpParameter(string $name): string
  1603. {
  1604. if (!$this->container->hasParameter($name) || ($this->dynamicParameters[$name] ?? false)) {
  1605. return sprintf('$this->getParameter(%s)', $this->doExport($name));
  1606. }
  1607. $value = $this->container->getParameter($name);
  1608. $dumpedValue = $this->dumpValue($value, false);
  1609. if (!$value || !\is_array($value)) {
  1610. return $dumpedValue;
  1611. }
  1612. return sprintf('$this->parameters[%s]', $this->doExport($name));
  1613. }
  1614. private function getServiceCall(string $id, ?Reference $reference = null): string
  1615. {
  1616. while ($this->container->hasAlias($id)) {
  1617. $id = (string) $this->container->getAlias($id);
  1618. }
  1619. if ('service_container' === $id) {
  1620. return '$this';
  1621. }
  1622. if ($this->container->hasDefinition($id) && $definition = $this->container->getDefinition($id)) {
  1623. if ($definition->isSynthetic()) {
  1624. $code = sprintf('$this->get(%s%s)', $this->doExport($id), null !== $reference ? ', '.$reference->getInvalidBehavior() : '');
  1625. } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
  1626. $code = 'null';
  1627. if (!$definition->isShared()) {
  1628. return $code;
  1629. }
  1630. } elseif ($this->isTrivialInstance($definition)) {
  1631. if ($definition->hasErrors() && $e = $definition->getErrors()) {
  1632. $this->addThrow = true;
  1633. return sprintf('$this->throw(%s)', $this->export(reset($e)));
  1634. }
  1635. $code = $this->addNewInstance($definition, '', $id);
  1636. if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) {
  1637. $code = sprintf('$this->%s[%s] = %s', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code);
  1638. }
  1639. $code = "($code)";
  1640. } else {
  1641. $code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$this->load('%s')" : '$this->%s()';
  1642. $code = sprintf($code, $this->generateMethodName($id));
  1643. if (!$definition->isShared()) {
  1644. $factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id));
  1645. $code = sprintf('(isset(%s) ? %1$s() : %s)', $factory, $code);
  1646. }
  1647. }
  1648. if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) {
  1649. $code = sprintf('($this->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code);
  1650. }
  1651. return $code;
  1652. }
  1653. if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
  1654. return 'null';
  1655. }
  1656. if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) {
  1657. $code = sprintf('$this->get(%s, /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $this->doExport($id), ContainerInterface::NULL_ON_INVALID_REFERENCE);
  1658. } else {
  1659. $code = sprintf('$this->get(%s)', $this->doExport($id));
  1660. }
  1661. return sprintf('($this->services[%s] ?? %s)', $this->doExport($id), $code);
  1662. }
  1663. /**
  1664. * Initializes the method names map to avoid conflicts with the Container methods.
  1665. */
  1666. private function initializeMethodNamesMap(string $class)
  1667. {
  1668. $this->serviceIdToMethodNameMap = [];
  1669. $this->usedMethodNames = [];
  1670. if ($reflectionClass = $this->container->getReflectionClass($class)) {
  1671. foreach ($reflectionClass->getMethods() as $method) {
  1672. $this->usedMethodNames[strtolower($method->getName())] = true;
  1673. }
  1674. }
  1675. }
  1676. /**
  1677. * @throws InvalidArgumentException
  1678. */
  1679. private function generateMethodName(string $id): string
  1680. {
  1681. if (isset($this->serviceIdToMethodNameMap[$id])) {
  1682. return $this->serviceIdToMethodNameMap[$id];
  1683. }
  1684. $i = strrpos($id, '\\');
  1685. $name = Container::camelize(false !== $i && isset($id[1 + $i]) ? substr($id, 1 + $i) : $id);
  1686. $name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name);
  1687. $methodName = 'get'.$name.'Service';
  1688. $suffix = 1;
  1689. while (isset($this->usedMethodNames[strtolower($methodName)])) {
  1690. ++$suffix;
  1691. $methodName = 'get'.$name.$suffix.'Service';
  1692. }
  1693. $this->serviceIdToMethodNameMap[$id] = $methodName;
  1694. $this->usedMethodNames[strtolower($methodName)] = true;
  1695. return $methodName;
  1696. }
  1697. private function getNextVariableName(): string
  1698. {
  1699. $firstChars = self::FIRST_CHARS;
  1700. $firstCharsLength = \strlen($firstChars);
  1701. $nonFirstChars = self::NON_FIRST_CHARS;
  1702. $nonFirstCharsLength = \strlen($nonFirstChars);
  1703. while (true) {
  1704. $name = '';
  1705. $i = $this->variableCount;
  1706. if ('' === $name) {
  1707. $name .= $firstChars[$i % $firstCharsLength];
  1708. $i = (int) ($i / $firstCharsLength);
  1709. }
  1710. while ($i > 0) {
  1711. --$i;
  1712. $name .= $nonFirstChars[$i % $nonFirstCharsLength];
  1713. $i = (int) ($i / $nonFirstCharsLength);
  1714. }
  1715. ++$this->variableCount;
  1716. // check that the name is not reserved
  1717. if (\in_array($name, $this->reservedVariables, true)) {
  1718. continue;
  1719. }
  1720. return $name;
  1721. }
  1722. }
  1723. private function getExpressionLanguage(): ExpressionLanguage
  1724. {
  1725. if (null === $this->expressionLanguage) {
  1726. if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) {
  1727. throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
  1728. }
  1729. $providers = $this->container->getExpressionLanguageProviders();
  1730. $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
  1731. $id = '""' === substr_replace($arg, '', 1, -1) ? stripcslashes(substr($arg, 1, -1)) : null;
  1732. if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) {
  1733. return $this->getServiceCall($id);
  1734. }
  1735. return sprintf('$this->get(%s)', $arg);
  1736. });
  1737. if ($this->container->isTrackingResources()) {
  1738. foreach ($providers as $provider) {
  1739. $this->container->addObjectResource($provider);
  1740. }
  1741. }
  1742. }
  1743. return $this->expressionLanguage;
  1744. }
  1745. private function isHotPath(Definition $definition): bool
  1746. {
  1747. return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated();
  1748. }
  1749. private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool
  1750. {
  1751. if ($node->getValue()->isPublic()) {
  1752. return false;
  1753. }
  1754. $ids = [];
  1755. foreach ($node->getInEdges() as $edge) {
  1756. if (!$value = $edge->getSourceNode()->getValue()) {
  1757. continue;
  1758. }
  1759. if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) {
  1760. return false;
  1761. }
  1762. $ids[$edge->getSourceNode()->getId()] = true;
  1763. }
  1764. return 1 === \count($ids);
  1765. }
  1766. /**
  1767. * @return mixed
  1768. */
  1769. private function export($value)
  1770. {
  1771. if (null !== $this->targetDirRegex && \is_string($value) && preg_match($this->targetDirRegex, $value, $matches, \PREG_OFFSET_CAPTURE)) {
  1772. $suffix = $matches[0][1] + \strlen($matches[0][0]);
  1773. $matches[0][1] += \strlen($matches[1][0]);
  1774. $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1]), true).'.' : '';
  1775. if ('\\' === \DIRECTORY_SEPARATOR && isset($value[$suffix])) {
  1776. $cookie = '\\'.random_int(100000, \PHP_INT_MAX);
  1777. $suffix = '.'.$this->doExport(str_replace('\\', $cookie, substr($value, $suffix)), true);
  1778. $suffix = str_replace('\\'.$cookie, "'.\\DIRECTORY_SEPARATOR.'", $suffix);
  1779. } else {
  1780. $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : '';
  1781. }
  1782. $dirname = $this->asFiles ? '$this->containerDir' : '__DIR__';
  1783. $offset = 2 + $this->targetDirMaxMatches - \count($matches);
  1784. if (0 < $offset) {
  1785. $dirname = sprintf('\dirname(__DIR__, %d)', $offset + (int) $this->asFiles);
  1786. } elseif ($this->asFiles) {
  1787. $dirname = "\$this->targetDir.''"; // empty string concatenation on purpose
  1788. }
  1789. if ($prefix || $suffix) {
  1790. return sprintf('(%s%s%s)', $prefix, $dirname, $suffix);
  1791. }
  1792. return $dirname;
  1793. }
  1794. return $this->doExport($value, true);
  1795. }
  1796. /**
  1797. * @return mixed
  1798. */
  1799. private function doExport($value, bool $resolveEnv = false)
  1800. {
  1801. $shouldCacheValue = $resolveEnv && \is_string($value);
  1802. if ($shouldCacheValue && isset($this->exportedVariables[$value])) {
  1803. return $this->exportedVariables[$value];
  1804. }
  1805. if (\is_string($value) && str_contains($value, "\n")) {
  1806. $cleanParts = explode("\n", $value);
  1807. $cleanParts = array_map(function ($part) { return var_export($part, true); }, $cleanParts);
  1808. $export = implode('."\n".', $cleanParts);
  1809. } else {
  1810. $export = var_export($value, true);
  1811. }
  1812. if ($this->asFiles) {
  1813. if (false !== strpos($export, '$this')) {
  1814. $export = str_replace('$this', "$'.'this", $export);
  1815. }
  1816. if (false !== strpos($export, 'function () {')) {
  1817. $export = str_replace('function () {', "function ('.') {", $export);
  1818. }
  1819. }
  1820. if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) {
  1821. $export = $resolvedExport;
  1822. if (str_ends_with($export, ".''")) {
  1823. $export = substr($export, 0, -3);
  1824. if ("'" === $export[1]) {
  1825. $export = substr_replace($export, '', 18, 7);
  1826. }
  1827. }
  1828. if ("'" === $export[1]) {
  1829. $export = substr($export, 3);
  1830. }
  1831. }
  1832. if ($shouldCacheValue) {
  1833. $this->exportedVariables[$value] = $export;
  1834. }
  1835. return $export;
  1836. }
  1837. private function getAutoloadFile(): ?string
  1838. {
  1839. $file = null;
  1840. foreach (spl_autoload_functions() as $autoloader) {
  1841. if (!\is_array($autoloader)) {
  1842. continue;
  1843. }
  1844. if ($autoloader[0] instanceof DebugClassLoader || $autoloader[0] instanceof LegacyDebugClassLoader) {
  1845. $autoloader = $autoloader[0]->getClassLoader();
  1846. }
  1847. if (!\is_array($autoloader) || !$autoloader[0] instanceof ClassLoader || !$autoloader[0]->findFile(__CLASS__)) {
  1848. continue;
  1849. }
  1850. foreach (get_declared_classes() as $class) {
  1851. if (str_starts_with($class, 'ComposerAutoloaderInit') && $class::getLoader() === $autoloader[0]) {
  1852. $file = \dirname((new \ReflectionClass($class))->getFileName(), 2).'/autoload.php';
  1853. if (null !== $this->targetDirRegex && preg_match($this->targetDirRegex.'A', $file)) {
  1854. return $file;
  1855. }
  1856. }
  1857. }
  1858. }
  1859. return $file;
  1860. }
  1861. private function getClasses(Definition $definition, string $id): array
  1862. {
  1863. $classes = [];
  1864. while ($definition instanceof Definition) {
  1865. foreach ($definition->getTag($this->preloadTags[0]) as $tag) {
  1866. if (!isset($tag['class'])) {
  1867. throw new InvalidArgumentException(sprintf('Missing attribute "class" on tag "%s" for service "%s".', $this->preloadTags[0], $id));
  1868. }
  1869. $classes[] = trim($tag['class'], '\\');
  1870. }
  1871. if ($class = $definition->getClass()) {
  1872. $classes[] = trim($class, '\\');
  1873. }
  1874. $factory = $definition->getFactory();
  1875. if (!\is_array($factory)) {
  1876. $factory = [$factory];
  1877. }
  1878. if (\is_string($factory[0])) {
  1879. if (false !== $i = strrpos($factory[0], '::')) {
  1880. $factory[0] = substr($factory[0], 0, $i);
  1881. }
  1882. $classes[] = trim($factory[0], '\\');
  1883. }
  1884. $definition = $factory[0];
  1885. }
  1886. return $classes;
  1887. }
  1888. }