PhpDumper.php 84 KB

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