Security update for Core, with self-updated composer
[yaffs-website] / vendor / symfony / dependency-injection / ContainerBuilder.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;
13
14 use Symfony\Component\DependencyInjection\Compiler\Compiler;
15 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
16 use Symfony\Component\DependencyInjection\Compiler\PassConfig;
17 use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
18 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
19 use Symfony\Component\DependencyInjection\Exception\LogicException;
20 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
21 use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
22 use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
23 use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
24 use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
25 use Symfony\Component\Config\Resource\FileResource;
26 use Symfony\Component\Config\Resource\ResourceInterface;
27 use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
28 use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
29 use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
30 use Symfony\Component\ExpressionLanguage\Expression;
31 use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
32
33 /**
34  * ContainerBuilder is a DI container that provides an API to easily describe services.
35  *
36  * @author Fabien Potencier <fabien@symfony.com>
37  */
38 class ContainerBuilder extends Container implements TaggedContainerInterface
39 {
40     /**
41      * @var ExtensionInterface[]
42      */
43     private $extensions = array();
44
45     /**
46      * @var ExtensionInterface[]
47      */
48     private $extensionsByNs = array();
49
50     /**
51      * @var Definition[]
52      */
53     private $definitions = array();
54
55     /**
56      * @var Alias[]
57      */
58     private $aliasDefinitions = array();
59
60     /**
61      * @var ResourceInterface[]
62      */
63     private $resources = array();
64
65     private $extensionConfigs = array();
66
67     /**
68      * @var Compiler
69      */
70     private $compiler;
71
72     private $trackResources;
73
74     /**
75      * @var InstantiatorInterface|null
76      */
77     private $proxyInstantiator;
78
79     /**
80      * @var ExpressionLanguage|null
81      */
82     private $expressionLanguage;
83
84     /**
85      * @var ExpressionFunctionProviderInterface[]
86      */
87     private $expressionLanguageProviders = array();
88
89     public function __construct(ParameterBagInterface $parameterBag = null)
90     {
91         parent::__construct($parameterBag);
92
93         $this->trackResources = interface_exists('Symfony\Component\Config\Resource\ResourceInterface');
94     }
95
96     /**
97      * @var string[] with tag names used by findTaggedServiceIds
98      */
99     private $usedTags = array();
100
101     /**
102      * @var string[][] a map of env var names to their placeholders
103      */
104     private $envPlaceholders = array();
105
106     /**
107      * @var int[] a map of env vars to their resolution counter
108      */
109     private $envCounters = array();
110
111     /**
112      * Sets the track resources flag.
113      *
114      * If you are not using the loaders and therefore don't want
115      * to depend on the Config component, set this flag to false.
116      *
117      * @param bool $track true if you want to track resources, false otherwise
118      */
119     public function setResourceTracking($track)
120     {
121         $this->trackResources = (bool) $track;
122     }
123
124     /**
125      * Checks if resources are tracked.
126      *
127      * @return bool true if resources are tracked, false otherwise
128      */
129     public function isTrackingResources()
130     {
131         return $this->trackResources;
132     }
133
134     /**
135      * Sets the instantiator to be used when fetching proxies.
136      *
137      * @param InstantiatorInterface $proxyInstantiator
138      */
139     public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator)
140     {
141         $this->proxyInstantiator = $proxyInstantiator;
142     }
143
144     /**
145      * Registers an extension.
146      *
147      * @param ExtensionInterface $extension An extension instance
148      */
149     public function registerExtension(ExtensionInterface $extension)
150     {
151         $this->extensions[$extension->getAlias()] = $extension;
152
153         if (false !== $extension->getNamespace()) {
154             $this->extensionsByNs[$extension->getNamespace()] = $extension;
155         }
156     }
157
158     /**
159      * Returns an extension by alias or namespace.
160      *
161      * @param string $name An alias or a namespace
162      *
163      * @return ExtensionInterface An extension instance
164      *
165      * @throws LogicException if the extension is not registered
166      */
167     public function getExtension($name)
168     {
169         if (isset($this->extensions[$name])) {
170             return $this->extensions[$name];
171         }
172
173         if (isset($this->extensionsByNs[$name])) {
174             return $this->extensionsByNs[$name];
175         }
176
177         throw new LogicException(sprintf('Container extension "%s" is not registered', $name));
178     }
179
180     /**
181      * Returns all registered extensions.
182      *
183      * @return ExtensionInterface[] An array of ExtensionInterface
184      */
185     public function getExtensions()
186     {
187         return $this->extensions;
188     }
189
190     /**
191      * Checks if we have an extension.
192      *
193      * @param string $name The name of the extension
194      *
195      * @return bool If the extension exists
196      */
197     public function hasExtension($name)
198     {
199         return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]);
200     }
201
202     /**
203      * Returns an array of resources loaded to build this configuration.
204      *
205      * @return ResourceInterface[] An array of resources
206      */
207     public function getResources()
208     {
209         return array_unique($this->resources);
210     }
211
212     /**
213      * Adds a resource for this configuration.
214      *
215      * @param ResourceInterface $resource A resource instance
216      *
217      * @return $this
218      */
219     public function addResource(ResourceInterface $resource)
220     {
221         if (!$this->trackResources) {
222             return $this;
223         }
224
225         $this->resources[] = $resource;
226
227         return $this;
228     }
229
230     /**
231      * Sets the resources for this configuration.
232      *
233      * @param ResourceInterface[] $resources An array of resources
234      *
235      * @return $this
236      */
237     public function setResources(array $resources)
238     {
239         if (!$this->trackResources) {
240             return $this;
241         }
242
243         $this->resources = $resources;
244
245         return $this;
246     }
247
248     /**
249      * Adds the object class hierarchy as resources.
250      *
251      * @param object $object An object instance
252      *
253      * @return $this
254      */
255     public function addObjectResource($object)
256     {
257         if ($this->trackResources) {
258             $this->addClassResource(new \ReflectionClass($object));
259         }
260
261         return $this;
262     }
263
264     /**
265      * Adds the given class hierarchy as resources.
266      *
267      * @param \ReflectionClass $class
268      *
269      * @return $this
270      */
271     public function addClassResource(\ReflectionClass $class)
272     {
273         if (!$this->trackResources) {
274             return $this;
275         }
276
277         do {
278             if (is_file($class->getFileName())) {
279                 $this->addResource(new FileResource($class->getFileName()));
280             }
281         } while ($class = $class->getParentClass());
282
283         return $this;
284     }
285
286     /**
287      * Loads the configuration for an extension.
288      *
289      * @param string $extension The extension alias or namespace
290      * @param array  $values    An array of values that customizes the extension
291      *
292      * @return $this
293      *
294      * @throws BadMethodCallException When this ContainerBuilder is frozen
295      * @throws \LogicException        if the container is frozen
296      */
297     public function loadFromExtension($extension, array $values = array())
298     {
299         if ($this->isFrozen()) {
300             throw new BadMethodCallException('Cannot load from an extension on a frozen container.');
301         }
302
303         $namespace = $this->getExtension($extension)->getAlias();
304
305         $this->extensionConfigs[$namespace][] = $values;
306
307         return $this;
308     }
309
310     /**
311      * Adds a compiler pass.
312      *
313      * @param CompilerPassInterface $pass     A compiler pass
314      * @param string                $type     The type of compiler pass
315      * @param int                   $priority Used to sort the passes
316      *
317      * @return $this
318      */
319     public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/*, $priority = 0*/)
320     {
321         if (func_num_args() >= 3) {
322             $priority = func_get_arg(2);
323         } else {
324             if (__CLASS__ !== get_class($this)) {
325                 $r = new \ReflectionMethod($this, __FUNCTION__);
326                 if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
327                     @trigger_error(sprintf('Method %s() will have a third `$priority = 0` argument in version 4.0. Not defining it is deprecated since 3.2.', __METHOD__), E_USER_DEPRECATED);
328                 }
329             }
330
331             $priority = 0;
332         }
333
334         $this->getCompiler()->addPass($pass, $type, $priority);
335
336         $this->addObjectResource($pass);
337
338         return $this;
339     }
340
341     /**
342      * Returns the compiler pass config which can then be modified.
343      *
344      * @return PassConfig The compiler pass config
345      */
346     public function getCompilerPassConfig()
347     {
348         return $this->getCompiler()->getPassConfig();
349     }
350
351     /**
352      * Returns the compiler.
353      *
354      * @return Compiler The compiler
355      */
356     public function getCompiler()
357     {
358         if (null === $this->compiler) {
359             $this->compiler = new Compiler();
360         }
361
362         return $this->compiler;
363     }
364
365     /**
366      * Sets a service.
367      *
368      * @param string $id      The service identifier
369      * @param object $service The service instance
370      *
371      * @throws BadMethodCallException When this ContainerBuilder is frozen
372      */
373     public function set($id, $service)
374     {
375         $id = strtolower($id);
376
377         if ($this->isFrozen() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) {
378             // setting a synthetic service on a frozen container is alright
379             throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a frozen container is not allowed.', $id));
380         }
381
382         unset($this->definitions[$id], $this->aliasDefinitions[$id]);
383
384         parent::set($id, $service);
385     }
386
387     /**
388      * Removes a service definition.
389      *
390      * @param string $id The service identifier
391      */
392     public function removeDefinition($id)
393     {
394         unset($this->definitions[strtolower($id)]);
395     }
396
397     /**
398      * Returns true if the given service is defined.
399      *
400      * @param string $id The service identifier
401      *
402      * @return bool true if the service is defined, false otherwise
403      */
404     public function has($id)
405     {
406         $id = strtolower($id);
407
408         return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id);
409     }
410
411     /**
412      * Gets a service.
413      *
414      * @param string $id              The service identifier
415      * @param int    $invalidBehavior The behavior when the service does not exist
416      *
417      * @return object The associated service
418      *
419      * @throws InvalidArgumentException          when no definitions are available
420      * @throws ServiceCircularReferenceException When a circular reference is detected
421      * @throws ServiceNotFoundException          When the service is not defined
422      * @throws \Exception
423      *
424      * @see Reference
425      */
426     public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
427     {
428         $id = strtolower($id);
429
430         if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
431             return $service;
432         }
433
434         if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) {
435             return $this->get((string) $this->aliasDefinitions[$id], $invalidBehavior);
436         }
437
438         try {
439             $definition = $this->getDefinition($id);
440         } catch (ServiceNotFoundException $e) {
441             if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
442                 return;
443             }
444
445             throw $e;
446         }
447
448         $this->loading[$id] = true;
449
450         try {
451             $service = $this->createService($definition, $id);
452         } finally {
453             unset($this->loading[$id]);
454         }
455
456         return $service;
457     }
458
459     /**
460      * Merges a ContainerBuilder with the current ContainerBuilder configuration.
461      *
462      * Service definitions overrides the current defined ones.
463      *
464      * But for parameters, they are overridden by the current ones. It allows
465      * the parameters passed to the container constructor to have precedence
466      * over the loaded ones.
467      *
468      * $container = new ContainerBuilder(array('foo' => 'bar'));
469      * $loader = new LoaderXXX($container);
470      * $loader->load('resource_name');
471      * $container->register('foo', new stdClass());
472      *
473      * In the above example, even if the loaded resource defines a foo
474      * parameter, the value will still be 'bar' as defined in the ContainerBuilder
475      * constructor.
476      *
477      * @param ContainerBuilder $container The ContainerBuilder instance to merge
478      *
479      * @throws BadMethodCallException When this ContainerBuilder is frozen
480      */
481     public function merge(ContainerBuilder $container)
482     {
483         if ($this->isFrozen()) {
484             throw new BadMethodCallException('Cannot merge on a frozen container.');
485         }
486
487         $this->addDefinitions($container->getDefinitions());
488         $this->addAliases($container->getAliases());
489         $this->getParameterBag()->add($container->getParameterBag()->all());
490
491         if ($this->trackResources) {
492             foreach ($container->getResources() as $resource) {
493                 $this->addResource($resource);
494             }
495         }
496
497         foreach ($this->extensions as $name => $extension) {
498             if (!isset($this->extensionConfigs[$name])) {
499                 $this->extensionConfigs[$name] = array();
500             }
501
502             $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
503         }
504
505         if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) {
506             $this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag());
507         }
508
509         foreach ($container->envCounters as $env => $count) {
510             if (!isset($this->envCounters[$env])) {
511                 $this->envCounters[$env] = $count;
512             } else {
513                 $this->envCounters[$env] += $count;
514             }
515         }
516     }
517
518     /**
519      * Returns the configuration array for the given extension.
520      *
521      * @param string $name The name of the extension
522      *
523      * @return array An array of configuration
524      */
525     public function getExtensionConfig($name)
526     {
527         if (!isset($this->extensionConfigs[$name])) {
528             $this->extensionConfigs[$name] = array();
529         }
530
531         return $this->extensionConfigs[$name];
532     }
533
534     /**
535      * Prepends a config array to the configs of the given extension.
536      *
537      * @param string $name   The name of the extension
538      * @param array  $config The config to set
539      */
540     public function prependExtensionConfig($name, array $config)
541     {
542         if (!isset($this->extensionConfigs[$name])) {
543             $this->extensionConfigs[$name] = array();
544         }
545
546         array_unshift($this->extensionConfigs[$name], $config);
547     }
548
549     /**
550      * Compiles the container.
551      *
552      * This method passes the container to compiler
553      * passes whose job is to manipulate and optimize
554      * the container.
555      *
556      * The main compiler passes roughly do four things:
557      *
558      *  * The extension configurations are merged;
559      *  * Parameter values are resolved;
560      *  * The parameter bag is frozen;
561      *  * Extension loading is disabled.
562      */
563     public function compile()
564     {
565         $compiler = $this->getCompiler();
566
567         if ($this->trackResources) {
568             foreach ($compiler->getPassConfig()->getPasses() as $pass) {
569                 $this->addObjectResource($pass);
570             }
571         }
572
573         $compiler->compile($this);
574
575         foreach ($this->definitions as $id => $definition) {
576             if ($this->trackResources && $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) {
577                 $this->addClassResource(new \ReflectionClass($class));
578             }
579         }
580
581         $this->extensionConfigs = array();
582         $bag = $this->getParameterBag();
583
584         parent::compile();
585
586         $this->envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : array();
587     }
588
589     /**
590      * Gets all service ids.
591      *
592      * @return array An array of all defined service ids
593      */
594     public function getServiceIds()
595     {
596         return array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds()));
597     }
598
599     /**
600      * Adds the service aliases.
601      *
602      * @param array $aliases An array of aliases
603      */
604     public function addAliases(array $aliases)
605     {
606         foreach ($aliases as $alias => $id) {
607             $this->setAlias($alias, $id);
608         }
609     }
610
611     /**
612      * Sets the service aliases.
613      *
614      * @param array $aliases An array of aliases
615      */
616     public function setAliases(array $aliases)
617     {
618         $this->aliasDefinitions = array();
619         $this->addAliases($aliases);
620     }
621
622     /**
623      * Sets an alias for an existing service.
624      *
625      * @param string       $alias The alias to create
626      * @param string|Alias $id    The service to alias
627      *
628      * @throws InvalidArgumentException if the id is not a string or an Alias
629      * @throws InvalidArgumentException if the alias is for itself
630      */
631     public function setAlias($alias, $id)
632     {
633         $alias = strtolower($alias);
634
635         if (is_string($id)) {
636             $id = new Alias($id);
637         } elseif (!$id instanceof Alias) {
638             throw new InvalidArgumentException('$id must be a string, or an Alias object.');
639         }
640
641         if ($alias === (string) $id) {
642             throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias));
643         }
644
645         unset($this->definitions[$alias]);
646
647         $this->aliasDefinitions[$alias] = $id;
648     }
649
650     /**
651      * Removes an alias.
652      *
653      * @param string $alias The alias to remove
654      */
655     public function removeAlias($alias)
656     {
657         unset($this->aliasDefinitions[strtolower($alias)]);
658     }
659
660     /**
661      * Returns true if an alias exists under the given identifier.
662      *
663      * @param string $id The service identifier
664      *
665      * @return bool true if the alias exists, false otherwise
666      */
667     public function hasAlias($id)
668     {
669         return isset($this->aliasDefinitions[strtolower($id)]);
670     }
671
672     /**
673      * Gets all defined aliases.
674      *
675      * @return Alias[] An array of aliases
676      */
677     public function getAliases()
678     {
679         return $this->aliasDefinitions;
680     }
681
682     /**
683      * Gets an alias.
684      *
685      * @param string $id The service identifier
686      *
687      * @return Alias An Alias instance
688      *
689      * @throws InvalidArgumentException if the alias does not exist
690      */
691     public function getAlias($id)
692     {
693         $id = strtolower($id);
694
695         if (!isset($this->aliasDefinitions[$id])) {
696             throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id));
697         }
698
699         return $this->aliasDefinitions[$id];
700     }
701
702     /**
703      * Registers a service definition.
704      *
705      * This methods allows for simple registration of service definition
706      * with a fluid interface.
707      *
708      * @param string $id    The service identifier
709      * @param string $class The service class
710      *
711      * @return Definition A Definition instance
712      */
713     public function register($id, $class = null)
714     {
715         return $this->setDefinition($id, new Definition($class));
716     }
717
718     /**
719      * Adds the service definitions.
720      *
721      * @param Definition[] $definitions An array of service definitions
722      */
723     public function addDefinitions(array $definitions)
724     {
725         foreach ($definitions as $id => $definition) {
726             $this->setDefinition($id, $definition);
727         }
728     }
729
730     /**
731      * Sets the service definitions.
732      *
733      * @param Definition[] $definitions An array of service definitions
734      */
735     public function setDefinitions(array $definitions)
736     {
737         $this->definitions = array();
738         $this->addDefinitions($definitions);
739     }
740
741     /**
742      * Gets all service definitions.
743      *
744      * @return Definition[] An array of Definition instances
745      */
746     public function getDefinitions()
747     {
748         return $this->definitions;
749     }
750
751     /**
752      * Sets a service definition.
753      *
754      * @param string     $id         The service identifier
755      * @param Definition $definition A Definition instance
756      *
757      * @return Definition the service definition
758      *
759      * @throws BadMethodCallException When this ContainerBuilder is frozen
760      */
761     public function setDefinition($id, Definition $definition)
762     {
763         if ($this->isFrozen()) {
764             throw new BadMethodCallException('Adding definition to a frozen container is not allowed');
765         }
766
767         $id = strtolower($id);
768
769         unset($this->aliasDefinitions[$id]);
770
771         return $this->definitions[$id] = $definition;
772     }
773
774     /**
775      * Returns true if a service definition exists under the given identifier.
776      *
777      * @param string $id The service identifier
778      *
779      * @return bool true if the service definition exists, false otherwise
780      */
781     public function hasDefinition($id)
782     {
783         return isset($this->definitions[strtolower($id)]);
784     }
785
786     /**
787      * Gets a service definition.
788      *
789      * @param string $id The service identifier
790      *
791      * @return Definition A Definition instance
792      *
793      * @throws ServiceNotFoundException if the service definition does not exist
794      */
795     public function getDefinition($id)
796     {
797         $id = strtolower($id);
798
799         if (!isset($this->definitions[$id])) {
800             throw new ServiceNotFoundException($id);
801         }
802
803         return $this->definitions[$id];
804     }
805
806     /**
807      * Gets a service definition by id or alias.
808      *
809      * The method "unaliases" recursively to return a Definition instance.
810      *
811      * @param string $id The service identifier or alias
812      *
813      * @return Definition A Definition instance
814      *
815      * @throws ServiceNotFoundException if the service definition does not exist
816      */
817     public function findDefinition($id)
818     {
819         $id = strtolower($id);
820
821         while (isset($this->aliasDefinitions[$id])) {
822             $id = (string) $this->aliasDefinitions[$id];
823         }
824
825         return $this->getDefinition($id);
826     }
827
828     /**
829      * Creates a service for a service definition.
830      *
831      * @param Definition $definition A service definition instance
832      * @param string     $id         The service identifier
833      * @param bool       $tryProxy   Whether to try proxying the service with a lazy proxy
834      *
835      * @return object The service described by the service definition
836      *
837      * @throws RuntimeException         When the factory definition is incomplete
838      * @throws RuntimeException         When the service is a synthetic service
839      * @throws InvalidArgumentException When configure callable is not callable
840      */
841     private function createService(Definition $definition, $id, $tryProxy = true)
842     {
843         if ($definition instanceof DefinitionDecorator) {
844             throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id));
845         }
846
847         if ($definition->isSynthetic()) {
848             throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id));
849         }
850
851         if ($definition->isDeprecated()) {
852             @trigger_error($definition->getDeprecationMessage($id), E_USER_DEPRECATED);
853         }
854
855         if ($tryProxy && $definition->isLazy()) {
856             $proxy = $this
857                 ->getProxyInstantiator()
858                 ->instantiateProxy(
859                     $this,
860                     $definition,
861                     $id, function () use ($definition, $id) {
862                         return $this->createService($definition, $id, false);
863                     }
864                 );
865             $this->shareService($definition, $proxy, $id);
866
867             return $proxy;
868         }
869
870         $parameterBag = $this->getParameterBag();
871
872         if (null !== $definition->getFile()) {
873             require_once $parameterBag->resolveValue($definition->getFile());
874         }
875
876         $arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())));
877
878         if (null !== $factory = $definition->getFactory()) {
879             if (is_array($factory)) {
880                 $factory = array($this->resolveServices($parameterBag->resolveValue($factory[0])), $factory[1]);
881             } elseif (!is_string($factory)) {
882                 throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
883             }
884
885             $service = call_user_func_array($factory, $arguments);
886
887             if (!$definition->isDeprecated() && is_array($factory) && is_string($factory[0])) {
888                 $r = new \ReflectionClass($factory[0]);
889
890                 if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
891                     @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);
892                 }
893             }
894         } else {
895             $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
896
897             $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
898
899             if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
900                 @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);
901             }
902         }
903
904         if ($tryProxy || !$definition->isLazy()) {
905             // share only if proxying failed, or if not a proxy
906             $this->shareService($definition, $service, $id);
907         }
908
909         $properties = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())));
910         foreach ($properties as $name => $value) {
911             $service->$name = $value;
912         }
913
914         foreach ($definition->getMethodCalls() as $call) {
915             $this->callMethod($service, $call);
916         }
917
918         if ($callable = $definition->getConfigurator()) {
919             if (is_array($callable)) {
920                 $callable[0] = $parameterBag->resolveValue($callable[0]);
921
922                 if ($callable[0] instanceof Reference) {
923                     $callable[0] = $this->get((string) $callable[0], $callable[0]->getInvalidBehavior());
924                 } elseif ($callable[0] instanceof Definition) {
925                     $callable[0] = $this->createService($callable[0], null);
926                 }
927             }
928
929             if (!is_callable($callable)) {
930                 throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
931             }
932
933             call_user_func($callable, $service);
934         }
935
936         return $service;
937     }
938
939     /**
940      * Replaces service references by the real service instance and evaluates expressions.
941      *
942      * @param mixed $value A value
943      *
944      * @return mixed The same value with all service references replaced by
945      *               the real service instances and all expressions evaluated
946      */
947     public function resolveServices($value)
948     {
949         if (is_array($value)) {
950             foreach ($value as $k => $v) {
951                 $value[$k] = $this->resolveServices($v);
952             }
953         } elseif ($value instanceof Reference) {
954             $value = $this->get((string) $value, $value->getInvalidBehavior());
955         } elseif ($value instanceof Definition) {
956             $value = $this->createService($value, null);
957         } elseif ($value instanceof Expression) {
958             $value = $this->getExpressionLanguage()->evaluate($value, array('container' => $this));
959         }
960
961         return $value;
962     }
963
964     /**
965      * Returns service ids for a given tag.
966      *
967      * Example:
968      *
969      * $container->register('foo')->addTag('my.tag', array('hello' => 'world'));
970      *
971      * $serviceIds = $container->findTaggedServiceIds('my.tag');
972      * foreach ($serviceIds as $serviceId => $tags) {
973      *     foreach ($tags as $tag) {
974      *         echo $tag['hello'];
975      *     }
976      * }
977      *
978      * @param string $name The tag name
979      *
980      * @return array An array of tags with the tagged service as key, holding a list of attribute arrays
981      */
982     public function findTaggedServiceIds($name)
983     {
984         $this->usedTags[] = $name;
985         $tags = array();
986         foreach ($this->getDefinitions() as $id => $definition) {
987             if ($definition->hasTag($name)) {
988                 $tags[$id] = $definition->getTag($name);
989             }
990         }
991
992         return $tags;
993     }
994
995     /**
996      * Returns all tags the defined services use.
997      *
998      * @return array An array of tags
999      */
1000     public function findTags()
1001     {
1002         $tags = array();
1003         foreach ($this->getDefinitions() as $id => $definition) {
1004             $tags = array_merge(array_keys($definition->getTags()), $tags);
1005         }
1006
1007         return array_unique($tags);
1008     }
1009
1010     /**
1011      * Returns all tags not queried by findTaggedServiceIds.
1012      *
1013      * @return string[] An array of tags
1014      */
1015     public function findUnusedTags()
1016     {
1017         return array_values(array_diff($this->findTags(), $this->usedTags));
1018     }
1019
1020     public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
1021     {
1022         $this->expressionLanguageProviders[] = $provider;
1023     }
1024
1025     /**
1026      * @return ExpressionFunctionProviderInterface[]
1027      */
1028     public function getExpressionLanguageProviders()
1029     {
1030         return $this->expressionLanguageProviders;
1031     }
1032
1033     /**
1034      * Resolves env parameter placeholders in a string or an array.
1035      *
1036      * @param mixed       $value     The value to resolve
1037      * @param string|null $format    A sprintf() format to use as replacement for env placeholders or null to use the default parameter format
1038      * @param array       &$usedEnvs Env vars found while resolving are added to this array
1039      *
1040      * @return string The string with env parameters resolved
1041      */
1042     public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null)
1043     {
1044         if (null === $format) {
1045             $format = '%%env(%s)%%';
1046         }
1047
1048         if (is_array($value)) {
1049             $result = array();
1050             foreach ($value as $k => $v) {
1051                 $result[$this->resolveEnvPlaceholders($k, $format, $usedEnvs)] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs);
1052             }
1053
1054             return $result;
1055         }
1056
1057         if (!is_string($value)) {
1058             return $value;
1059         }
1060
1061         $bag = $this->getParameterBag();
1062         $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
1063
1064         foreach ($envPlaceholders as $env => $placeholders) {
1065             foreach ($placeholders as $placeholder) {
1066                 if (false !== stripos($value, $placeholder)) {
1067                     $value = str_ireplace($placeholder, sprintf($format, $env), $value);
1068                     $usedEnvs[$env] = $env;
1069                     $this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1;
1070                 }
1071             }
1072         }
1073
1074         return $value;
1075     }
1076
1077     /**
1078      * Get statistics about env usage.
1079      *
1080      * @return int[] The number of time each env vars has been resolved
1081      */
1082     public function getEnvCounters()
1083     {
1084         $bag = $this->getParameterBag();
1085         $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
1086
1087         foreach ($envPlaceholders as $env => $placeholders) {
1088             if (!isset($this->envCounters[$env])) {
1089                 $this->envCounters[$env] = 0;
1090             }
1091         }
1092
1093         return $this->envCounters;
1094     }
1095
1096     /**
1097      * Returns the Service Conditionals.
1098      *
1099      * @param mixed $value An array of conditionals to return
1100      *
1101      * @return array An array of Service conditionals
1102      */
1103     public static function getServiceConditionals($value)
1104     {
1105         $services = array();
1106
1107         if (is_array($value)) {
1108             foreach ($value as $v) {
1109                 $services = array_unique(array_merge($services, self::getServiceConditionals($v)));
1110             }
1111         } elseif ($value instanceof Reference && $value->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE) {
1112             $services[] = (string) $value;
1113         }
1114
1115         return $services;
1116     }
1117
1118     /**
1119      * Retrieves the currently set proxy instantiator or instantiates one.
1120      *
1121      * @return InstantiatorInterface
1122      */
1123     private function getProxyInstantiator()
1124     {
1125         if (!$this->proxyInstantiator) {
1126             $this->proxyInstantiator = new RealServiceInstantiator();
1127         }
1128
1129         return $this->proxyInstantiator;
1130     }
1131
1132     private function callMethod($service, $call)
1133     {
1134         $services = self::getServiceConditionals($call[1]);
1135
1136         foreach ($services as $s) {
1137             if (!$this->has($s)) {
1138                 return;
1139             }
1140         }
1141
1142         call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1]))));
1143     }
1144
1145     /**
1146      * Shares a given service in the container.
1147      *
1148      * @param Definition  $definition
1149      * @param mixed       $service
1150      * @param string|null $id
1151      */
1152     private function shareService(Definition $definition, $service, $id)
1153     {
1154         if (null !== $id && $definition->isShared()) {
1155             $this->services[strtolower($id)] = $service;
1156         }
1157     }
1158
1159     private function getExpressionLanguage()
1160     {
1161         if (null === $this->expressionLanguage) {
1162             if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
1163                 throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
1164             }
1165             $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
1166         }
1167
1168         return $this->expressionLanguage;
1169     }
1170 }