4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\DependencyInjection;
14 use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
15 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16 use Symfony\Component\DependencyInjection\Exception\LogicException;
17 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
18 use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
19 use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
20 use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
21 use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
22 use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
25 * Container is a dependency injection container.
27 * It gives access to object instances (services).
29 * Services and parameters are simple key/pair stores.
31 * Parameter and service keys are case insensitive.
33 * A service id can contain lowercased letters, digits, underscores, and dots.
34 * Underscores are used to separate words, and dots to group services
39 * <li>mysql_session_storage</li>
40 * <li>symfony.mysql_session_storage</li>
43 * A service can also be defined by creating a method named
44 * getXXXService(), where XXX is the camelized version of the id:
47 * <li>request -> getRequestService()</li>
48 * <li>mysql_session_storage -> getMysqlSessionStorageService()</li>
49 * <li>symfony.mysql_session_storage -> getSymfony_MysqlSessionStorageService()</li>
52 * The container can have three possible behaviors when a service does not exist:
54 * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default)
55 * * NULL_ON_INVALID_REFERENCE: Returns null
56 * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference
57 * (for instance, ignore a setter if the service does not exist)
59 * @author Fabien Potencier <fabien@symfony.com>
60 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
62 class Container implements IntrospectableContainerInterface, ResettableContainerInterface
65 * @var ParameterBagInterface
67 protected $parameterBag;
69 protected $services = array();
70 protected $methodMap = array();
71 protected $aliases = array();
72 protected $scopes = array();
73 protected $scopeChildren = array();
74 protected $scopedServices = array();
75 protected $scopeStacks = array();
76 protected $loading = array();
78 private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_');
81 * @param ParameterBagInterface $parameterBag A ParameterBagInterface instance
83 public function __construct(ParameterBagInterface $parameterBag = null)
85 $this->parameterBag = $parameterBag ?: new ParameterBag();
89 * Compiles the container.
91 * This method does two things:
93 * * Parameter values are resolved;
94 * * The parameter bag is frozen.
96 public function compile()
98 $this->parameterBag->resolve();
100 $this->parameterBag = new FrozenParameterBag($this->parameterBag->all());
104 * Returns true if the container parameter bag are frozen.
106 * @return bool true if the container parameter bag are frozen, false otherwise
108 public function isFrozen()
110 return $this->parameterBag instanceof FrozenParameterBag;
114 * Gets the service container parameter bag.
116 * @return ParameterBagInterface A ParameterBagInterface instance
118 public function getParameterBag()
120 return $this->parameterBag;
126 * @param string $name The parameter name
128 * @return mixed The parameter value
130 * @throws InvalidArgumentException if the parameter is not defined
132 public function getParameter($name)
134 return $this->parameterBag->get($name);
138 * Checks if a parameter exists.
140 * @param string $name The parameter name
142 * @return bool The presence of parameter in container
144 public function hasParameter($name)
146 return $this->parameterBag->has($name);
152 * @param string $name The parameter name
153 * @param mixed $value The parameter value
155 public function setParameter($name, $value)
157 $this->parameterBag->set($name, $value);
163 * Setting a service to null resets the service: has() returns false and get()
164 * behaves in the same way as if the service was never created.
166 * Note: The $scope parameter is deprecated since version 2.8 and will be removed in 3.0.
168 * @param string $id The service identifier
169 * @param object $service The service instance
170 * @param string $scope The scope of the service
172 * @throws RuntimeException When trying to set a service in an inactive scope
173 * @throws InvalidArgumentException When trying to set a service in the prototype scope
175 public function set($id, $service, $scope = self::SCOPE_CONTAINER)
177 if (!in_array($scope, array('container', 'request')) || ('request' === $scope && 'request' !== $id)) {
178 @trigger_error('The concept of container scopes is deprecated since version 2.8 and will be removed in 3.0. Omit the third parameter.', E_USER_DEPRECATED);
181 if (self::SCOPE_PROTOTYPE === $scope) {
182 throw new InvalidArgumentException(sprintf('You cannot set service "%s" of scope "prototype".', $id));
185 $id = strtolower($id);
187 if ('service_container' === $id) {
188 // BC: 'service_container' is no longer a self-reference but always
189 // $this, so ignore this call.
190 // @todo Throw InvalidArgumentException in next major release.
193 if (self::SCOPE_CONTAINER !== $scope) {
194 if (!isset($this->scopedServices[$scope])) {
195 throw new RuntimeException(sprintf('You cannot set service "%s" of inactive scope.', $id));
198 $this->scopedServices[$scope][$id] = $service;
201 if (isset($this->aliases[$id])) {
202 unset($this->aliases[$id]);
205 $this->services[$id] = $service;
207 if (method_exists($this, $method = 'synchronize'.strtr($id, $this->underscoreMap).'Service')) {
211 if (null === $service) {
212 if (self::SCOPE_CONTAINER !== $scope) {
213 unset($this->scopedServices[$scope][$id]);
216 unset($this->services[$id]);
221 * Returns true if the given service is defined.
223 * @param string $id The service identifier
225 * @return bool true if the service is defined, false otherwise
227 public function has($id)
230 if ('service_container' === $id
231 || isset($this->aliases[$id])
232 || isset($this->services[$id])
233 || array_key_exists($id, $this->services)
237 if (--$i && $id !== $lcId = strtolower($id)) {
240 return method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service');
248 * If a service is defined both through a set() method and
249 * with a get{$id}Service() method, the former has always precedence.
251 * @param string $id The service identifier
252 * @param int $invalidBehavior The behavior when the service does not exist
254 * @return object The associated service
256 * @throws ServiceCircularReferenceException When a circular reference is detected
257 * @throws ServiceNotFoundException When the service is not defined
258 * @throws \Exception if an exception has been thrown when the service has been resolved
262 public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE)
264 // Attempt to retrieve the service by checking first aliases then
265 // available services. Service IDs are case insensitive, however since
266 // this method can be called thousands of times during a request, avoid
267 // calling strtolower() unless necessary.
269 if ('service_container' === $id) {
272 if (isset($this->aliases[$id])) {
273 $id = $this->aliases[$id];
275 // Re-use shared service instance if it exists.
276 if (isset($this->services[$id]) || array_key_exists($id, $this->services)) {
277 return $this->services[$id];
280 if (isset($this->loading[$id])) {
281 throw new ServiceCircularReferenceException($id, array_keys($this->loading));
284 if (isset($this->methodMap[$id])) {
285 $method = $this->methodMap[$id];
286 } elseif (--$i && $id !== $lcId = strtolower($id)) {
289 } elseif (method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) {
290 // $method is set to the right value, proceed
292 if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
294 throw new ServiceNotFoundException($id);
297 $alternatives = array();
298 foreach ($this->getServiceIds() as $knownId) {
299 $lev = levenshtein($id, $knownId);
300 if ($lev <= strlen($id) / 3 || false !== strpos($knownId, $id)) {
301 $alternatives[] = $knownId;
305 throw new ServiceNotFoundException($id, null, null, $alternatives);
311 $this->loading[$id] = true;
314 $service = $this->$method();
315 } catch (\Exception $e) {
316 unset($this->loading[$id]);
317 unset($this->services[$id]);
319 if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
324 } catch (\Throwable $e) {
325 unset($this->loading[$id]);
326 unset($this->services[$id]);
331 unset($this->loading[$id]);
338 * Returns true if the given service has actually been initialized.
340 * @param string $id The service identifier
342 * @return bool true if service has already been initialized, false otherwise
344 public function initialized($id)
346 $id = strtolower($id);
348 if ('service_container' === $id) {
349 // BC: 'service_container' was a synthetic service previously.
350 // @todo Change to false in next major release.
354 if (isset($this->aliases[$id])) {
355 $id = $this->aliases[$id];
358 return isset($this->services[$id]) || array_key_exists($id, $this->services);
364 public function reset()
366 if (!empty($this->scopedServices)) {
367 throw new LogicException('Resetting the container is not allowed when a scope is active.');
370 $this->services = array();
374 * Gets all service ids.
376 * @return array An array of all defined service ids
378 public function getServiceIds()
381 foreach (get_class_methods($this) as $method) {
382 if (preg_match('/^get(.+)Service$/', $method, $match)) {
383 $ids[] = self::underscore($match[1]);
386 $ids[] = 'service_container';
388 return array_unique(array_merge($ids, array_keys($this->services)));
392 * This is called when you enter a scope.
394 * @param string $name
396 * @throws RuntimeException When the parent scope is inactive
397 * @throws InvalidArgumentException When the scope does not exist
399 * @deprecated since version 2.8, to be removed in 3.0.
401 public function enterScope($name)
403 if ('request' !== $name) {
404 @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);
407 if (!isset($this->scopes[$name])) {
408 throw new InvalidArgumentException(sprintf('The scope "%s" does not exist.', $name));
411 if (self::SCOPE_CONTAINER !== $this->scopes[$name] && !isset($this->scopedServices[$this->scopes[$name]])) {
412 throw new RuntimeException(sprintf('The parent scope "%s" must be active when entering this scope.', $this->scopes[$name]));
415 // check if a scope of this name is already active, if so we need to
416 // remove all services of this scope, and those of any of its child
417 // scopes from the global services map
418 if (isset($this->scopedServices[$name])) {
419 $services = array($this->services, $name => $this->scopedServices[$name]);
420 unset($this->scopedServices[$name]);
422 foreach ($this->scopeChildren[$name] as $child) {
423 if (isset($this->scopedServices[$child])) {
424 $services[$child] = $this->scopedServices[$child];
425 unset($this->scopedServices[$child]);
430 $this->services = call_user_func_array('array_diff_key', $services);
431 array_shift($services);
433 // add stack entry for this scope so we can restore the removed services later
434 if (!isset($this->scopeStacks[$name])) {
435 $this->scopeStacks[$name] = new \SplStack();
437 $this->scopeStacks[$name]->push($services);
440 $this->scopedServices[$name] = array();
444 * This is called to leave the current scope, and move back to the parent
447 * @param string $name The name of the scope to leave
449 * @throws InvalidArgumentException if the scope is not active
451 * @deprecated since version 2.8, to be removed in 3.0.
453 public function leaveScope($name)
455 if ('request' !== $name) {
456 @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);
459 if (!isset($this->scopedServices[$name])) {
460 throw new InvalidArgumentException(sprintf('The scope "%s" is not active.', $name));
463 // remove all services of this scope, or any of its child scopes from
464 // the global service map
465 $services = array($this->services, $this->scopedServices[$name]);
466 unset($this->scopedServices[$name]);
468 foreach ($this->scopeChildren[$name] as $child) {
469 if (isset($this->scopedServices[$child])) {
470 $services[] = $this->scopedServices[$child];
471 unset($this->scopedServices[$child]);
476 $this->services = call_user_func_array('array_diff_key', $services);
478 // check if we need to restore services of a previous scope of this type
479 if (isset($this->scopeStacks[$name]) && count($this->scopeStacks[$name]) > 0) {
480 $services = $this->scopeStacks[$name]->pop();
481 $this->scopedServices += $services;
483 if ($this->scopeStacks[$name]->isEmpty()) {
484 unset($this->scopeStacks[$name]);
487 foreach ($services as $array) {
488 foreach ($array as $id => $service) {
489 $this->set($id, $service, $name);
496 * Adds a scope to the container.
498 * @param ScopeInterface $scope
500 * @throws InvalidArgumentException
502 * @deprecated since version 2.8, to be removed in 3.0.
504 public function addScope(ScopeInterface $scope)
506 $name = $scope->getName();
507 $parentScope = $scope->getParentName();
509 if ('request' !== $name) {
510 @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);
512 if (self::SCOPE_CONTAINER === $name || self::SCOPE_PROTOTYPE === $name) {
513 throw new InvalidArgumentException(sprintf('The scope "%s" is reserved.', $name));
515 if (isset($this->scopes[$name])) {
516 throw new InvalidArgumentException(sprintf('A scope with name "%s" already exists.', $name));
518 if (self::SCOPE_CONTAINER !== $parentScope && !isset($this->scopes[$parentScope])) {
519 throw new InvalidArgumentException(sprintf('The parent scope "%s" does not exist, or is invalid.', $parentScope));
522 $this->scopes[$name] = $parentScope;
523 $this->scopeChildren[$name] = array();
525 // normalize the child relations
526 while ($parentScope !== self::SCOPE_CONTAINER) {
527 $this->scopeChildren[$parentScope][] = $name;
528 $parentScope = $this->scopes[$parentScope];
533 * Returns whether this container has a certain scope.
535 * @param string $name The name of the scope
539 * @deprecated since version 2.8, to be removed in 3.0.
541 public function hasScope($name)
543 if ('request' !== $name) {
544 @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);
547 return isset($this->scopes[$name]);
551 * Returns whether this scope is currently active.
553 * This does not actually check if the passed scope actually exists.
555 * @param string $name
559 * @deprecated since version 2.8, to be removed in 3.0.
561 public function isScopeActive($name)
563 @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);
565 return isset($this->scopedServices[$name]);
569 * Camelizes a string.
571 * @param string $id A string to camelize
573 * @return string The camelized string
575 public static function camelize($id)
577 return strtr(ucwords(strtr($id, array('_' => ' ', '.' => '_ ', '\\' => '_ '))), array(' ' => ''));
581 * A string to underscore.
583 * @param string $id The string to underscore
585 * @return string The underscored string
587 public static function underscore($id)
589 return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), str_replace('_', '.', $id)));
592 private function __clone()