PhpDumper.php 82 KB

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