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
12namespace Symfony\Component\DependencyInjection;
13
14use Psr\Container\ContainerInterface as PsrContainerInterface;
15use Symfony\Component\Config\Resource\ClassExistenceResource;
16use Symfony\Component\Config\Resource\ComposerResource;
17use Symfony\Component\Config\Resource\DirectoryResource;
18use Symfony\Component\Config\Resource\FileExistenceResource;
19use Symfony\Component\Config\Resource\FileResource;
20use Symfony\Component\Config\Resource\GlobResource;
21use Symfony\Component\Config\Resource\ReflectionClassResource;
22use Symfony\Component\Config\Resource\ResourceInterface;
23use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
24use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
25use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
26use Symfony\Component\DependencyInjection\Compiler\Compiler;
27use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
28use Symfony\Component\DependencyInjection\Compiler\PassConfig;
29use Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass;
30use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
31use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
32use Symfony\Component\DependencyInjection\Exception\LogicException;
33use Symfony\Component\DependencyInjection\Exception\RuntimeException;
34use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
35use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
36use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
37use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
38use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
39use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
40use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
41use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
42use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
43use Symfony\Component\ExpressionLanguage\Expression;
44use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
45
46/**
47 * ContainerBuilder is a DI container that provides an API to easily describe services.
48 *
49 * @author Fabien Potencier <fabien@symfony.com>
50 */
51class ContainerBuilder extends Container implements TaggedContainerInterface
52{
53    /**
54     * @var ExtensionInterface[]
55     */
56    private $extensions = [];
57
58    /**
59     * @var ExtensionInterface[]
60     */
61    private $extensionsByNs = [];
62
63    /**
64     * @var Definition[]
65     */
66    private $definitions = [];
67
68    /**
69     * @var Alias[]
70     */
71    private $aliasDefinitions = [];
72
73    /**
74     * @var ResourceInterface[]
75     */
76    private $resources = [];
77
78    private $extensionConfigs = [];
79
80    /**
81     * @var Compiler
82     */
83    private $compiler;
84
85    private $trackResources;
86
87    /**
88     * @var InstantiatorInterface|null
89     */
90    private $proxyInstantiator;
91
92    /**
93     * @var ExpressionLanguage|null
94     */
95    private $expressionLanguage;
96
97    /**
98     * @var ExpressionFunctionProviderInterface[]
99     */
100    private $expressionLanguageProviders = [];
101
102    /**
103     * @var string[] with tag names used by findTaggedServiceIds
104     */
105    private $usedTags = [];
106
107    /**
108     * @var string[][] a map of env var names to their placeholders
109     */
110    private $envPlaceholders = [];
111
112    /**
113     * @var int[] a map of env vars to their resolution counter
114     */
115    private $envCounters = [];
116
117    /**
118     * @var string[] the list of vendor directories
119     */
120    private $vendors;
121
122    private $autoconfiguredInstanceof = [];
123
124    private $removedIds = [];
125
126    private $removedBindingIds = [];
127
128    private static $internalTypes = [
129        'int' => true,
130        'float' => true,
131        'string' => true,
132        'bool' => true,
133        'resource' => true,
134        'object' => true,
135        'array' => true,
136        'null' => true,
137        'callable' => true,
138        'iterable' => true,
139        'mixed' => true,
140    ];
141
142    public function __construct(ParameterBagInterface $parameterBag = null)
143    {
144        parent::__construct($parameterBag);
145
146        $this->trackResources = interface_exists('Symfony\Component\Config\Resource\ResourceInterface');
147        $this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(true)->setPublic(true));
148        $this->setAlias(PsrContainerInterface::class, new Alias('service_container', false));
149        $this->setAlias(ContainerInterface::class, new Alias('service_container', false));
150    }
151
152    /**
153     * @var \ReflectionClass[] a list of class reflectors
154     */
155    private $classReflectors;
156
157    /**
158     * Sets the track resources flag.
159     *
160     * If you are not using the loaders and therefore don't want
161     * to depend on the Config component, set this flag to false.
162     *
163     * @param bool $track True if you want to track resources, false otherwise
164     */
165    public function setResourceTracking($track)
166    {
167        $this->trackResources = (bool) $track;
168    }
169
170    /**
171     * Checks if resources are tracked.
172     *
173     * @return bool true If resources are tracked, false otherwise
174     */
175    public function isTrackingResources()
176    {
177        return $this->trackResources;
178    }
179
180    /**
181     * Sets the instantiator to be used when fetching proxies.
182     */
183    public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator)
184    {
185        $this->proxyInstantiator = $proxyInstantiator;
186    }
187
188    public function registerExtension(ExtensionInterface $extension)
189    {
190        $this->extensions[$extension->getAlias()] = $extension;
191
192        if (false !== $extension->getNamespace()) {
193            $this->extensionsByNs[$extension->getNamespace()] = $extension;
194        }
195    }
196
197    /**
198     * Returns an extension by alias or namespace.
199     *
200     * @param string $name An alias or a namespace
201     *
202     * @return ExtensionInterface An extension instance
203     *
204     * @throws LogicException if the extension is not registered
205     */
206    public function getExtension($name)
207    {
208        if (isset($this->extensions[$name])) {
209            return $this->extensions[$name];
210        }
211
212        if (isset($this->extensionsByNs[$name])) {
213            return $this->extensionsByNs[$name];
214        }
215
216        throw new LogicException(sprintf('Container extension "%s" is not registered.', $name));
217    }
218
219    /**
220     * Returns all registered extensions.
221     *
222     * @return ExtensionInterface[] An array of ExtensionInterface
223     */
224    public function getExtensions()
225    {
226        return $this->extensions;
227    }
228
229    /**
230     * Checks if we have an extension.
231     *
232     * @param string $name The name of the extension
233     *
234     * @return bool If the extension exists
235     */
236    public function hasExtension($name)
237    {
238        return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]);
239    }
240
241    /**
242     * Returns an array of resources loaded to build this configuration.
243     *
244     * @return ResourceInterface[] An array of resources
245     */
246    public function getResources()
247    {
248        return array_values($this->resources);
249    }
250
251    /**
252     * @return $this
253     */
254    public function addResource(ResourceInterface $resource)
255    {
256        if (!$this->trackResources) {
257            return $this;
258        }
259
260        if ($resource instanceof GlobResource && $this->inVendors($resource->getPrefix())) {
261            return $this;
262        }
263
264        $this->resources[(string) $resource] = $resource;
265
266        return $this;
267    }
268
269    /**
270     * Sets the resources for this configuration.
271     *
272     * @param ResourceInterface[] $resources An array of resources
273     *
274     * @return $this
275     */
276    public function setResources(array $resources)
277    {
278        if (!$this->trackResources) {
279            return $this;
280        }
281
282        $this->resources = $resources;
283
284        return $this;
285    }
286
287    /**
288     * Adds the object class hierarchy as resources.
289     *
290     * @param object|string $object An object instance or class name
291     *
292     * @return $this
293     */
294    public function addObjectResource($object)
295    {
296        if ($this->trackResources) {
297            if (\is_object($object)) {
298                $object = \get_class($object);
299            }
300            if (!isset($this->classReflectors[$object])) {
301                $this->classReflectors[$object] = new \ReflectionClass($object);
302            }
303            $class = $this->classReflectors[$object];
304
305            foreach ($class->getInterfaceNames() as $name) {
306                if (null === $interface = &$this->classReflectors[$name]) {
307                    $interface = new \ReflectionClass($name);
308                }
309                $file = $interface->getFileName();
310                if (false !== $file && file_exists($file)) {
311                    $this->fileExists($file);
312                }
313            }
314            do {
315                $file = $class->getFileName();
316                if (false !== $file && file_exists($file)) {
317                    $this->fileExists($file);
318                }
319                foreach ($class->getTraitNames() as $name) {
320                    $this->addObjectResource($name);
321                }
322            } while ($class = $class->getParentClass());
323        }
324
325        return $this;
326    }
327
328    /**
329     * Adds the given class hierarchy as resources.
330     *
331     * @return $this
332     *
333     * @deprecated since version 3.3, to be removed in 4.0. Use addObjectResource() or getReflectionClass() instead.
334     */
335    public function addClassResource(\ReflectionClass $class)
336    {
337        @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the addObjectResource() or the getReflectionClass() method instead.', E_USER_DEPRECATED);
338
339        return $this->addObjectResource($class->name);
340    }
341
342    /**
343     * Retrieves the requested reflection class and registers it for resource tracking.
344     *
345     * @param string $class
346     * @param bool   $throw
347     *
348     * @return \ReflectionClass|null
349     *
350     * @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true
351     *
352     * @final
353     */
354    public function getReflectionClass($class, $throw = true)
355    {
356        if (!$class = $this->getParameterBag()->resolveValue($class)) {
357            return null;
358        }
359
360        if (isset(self::$internalTypes[$class])) {
361            return null;
362        }
363
364        $resource = $classReflector = null;
365
366        try {
367            if (isset($this->classReflectors[$class])) {
368                $classReflector = $this->classReflectors[$class];
369            } elseif (class_exists(ClassExistenceResource::class)) {
370                $resource = new ClassExistenceResource($class, false);
371                $classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class);
372            } else {
373                $classReflector = class_exists($class) ? new \ReflectionClass($class) : false;
374            }
375        } catch (\ReflectionException $e) {
376            if ($throw) {
377                throw $e;
378            }
379        }
380
381        if ($this->trackResources) {
382            if (!$classReflector) {
383                $this->addResource($resource ?: new ClassExistenceResource($class, false));
384            } elseif (!$classReflector->isInternal()) {
385                $path = $classReflector->getFileName();
386
387                if (!$this->inVendors($path)) {
388                    $this->addResource(new ReflectionClassResource($classReflector, $this->vendors));
389                }
390            }
391            $this->classReflectors[$class] = $classReflector;
392        }
393
394        return $classReflector ?: null;
395    }
396
397    /**
398     * Checks whether the requested file or directory exists and registers the result for resource tracking.
399     *
400     * @param string      $path          The file or directory path for which to check the existence
401     * @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed,
402     *                                   it will be used as pattern for tracking contents of the requested directory
403     *
404     * @return bool
405     *
406     * @final
407     */
408    public function fileExists($path, $trackContents = true)
409    {
410        $exists = file_exists($path);
411
412        if (!$this->trackResources || $this->inVendors($path)) {
413            return $exists;
414        }
415
416        if (!$exists) {
417            $this->addResource(new FileExistenceResource($path));
418
419            return $exists;
420        }
421
422        if (is_dir($path)) {
423            if ($trackContents) {
424                $this->addResource(new DirectoryResource($path, \is_string($trackContents) ? $trackContents : null));
425            } else {
426                $this->addResource(new GlobResource($path, '/*', false));
427            }
428        } elseif ($trackContents) {
429            $this->addResource(new FileResource($path));
430        }
431
432        return $exists;
433    }
434
435    /**
436     * Loads the configuration for an extension.
437     *
438     * @param string $extension The extension alias or namespace
439     * @param array  $values    An array of values that customizes the extension
440     *
441     * @return $this
442     *
443     * @throws BadMethodCallException When this ContainerBuilder is compiled
444     * @throws \LogicException        if the extension is not registered
445     */
446    public function loadFromExtension($extension, array $values = null)
447    {
448        if ($this->isCompiled()) {
449            throw new BadMethodCallException('Cannot load from an extension on a compiled container.');
450        }
451
452        if (\func_num_args() < 2) {
453            $values = [];
454        }
455
456        $namespace = $this->getExtension($extension)->getAlias();
457
458        $this->extensionConfigs[$namespace][] = $values;
459
460        return $this;
461    }
462
463    /**
464     * Adds a compiler pass.
465     *
466     * @param CompilerPassInterface $pass     A compiler pass
467     * @param string                $type     The type of compiler pass
468     * @param int                   $priority Used to sort the passes
469     *
470     * @return $this
471     */
472    public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/*, int $priority = 0*/)
473    {
474        if (\func_num_args() >= 3) {
475            $priority = func_get_arg(2);
476        } else {
477            if (__CLASS__ !== static::class) {
478                $r = new \ReflectionMethod($this, __FUNCTION__);
479                if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
480                    @trigger_error(sprintf('Method %s() will have a third `int $priority = 0` argument in version 4.0. Not defining it is deprecated since Symfony 3.2.', __METHOD__), E_USER_DEPRECATED);
481                }
482            }
483
484            $priority = 0;
485        }
486
487        $this->getCompiler()->addPass($pass, $type, $priority);
488
489        $this->addObjectResource($pass);
490
491        return $this;
492    }
493
494    /**
495     * Returns the compiler pass config which can then be modified.
496     *
497     * @return PassConfig The compiler pass config
498     */
499    public function getCompilerPassConfig()
500    {
501        return $this->getCompiler()->getPassConfig();
502    }
503
504    /**
505     * Returns the compiler.
506     *
507     * @return Compiler The compiler
508     */
509    public function getCompiler()
510    {
511        if (null === $this->compiler) {
512            $this->compiler = new Compiler();
513        }
514
515        return $this->compiler;
516    }
517
518    /**
519     * Sets a service.
520     *
521     * @param string      $id      The service identifier
522     * @param object|null $service The service instance
523     *
524     * @throws BadMethodCallException When this ContainerBuilder is compiled
525     */
526    public function set($id, $service)
527    {
528        $id = $this->normalizeId($id);
529
530        if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) {
531            // setting a synthetic service on a compiled container is alright
532            throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a compiled container is not allowed.', $id));
533        }
534
535        unset($this->definitions[$id], $this->aliasDefinitions[$id], $this->removedIds[$id]);
536
537        parent::set($id, $service);
538    }
539
540    /**
541     * Removes a service definition.
542     *
543     * @param string $id The service identifier
544     */
545    public function removeDefinition($id)
546    {
547        if (isset($this->definitions[$id = $this->normalizeId($id)])) {
548            unset($this->definitions[$id]);
549            $this->removedIds[$id] = true;
550        }
551    }
552
553    /**
554     * Returns true if the given service is defined.
555     *
556     * @param string $id The service identifier
557     *
558     * @return bool true if the service is defined, false otherwise
559     */
560    public function has($id)
561    {
562        $id = $this->normalizeId($id);
563
564        return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id);
565    }
566
567    /**
568     * Gets a service.
569     *
570     * @param string $id              The service identifier
571     * @param int    $invalidBehavior The behavior when the service does not exist
572     *
573     * @return object|null The associated service
574     *
575     * @throws InvalidArgumentException          when no definitions are available
576     * @throws ServiceCircularReferenceException When a circular reference is detected
577     * @throws ServiceNotFoundException          When the service is not defined
578     * @throws \Exception
579     *
580     * @see Reference
581     */
582    public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
583    {
584        if ($this->isCompiled() && isset($this->removedIds[$id = $this->normalizeId($id)])) {
585            @trigger_error(sprintf('Fetching the "%s" private service or alias is deprecated since Symfony 3.4 and will fail in 4.0. Make it public instead.', $id), E_USER_DEPRECATED);
586        }
587
588        return $this->doGet($id, $invalidBehavior);
589    }
590
591    private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, $isConstructorArgument = false)
592    {
593        $id = $this->normalizeId($id);
594
595        if (isset($inlineServices[$id])) {
596            return $inlineServices[$id];
597        }
598        if (null === $inlineServices) {
599            $isConstructorArgument = true;
600            $inlineServices = [];
601        }
602        try {
603            if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) {
604                return parent::get($id, $invalidBehavior);
605            }
606            if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
607                return $service;
608            }
609        } catch (ServiceCircularReferenceException $e) {
610            if ($isConstructorArgument) {
611                throw $e;
612            }
613        }
614
615        if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) {
616            return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices, $isConstructorArgument);
617        }
618
619        try {
620            $definition = $this->getDefinition($id);
621        } catch (ServiceNotFoundException $e) {
622            if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
623                return null;
624            }
625
626            throw $e;
627        }
628
629        if ($isConstructorArgument) {
630            $this->loading[$id] = true;
631        }
632
633        try {
634            return $this->createService($definition, $inlineServices, $isConstructorArgument, $id);
635        } finally {
636            if ($isConstructorArgument) {
637                unset($this->loading[$id]);
638            }
639        }
640    }
641
642    /**
643     * Merges a ContainerBuilder with the current ContainerBuilder configuration.
644     *
645     * Service definitions overrides the current defined ones.
646     *
647     * But for parameters, they are overridden by the current ones. It allows
648     * the parameters passed to the container constructor to have precedence
649     * over the loaded ones.
650     *
651     *     $container = new ContainerBuilder(new ParameterBag(['foo' => 'bar']));
652     *     $loader = new LoaderXXX($container);
653     *     $loader->load('resource_name');
654     *     $container->register('foo', 'stdClass');
655     *
656     * In the above example, even if the loaded resource defines a foo
657     * parameter, the value will still be 'bar' as defined in the ContainerBuilder
658     * constructor.
659     *
660     * @throws BadMethodCallException When this ContainerBuilder is compiled
661     */
662    public function merge(self $container)
663    {
664        if ($this->isCompiled()) {
665            throw new BadMethodCallException('Cannot merge on a compiled container.');
666        }
667
668        $this->addDefinitions($container->getDefinitions());
669        $this->addAliases($container->getAliases());
670        $this->getParameterBag()->add($container->getParameterBag()->all());
671
672        if ($this->trackResources) {
673            foreach ($container->getResources() as $resource) {
674                $this->addResource($resource);
675            }
676        }
677
678        foreach ($this->extensions as $name => $extension) {
679            if (!isset($this->extensionConfigs[$name])) {
680                $this->extensionConfigs[$name] = [];
681            }
682
683            $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
684        }
685
686        if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) {
687            $envPlaceholders = $container->getParameterBag()->getEnvPlaceholders();
688            $this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag());
689        } else {
690            $envPlaceholders = [];
691        }
692
693        foreach ($container->envCounters as $env => $count) {
694            if (!$count && !isset($envPlaceholders[$env])) {
695                continue;
696            }
697            if (!isset($this->envCounters[$env])) {
698                $this->envCounters[$env] = $count;
699            } else {
700                $this->envCounters[$env] += $count;
701            }
702        }
703
704        foreach ($container->getAutoconfiguredInstanceof() as $interface => $childDefinition) {
705            if (isset($this->autoconfiguredInstanceof[$interface])) {
706                throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface));
707            }
708
709            $this->autoconfiguredInstanceof[$interface] = $childDefinition;
710        }
711    }
712
713    /**
714     * Returns the configuration array for the given extension.
715     *
716     * @param string $name The name of the extension
717     *
718     * @return array An array of configuration
719     */
720    public function getExtensionConfig($name)
721    {
722        if (!isset($this->extensionConfigs[$name])) {
723            $this->extensionConfigs[$name] = [];
724        }
725
726        return $this->extensionConfigs[$name];
727    }
728
729    /**
730     * Prepends a config array to the configs of the given extension.
731     *
732     * @param string $name   The name of the extension
733     * @param array  $config The config to set
734     */
735    public function prependExtensionConfig($name, array $config)
736    {
737        if (!isset($this->extensionConfigs[$name])) {
738            $this->extensionConfigs[$name] = [];
739        }
740
741        array_unshift($this->extensionConfigs[$name], $config);
742    }
743
744    /**
745     * Compiles the container.
746     *
747     * This method passes the container to compiler
748     * passes whose job is to manipulate and optimize
749     * the container.
750     *
751     * The main compiler passes roughly do four things:
752     *
753     *  * The extension configurations are merged;
754     *  * Parameter values are resolved;
755     *  * The parameter bag is frozen;
756     *  * Extension loading is disabled.
757     *
758     * @param bool $resolveEnvPlaceholders Whether %env()% parameters should be resolved using the current
759     *                                     env vars or be replaced by uniquely identifiable placeholders.
760     *                                     Set to "true" when you want to use the current ContainerBuilder
761     *                                     directly, keep to "false" when the container is dumped instead.
762     */
763    public function compile(/*$resolveEnvPlaceholders = false*/)
764    {
765        if (1 <= \func_num_args()) {
766            $resolveEnvPlaceholders = func_get_arg(0);
767        } else {
768            if (__CLASS__ !== static::class) {
769                $r = new \ReflectionMethod($this, __FUNCTION__);
770                if (__CLASS__ !== $r->getDeclaringClass()->getName() && (1 > $r->getNumberOfParameters() || 'resolveEnvPlaceholders' !== $r->getParameters()[0]->name)) {
771                    @trigger_error(sprintf('The %s::compile() method expects a first "$resolveEnvPlaceholders" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), E_USER_DEPRECATED);
772                }
773            }
774            $resolveEnvPlaceholders = false;
775        }
776        $compiler = $this->getCompiler();
777
778        if ($this->trackResources) {
779            foreach ($compiler->getPassConfig()->getPasses() as $pass) {
780                $this->addObjectResource($pass);
781            }
782        }
783        $bag = $this->getParameterBag();
784
785        if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) {
786            $compiler->addPass(new ResolveEnvPlaceholdersPass(), PassConfig::TYPE_AFTER_REMOVING, -1000);
787        }
788
789        $compiler->compile($this);
790
791        foreach ($this->definitions as $id => $definition) {
792            if ($this->trackResources && $definition->isLazy()) {
793                $this->getReflectionClass($definition->getClass());
794            }
795        }
796
797        $this->extensionConfigs = [];
798
799        if ($bag instanceof EnvPlaceholderParameterBag) {
800            if ($resolveEnvPlaceholders) {
801                $this->parameterBag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), true));
802            }
803
804            $this->envPlaceholders = $bag->getEnvPlaceholders();
805        }
806
807        parent::compile();
808
809        foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) {
810            if (!$definition->isPublic() || $definition->isPrivate()) {
811                $this->removedIds[$id] = true;
812            }
813        }
814    }
815
816    /**
817     * {@inheritdoc}
818     */
819    public function getServiceIds()
820    {
821        return array_map('strval', array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds())));
822    }
823
824    /**
825     * Gets removed service or alias ids.
826     *
827     * @return array
828     */
829    public function getRemovedIds()
830    {
831        return $this->removedIds;
832    }
833
834    /**
835     * Adds the service aliases.
836     */
837    public function addAliases(array $aliases)
838    {
839        foreach ($aliases as $alias => $id) {
840            $this->setAlias($alias, $id);
841        }
842    }
843
844    /**
845     * Sets the service aliases.
846     */
847    public function setAliases(array $aliases)
848    {
849        $this->aliasDefinitions = [];
850        $this->addAliases($aliases);
851    }
852
853    /**
854     * Sets an alias for an existing service.
855     *
856     * @param string       $alias The alias to create
857     * @param string|Alias $id    The service to alias
858     *
859     * @return Alias
860     *
861     * @throws InvalidArgumentException if the id is not a string or an Alias
862     * @throws InvalidArgumentException if the alias is for itself
863     */
864    public function setAlias($alias, $id)
865    {
866        $alias = $this->normalizeId($alias);
867
868        if ('' === $alias || '\\' === substr($alias, -1) || \strlen($alias) !== strcspn($alias, "\0\r\n'")) {
869            throw new InvalidArgumentException(sprintf('Invalid alias id: "%s".', $alias));
870        }
871
872        if (\is_string($id)) {
873            $id = new Alias($this->normalizeId($id));
874        } elseif (!$id instanceof Alias) {
875            throw new InvalidArgumentException('$id must be a string, or an Alias object.');
876        }
877
878        if ($alias === (string) $id) {
879            throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias));
880        }
881
882        unset($this->definitions[$alias], $this->removedIds[$alias]);
883
884        return $this->aliasDefinitions[$alias] = $id;
885    }
886
887    /**
888     * Removes an alias.
889     *
890     * @param string $alias The alias to remove
891     */
892    public function removeAlias($alias)
893    {
894        if (isset($this->aliasDefinitions[$alias = $this->normalizeId($alias)])) {
895            unset($this->aliasDefinitions[$alias]);
896            $this->removedIds[$alias] = true;
897        }
898    }
899
900    /**
901     * Returns true if an alias exists under the given identifier.
902     *
903     * @param string $id The service identifier
904     *
905     * @return bool true if the alias exists, false otherwise
906     */
907    public function hasAlias($id)
908    {
909        return isset($this->aliasDefinitions[$this->normalizeId($id)]);
910    }
911
912    /**
913     * Gets all defined aliases.
914     *
915     * @return Alias[] An array of aliases
916     */
917    public function getAliases()
918    {
919        return $this->aliasDefinitions;
920    }
921
922    /**
923     * Gets an alias.
924     *
925     * @param string $id The service identifier
926     *
927     * @return Alias An Alias instance
928     *
929     * @throws InvalidArgumentException if the alias does not exist
930     */
931    public function getAlias($id)
932    {
933        $id = $this->normalizeId($id);
934
935        if (!isset($this->aliasDefinitions[$id])) {
936            throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id));
937        }
938
939        return $this->aliasDefinitions[$id];
940    }
941
942    /**
943     * Registers a service definition.
944     *
945     * This methods allows for simple registration of service definition
946     * with a fluid interface.
947     *
948     * @param string      $id    The service identifier
949     * @param string|null $class The service class
950     *
951     * @return Definition A Definition instance
952     */
953    public function register($id, $class = null)
954    {
955        return $this->setDefinition($id, new Definition($class));
956    }
957
958    /**
959     * Registers an autowired service definition.
960     *
961     * This method implements a shortcut for using setDefinition() with
962     * an autowired definition.
963     *
964     * @param string      $id    The service identifier
965     * @param string|null $class The service class
966     *
967     * @return Definition The created definition
968     */
969    public function autowire($id, $class = null)
970    {
971        return $this->setDefinition($id, (new Definition($class))->setAutowired(true));
972    }
973
974    /**
975     * Adds the service definitions.
976     *
977     * @param Definition[] $definitions An array of service definitions
978     */
979    public function addDefinitions(array $definitions)
980    {
981        foreach ($definitions as $id => $definition) {
982            $this->setDefinition($id, $definition);
983        }
984    }
985
986    /**
987     * Sets the service definitions.
988     *
989     * @param Definition[] $definitions An array of service definitions
990     */
991    public function setDefinitions(array $definitions)
992    {
993        $this->definitions = [];
994        $this->addDefinitions($definitions);
995    }
996
997    /**
998     * Gets all service definitions.
999     *
1000     * @return Definition[] An array of Definition instances
1001     */
1002    public function getDefinitions()
1003    {
1004        return $this->definitions;
1005    }
1006
1007    /**
1008     * Sets a service definition.
1009     *
1010     * @param string     $id         The service identifier
1011     * @param Definition $definition A Definition instance
1012     *
1013     * @return Definition the service definition
1014     *
1015     * @throws BadMethodCallException When this ContainerBuilder is compiled
1016     */
1017    public function setDefinition($id, Definition $definition)
1018    {
1019        if ($this->isCompiled()) {
1020            throw new BadMethodCallException('Adding definition to a compiled container is not allowed.');
1021        }
1022
1023        $id = $this->normalizeId($id);
1024
1025        if ('' === $id || '\\' === substr($id, -1) || \strlen($id) !== strcspn($id, "\0\r\n'")) {
1026            throw new InvalidArgumentException(sprintf('Invalid service id: "%s".', $id));
1027        }
1028
1029        unset($this->aliasDefinitions[$id], $this->removedIds[$id]);
1030
1031        return $this->definitions[$id] = $definition;
1032    }
1033
1034    /**
1035     * Returns true if a service definition exists under the given identifier.
1036     *
1037     * @param string $id The service identifier
1038     *
1039     * @return bool true if the service definition exists, false otherwise
1040     */
1041    public function hasDefinition($id)
1042    {
1043        return isset($this->definitions[$this->normalizeId($id)]);
1044    }
1045
1046    /**
1047     * Gets a service definition.
1048     *
1049     * @param string $id The service identifier
1050     *
1051     * @return Definition A Definition instance
1052     *
1053     * @throws ServiceNotFoundException if the service definition does not exist
1054     */
1055    public function getDefinition($id)
1056    {
1057        $id = $this->normalizeId($id);
1058
1059        if (!isset($this->definitions[$id])) {
1060            throw new ServiceNotFoundException($id);
1061        }
1062
1063        return $this->definitions[$id];
1064    }
1065
1066    /**
1067     * Gets a service definition by id or alias.
1068     *
1069     * The method "unaliases" recursively to return a Definition instance.
1070     *
1071     * @param string $id The service identifier or alias
1072     *
1073     * @return Definition A Definition instance
1074     *
1075     * @throws ServiceNotFoundException if the service definition does not exist
1076     */
1077    public function findDefinition($id)
1078    {
1079        $id = $this->normalizeId($id);
1080
1081        $seen = [];
1082        while (isset($this->aliasDefinitions[$id])) {
1083            $id = (string) $this->aliasDefinitions[$id];
1084
1085            if (isset($seen[$id])) {
1086                $seen = array_values($seen);
1087                $seen = \array_slice($seen, array_search($id, $seen));
1088                $seen[] = $id;
1089
1090                throw new ServiceCircularReferenceException($id, $seen);
1091            }
1092
1093            $seen[$id] = $id;
1094        }
1095
1096        return $this->getDefinition($id);
1097    }
1098
1099    /**
1100     * Creates a service for a service definition.
1101     *
1102     * @param Definition $definition A service definition instance
1103     * @param string     $id         The service identifier
1104     * @param bool       $tryProxy   Whether to try proxying the service with a lazy proxy
1105     *
1106     * @return mixed The service described by the service definition
1107     *
1108     * @throws RuntimeException         When the factory definition is incomplete
1109     * @throws RuntimeException         When the service is a synthetic service
1110     * @throws InvalidArgumentException When configure callable is not callable
1111     */
1112    private function createService(Definition $definition, array &$inlineServices, $isConstructorArgument = false, $id = null, $tryProxy = true)
1113    {
1114        if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) {
1115            return $inlineServices[$h];
1116        }
1117
1118        if ($definition instanceof ChildDefinition) {
1119            throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id));
1120        }
1121
1122        if ($definition->isSynthetic()) {
1123            throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id));
1124        }
1125
1126        if ($definition->isDeprecated()) {
1127            @trigger_error($definition->getDeprecationMessage($id), E_USER_DEPRECATED);
1128        }
1129
1130        if ($tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
1131            $proxy = $proxy->instantiateProxy(
1132                $this,
1133                $definition,
1134                $id, function () use ($definition, &$inlineServices, $id) {
1135                    return $this->createService($definition, $inlineServices, true, $id, false);
1136                }
1137            );
1138            $this->shareService($definition, $proxy, $id, $inlineServices);
1139
1140            return $proxy;
1141        }
1142
1143        $parameterBag = $this->getParameterBag();
1144
1145        if (null !== $definition->getFile()) {
1146            require_once $parameterBag->resolveValue($definition->getFile());
1147        }
1148
1149        $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices, $isConstructorArgument);
1150
1151        if (null !== $factory = $definition->getFactory()) {
1152            if (\is_array($factory)) {
1153                $factory = [$this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices, $isConstructorArgument), $factory[1]];
1154            } elseif (!\is_string($factory)) {
1155                throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory.', $id));
1156            }
1157        }
1158
1159        if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) {
1160            return $this->services[$id];
1161        }
1162
1163        if (null !== $factory) {
1164            $service = \call_user_func_array($factory, $arguments);
1165
1166            if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) {
1167                $r = new \ReflectionClass($factory[0]);
1168
1169                if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
1170                    @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name), E_USER_DEPRECATED);
1171                }
1172            }
1173        } else {
1174            $r = new \ReflectionClass($class = $parameterBag->resolveValue($definition->getClass()));
1175
1176            $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
1177            // don't trigger deprecations for internal uses
1178            // @deprecated since version 3.3, to be removed in 4.0 along with the deprecated class
1179            $deprecationWhitelist = ['event_dispatcher' => ContainerAwareEventDispatcher::class];
1180
1181            if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ") && (!isset($deprecationWhitelist[$id]) || $deprecationWhitelist[$id] !== $class)) {
1182                @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name), E_USER_DEPRECATED);
1183            }
1184        }
1185
1186        if ($tryProxy || !$definition->isLazy()) {
1187            // share only if proxying failed, or if not a proxy
1188            $this->shareService($definition, $service, $id, $inlineServices);
1189        }
1190
1191        $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlineServices);
1192        foreach ($properties as $name => $value) {
1193            $service->$name = $value;
1194        }
1195
1196        foreach ($definition->getMethodCalls() as $call) {
1197            $this->callMethod($service, $call, $inlineServices);
1198        }
1199
1200        if ($callable = $definition->getConfigurator()) {
1201            if (\is_array($callable)) {
1202                $callable[0] = $parameterBag->resolveValue($callable[0]);
1203
1204                if ($callable[0] instanceof Reference) {
1205                    $callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior(), $inlineServices);
1206                } elseif ($callable[0] instanceof Definition) {
1207                    $callable[0] = $this->createService($callable[0], $inlineServices);
1208                }
1209            }
1210
1211            if (!\is_callable($callable)) {
1212                throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', \get_class($service)));
1213            }
1214
1215            \call_user_func($callable, $service);
1216        }
1217
1218        return $service;
1219    }
1220
1221    /**
1222     * Replaces service references by the real service instance and evaluates expressions.
1223     *
1224     * @param mixed $value A value
1225     *
1226     * @return mixed The same value with all service references replaced by
1227     *               the real service instances and all expressions evaluated
1228     */
1229    public function resolveServices($value)
1230    {
1231        return $this->doResolveServices($value);
1232    }
1233
1234    private function doResolveServices($value, array &$inlineServices = [], $isConstructorArgument = false)
1235    {
1236        if (\is_array($value)) {
1237            foreach ($value as $k => $v) {
1238                $value[$k] = $this->doResolveServices($v, $inlineServices, $isConstructorArgument);
1239            }
1240        } elseif ($value instanceof ServiceClosureArgument) {
1241            $reference = $value->getValues()[0];
1242            $value = function () use ($reference) {
1243                return $this->resolveServices($reference);
1244            };
1245        } elseif ($value instanceof IteratorArgument) {
1246            $value = new RewindableGenerator(function () use ($value) {
1247                foreach ($value->getValues() as $k => $v) {
1248                    foreach (self::getServiceConditionals($v) as $s) {
1249                        if (!$this->has($s)) {
1250                            continue 2;
1251                        }
1252                    }
1253                    foreach (self::getInitializedConditionals($v) as $s) {
1254                        if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
1255                            continue 2;
1256                        }
1257                    }
1258
1259                    yield $k => $this->resolveServices($v);
1260                }
1261            }, function () use ($value) {
1262                $count = 0;
1263                foreach ($value->getValues() as $v) {
1264                    foreach (self::getServiceConditionals($v) as $s) {
1265                        if (!$this->has($s)) {
1266                            continue 2;
1267                        }
1268                    }
1269                    foreach (self::getInitializedConditionals($v) as $s) {
1270                        if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
1271                            continue 2;
1272                        }
1273                    }
1274
1275                    ++$count;
1276                }
1277
1278                return $count;
1279            });
1280        } elseif ($value instanceof Reference) {
1281            $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument);
1282        } elseif ($value instanceof Definition) {
1283            $value = $this->createService($value, $inlineServices, $isConstructorArgument);
1284        } elseif ($value instanceof Parameter) {
1285            $value = $this->getParameter((string) $value);
1286        } elseif ($value instanceof Expression) {
1287            $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this]);
1288        }
1289
1290        return $value;
1291    }
1292
1293    /**
1294     * Returns service ids for a given tag.
1295     *
1296     * Example:
1297     *
1298     *     $container->register('foo')->addTag('my.tag', ['hello' => 'world']);
1299     *
1300     *     $serviceIds = $container->findTaggedServiceIds('my.tag');
1301     *     foreach ($serviceIds as $serviceId => $tags) {
1302     *         foreach ($tags as $tag) {
1303     *             echo $tag['hello'];
1304     *         }
1305     *     }
1306     *
1307     * @param string $name
1308     * @param bool   $throwOnAbstract
1309     *
1310     * @return array An array of tags with the tagged service as key, holding a list of attribute arrays
1311     */
1312    public function findTaggedServiceIds($name, $throwOnAbstract = false)
1313    {
1314        $this->usedTags[] = $name;
1315        $tags = [];
1316        foreach ($this->getDefinitions() as $id => $definition) {
1317            if ($definition->hasTag($name)) {
1318                if ($throwOnAbstract && $definition->isAbstract()) {
1319                    throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must not be abstract.', $id, $name));
1320                }
1321                $tags[$id] = $definition->getTag($name);
1322            }
1323        }
1324
1325        return $tags;
1326    }
1327
1328    /**
1329     * Returns all tags the defined services use.
1330     *
1331     * @return array An array of tags
1332     */
1333    public function findTags()
1334    {
1335        $tags = [];
1336        foreach ($this->getDefinitions() as $id => $definition) {
1337            $tags = array_merge(array_keys($definition->getTags()), $tags);
1338        }
1339
1340        return array_unique($tags);
1341    }
1342
1343    /**
1344     * Returns all tags not queried by findTaggedServiceIds.
1345     *
1346     * @return string[] An array of tags
1347     */
1348    public function findUnusedTags()
1349    {
1350        return array_values(array_diff($this->findTags(), $this->usedTags));
1351    }
1352
1353    public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
1354    {
1355        $this->expressionLanguageProviders[] = $provider;
1356    }
1357
1358    /**
1359     * @return ExpressionFunctionProviderInterface[]
1360     */
1361    public function getExpressionLanguageProviders()
1362    {
1363        return $this->expressionLanguageProviders;
1364    }
1365
1366    /**
1367     * Returns a ChildDefinition that will be used for autoconfiguring the interface/class.
1368     *
1369     * @param string $interface The class or interface to match
1370     *
1371     * @return ChildDefinition
1372     */
1373    public function registerForAutoconfiguration($interface)
1374    {
1375        if (!isset($this->autoconfiguredInstanceof[$interface])) {
1376            $this->autoconfiguredInstanceof[$interface] = new ChildDefinition('');
1377        }
1378
1379        return $this->autoconfiguredInstanceof[$interface];
1380    }
1381
1382    /**
1383     * Returns an array of ChildDefinition[] keyed by interface.
1384     *
1385     * @return ChildDefinition[]
1386     */
1387    public function getAutoconfiguredInstanceof()
1388    {
1389        return $this->autoconfiguredInstanceof;
1390    }
1391
1392    /**
1393     * Resolves env parameter placeholders in a string or an array.
1394     *
1395     * @param mixed            $value     The value to resolve
1396     * @param string|true|null $format    A sprintf() format returning the replacement for each env var name or
1397     *                                    null to resolve back to the original "%env(VAR)%" format or
1398     *                                    true to resolve to the actual values of the referenced env vars
1399     * @param array            &$usedEnvs Env vars found while resolving are added to this array
1400     *
1401     * @return mixed The value with env parameters resolved if a string or an array is passed
1402     */
1403    public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null)
1404    {
1405        if (null === $format) {
1406            $format = '%%env(%s)%%';
1407        }
1408
1409        $bag = $this->getParameterBag();
1410        if (true === $format) {
1411            $value = $bag->resolveValue($value);
1412        }
1413
1414        if (\is_array($value)) {
1415            $result = [];
1416            foreach ($value as $k => $v) {
1417                $result[\is_string($k) ? $this->resolveEnvPlaceholders($k, $format, $usedEnvs) : $k] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs);
1418            }
1419
1420            return $result;
1421        }
1422
1423        if (!\is_string($value) || 38 > \strlen($value)) {
1424            return $value;
1425        }
1426        $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
1427
1428        $completed = false;
1429        foreach ($envPlaceholders as $env => $placeholders) {
1430            foreach ($placeholders as $placeholder) {
1431                if (false !== stripos($value, $placeholder)) {
1432                    if (true === $format) {
1433                        $resolved = $bag->escapeValue($this->getEnv($env));
1434                    } else {
1435                        $resolved = sprintf($format, $env);
1436                    }
1437                    if ($placeholder === $value) {
1438                        $value = $resolved;
1439                        $completed = true;
1440                    } else {
1441                        if (!\is_string($resolved) && !is_numeric($resolved)) {
1442                            throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type "%s" inside string value "%s".', $env, \gettype($resolved), $this->resolveEnvPlaceholders($value)));
1443                        }
1444                        $value = str_ireplace($placeholder, $resolved, $value);
1445                    }
1446                    $usedEnvs[$env] = $env;
1447                    $this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1;
1448
1449                    if ($completed) {
1450                        break 2;
1451                    }
1452                }
1453            }
1454        }
1455
1456        return $value;
1457    }
1458
1459    /**
1460     * Get statistics about env usage.
1461     *
1462     * @return int[] The number of time each env vars has been resolved
1463     */
1464    public function getEnvCounters()
1465    {
1466        $bag = $this->getParameterBag();
1467        $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
1468
1469        foreach ($envPlaceholders as $env => $placeholders) {
1470            if (!isset($this->envCounters[$env])) {
1471                $this->envCounters[$env] = 0;
1472            }
1473        }
1474
1475        return $this->envCounters;
1476    }
1477
1478    /**
1479     * @internal
1480     */
1481    public function getNormalizedIds()
1482    {
1483        $normalizedIds = [];
1484
1485        foreach ($this->normalizedIds as $k => $v) {
1486            if ($v !== (string) $k) {
1487                $normalizedIds[$k] = $v;
1488            }
1489        }
1490
1491        return $normalizedIds;
1492    }
1493
1494    /**
1495     * @final
1496     */
1497    public function log(CompilerPassInterface $pass, $message)
1498    {
1499        $this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message));
1500    }
1501
1502    /**
1503     * {@inheritdoc}
1504     */
1505    public function normalizeId($id)
1506    {
1507        if (!\is_string($id)) {
1508            $id = (string) $id;
1509        }
1510
1511        return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || isset($this->removedIds[$id]) ? $id : parent::normalizeId($id);
1512    }
1513
1514    /**
1515     * Gets removed binding ids.
1516     *
1517     * @return array
1518     *
1519     * @internal
1520     */
1521    public function getRemovedBindingIds()
1522    {
1523        return $this->removedBindingIds;
1524    }
1525
1526    /**
1527     * Removes bindings for a service.
1528     *
1529     * @param string $id The service identifier
1530     *
1531     * @internal
1532     */
1533    public function removeBindings($id)
1534    {
1535        if ($this->hasDefinition($id)) {
1536            foreach ($this->getDefinition($id)->getBindings() as $key => $binding) {
1537                list(, $bindingId) = $binding->getValues();
1538                $this->removedBindingIds[(int) $bindingId] = true;
1539            }
1540        }
1541    }
1542
1543    /**
1544     * Returns the Service Conditionals.
1545     *
1546     * @param mixed $value An array of conditionals to return
1547     *
1548     * @return array An array of Service conditionals
1549     *
1550     * @internal since version 3.4
1551     */
1552    public static function getServiceConditionals($value)
1553    {
1554        $services = [];
1555
1556        if (\is_array($value)) {
1557            foreach ($value as $v) {
1558                $services = array_unique(array_merge($services, self::getServiceConditionals($v)));
1559            }
1560        } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
1561            $services[] = (string) $value;
1562        }
1563
1564        return $services;
1565    }
1566
1567    /**
1568     * Returns the initialized conditionals.
1569     *
1570     * @param mixed $value An array of conditionals to return
1571     *
1572     * @return array An array of uninitialized conditionals
1573     *
1574     * @internal
1575     */
1576    public static function getInitializedConditionals($value)
1577    {
1578        $services = [];
1579
1580        if (\is_array($value)) {
1581            foreach ($value as $v) {
1582                $services = array_unique(array_merge($services, self::getInitializedConditionals($v)));
1583            }
1584        } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()) {
1585            $services[] = (string) $value;
1586        }
1587
1588        return $services;
1589    }
1590
1591    /**
1592     * Computes a reasonably unique hash of a value.
1593     *
1594     * @param mixed $value A serializable value
1595     *
1596     * @return string
1597     */
1598    public static function hash($value)
1599    {
1600        $hash = substr(base64_encode(hash('sha256', serialize($value), true)), 0, 7);
1601
1602        return str_replace(['/', '+'], ['.', '_'], strtolower($hash));
1603    }
1604
1605    /**
1606     * {@inheritdoc}
1607     */
1608    protected function getEnv($name)
1609    {
1610        $value = parent::getEnv($name);
1611        $bag = $this->getParameterBag();
1612
1613        if (!\is_string($value) || !$bag instanceof EnvPlaceholderParameterBag) {
1614            return $value;
1615        }
1616
1617        foreach ($bag->getEnvPlaceholders() as $env => $placeholders) {
1618            if (isset($placeholders[$value])) {
1619                $bag = new ParameterBag($bag->all());
1620
1621                return $bag->unescapeValue($bag->get("env($name)"));
1622            }
1623        }
1624
1625        $this->resolving["env($name)"] = true;
1626        try {
1627            return $bag->unescapeValue($this->resolveEnvPlaceholders($bag->escapeValue($value), true));
1628        } finally {
1629            unset($this->resolving["env($name)"]);
1630        }
1631    }
1632
1633    private function callMethod($service, $call, array &$inlineServices)
1634    {
1635        foreach (self::getServiceConditionals($call[1]) as $s) {
1636            if (!$this->has($s)) {
1637                return;
1638            }
1639        }
1640        foreach (self::getInitializedConditionals($call[1]) as $s) {
1641            if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
1642                return;
1643            }
1644        }
1645
1646        \call_user_func_array([$service, $call[0]], $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices));
1647    }
1648
1649    /**
1650     * Shares a given service in the container.
1651     *
1652     * @param mixed       $service
1653     * @param string|null $id
1654     */
1655    private function shareService(Definition $definition, $service, $id, array &$inlineServices)
1656    {
1657        $inlineServices[null !== $id ? $id : spl_object_hash($definition)] = $service;
1658
1659        if (null !== $id && $definition->isShared()) {
1660            $this->services[$id] = $service;
1661            unset($this->loading[$id]);
1662        }
1663    }
1664
1665    private function getExpressionLanguage()
1666    {
1667        if (null === $this->expressionLanguage) {
1668            if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
1669                throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
1670            }
1671            $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
1672        }
1673
1674        return $this->expressionLanguage;
1675    }
1676
1677    private function inVendors($path)
1678    {
1679        if (null === $this->vendors) {
1680            $resource = new ComposerResource();
1681            $this->vendors = $resource->getVendors();
1682            $this->addResource($resource);
1683        }
1684        $path = realpath($path) ?: $path;
1685
1686        foreach ($this->vendors as $vendor) {
1687            if (0 === strpos($path, $vendor) && false !== strpbrk(substr($path, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
1688                return true;
1689            }
1690        }
1691
1692        return false;
1693    }
1694}
1695