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