Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\DependencyInjection; Chris@0: Chris@0: use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; Chris@0: use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; Chris@14: use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; Chris@17: use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; Chris@0: use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; Chris@0: use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; Chris@0: use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; Chris@17: use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; Chris@0: Chris@0: /** Chris@0: * Container is a dependency injection container. Chris@0: * Chris@0: * It gives access to object instances (services). Chris@0: * Services and parameters are simple key/pair stores. Chris@14: * The container can have four possible behaviors when a service Chris@14: * does not exist (or is not initialized for the last case): Chris@0: * Chris@0: * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default) Chris@0: * * NULL_ON_INVALID_REFERENCE: Returns null Chris@0: * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference Chris@0: * (for instance, ignore a setter if the service does not exist) Chris@14: * * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services or invalid references Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: * @author Johannes M. Schmitt Chris@0: */ Chris@0: class Container implements ResettableContainerInterface Chris@0: { Chris@0: protected $parameterBag; Chris@17: protected $services = []; Chris@17: protected $fileMap = []; Chris@17: protected $methodMap = []; Chris@17: protected $aliases = []; Chris@17: protected $loading = []; Chris@17: protected $resolving = []; Chris@17: protected $syntheticIds = []; Chris@0: Chris@12: /** Chris@12: * @internal Chris@12: */ Chris@17: protected $privates = []; Chris@12: Chris@14: /** Chris@14: * @internal Chris@14: */ Chris@17: protected $normalizedIds = []; Chris@14: Chris@17: private $underscoreMap = ['_' => '', '.' => '_', '\\' => '_']; Chris@17: private $envCache = []; Chris@14: private $compiled = false; Chris@14: private $getEnv; Chris@0: Chris@0: public function __construct(ParameterBagInterface $parameterBag = null) Chris@0: { Chris@0: $this->parameterBag = $parameterBag ?: new EnvPlaceholderParameterBag(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Compiles the container. Chris@0: * Chris@0: * This method does two things: Chris@0: * Chris@0: * * Parameter values are resolved; Chris@0: * * The parameter bag is frozen. Chris@0: */ Chris@0: public function compile() Chris@0: { Chris@0: $this->parameterBag->resolve(); Chris@0: Chris@0: $this->parameterBag = new FrozenParameterBag($this->parameterBag->all()); Chris@14: Chris@14: $this->compiled = true; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns true if the container is compiled. Chris@14: * Chris@14: * @return bool Chris@14: */ Chris@14: public function isCompiled() Chris@14: { Chris@14: return $this->compiled; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns true if the container parameter bag are frozen. Chris@0: * Chris@14: * @deprecated since version 3.3, to be removed in 4.0. Chris@14: * Chris@0: * @return bool true if the container parameter bag are frozen, false otherwise Chris@0: */ Chris@0: public function isFrozen() Chris@0: { Chris@14: @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED); Chris@14: Chris@0: return $this->parameterBag instanceof FrozenParameterBag; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the service container parameter bag. Chris@0: * Chris@0: * @return ParameterBagInterface A ParameterBagInterface instance Chris@0: */ Chris@0: public function getParameterBag() Chris@0: { Chris@0: return $this->parameterBag; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a parameter. Chris@0: * Chris@0: * @param string $name The parameter name Chris@0: * Chris@0: * @return mixed The parameter value Chris@0: * Chris@0: * @throws InvalidArgumentException if the parameter is not defined Chris@0: */ Chris@0: public function getParameter($name) Chris@0: { Chris@0: return $this->parameterBag->get($name); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks if a parameter exists. Chris@0: * Chris@0: * @param string $name The parameter name Chris@0: * Chris@0: * @return bool The presence of parameter in container Chris@0: */ Chris@0: public function hasParameter($name) Chris@0: { Chris@0: return $this->parameterBag->has($name); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a parameter. Chris@0: * Chris@0: * @param string $name The parameter name Chris@0: * @param mixed $value The parameter value Chris@0: */ Chris@0: public function setParameter($name, $value) Chris@0: { Chris@0: $this->parameterBag->set($name, $value); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a service. Chris@0: * Chris@18: * Setting a synthetic service to null resets it: has() returns false and get() Chris@0: * behaves in the same way as if the service was never created. Chris@0: * Chris@0: * @param string $id The service identifier Chris@0: * @param object $service The service instance Chris@0: */ Chris@0: public function set($id, $service) Chris@0: { Chris@14: // Runs the internal initializer; used by the dumped container to include always-needed files Chris@14: if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) { Chris@14: $initialize = $this->privates['service_container']; Chris@14: unset($this->privates['service_container']); Chris@14: $initialize(); Chris@14: } Chris@14: Chris@14: $id = $this->normalizeId($id); Chris@0: Chris@0: if ('service_container' === $id) { Chris@0: throw new InvalidArgumentException('You cannot set service "service_container".'); Chris@0: } Chris@0: Chris@14: if (isset($this->privates[$id]) || !(isset($this->fileMap[$id]) || isset($this->methodMap[$id]))) { Chris@14: if (!isset($this->privates[$id]) && !isset($this->getRemovedIds()[$id])) { Chris@14: // no-op Chris@14: } elseif (null === $service) { Chris@14: @trigger_error(sprintf('The "%s" service is private, unsetting it is deprecated since Symfony 3.2 and will fail in 4.0.', $id), E_USER_DEPRECATED); Chris@14: unset($this->privates[$id]); Chris@14: } else { Chris@14: @trigger_error(sprintf('The "%s" service is private, replacing it is deprecated since Symfony 3.2 and will fail in 4.0.', $id), E_USER_DEPRECATED); Chris@14: } Chris@14: } elseif (isset($this->services[$id])) { Chris@14: if (null === $service) { Chris@14: @trigger_error(sprintf('The "%s" service is already initialized, unsetting it is deprecated since Symfony 3.3 and will fail in 4.0.', $id), E_USER_DEPRECATED); Chris@14: } else { Chris@14: @trigger_error(sprintf('The "%s" service is already initialized, replacing it is deprecated since Symfony 3.3 and will fail in 4.0.', $id), E_USER_DEPRECATED); Chris@14: } Chris@14: } Chris@14: Chris@0: if (isset($this->aliases[$id])) { Chris@0: unset($this->aliases[$id]); Chris@0: } Chris@0: Chris@0: if (null === $service) { Chris@0: unset($this->services[$id]); Chris@14: Chris@14: return; Chris@0: } Chris@0: Chris@14: $this->services[$id] = $service; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns true if the given service is defined. Chris@0: * Chris@0: * @param string $id The service identifier Chris@0: * Chris@0: * @return bool true if the service is defined, false otherwise Chris@0: */ Chris@0: public function has($id) Chris@0: { Chris@0: for ($i = 2;;) { Chris@12: if (isset($this->privates[$id])) { Chris@14: @trigger_error(sprintf('The "%s" service is private, checking for its existence is deprecated since Symfony 3.2 and will fail in 4.0.', $id), E_USER_DEPRECATED); Chris@12: } Chris@14: if (isset($this->aliases[$id])) { Chris@14: $id = $this->aliases[$id]; Chris@14: } Chris@14: if (isset($this->services[$id])) { Chris@14: return true; Chris@14: } Chris@14: if ('service_container' === $id) { Chris@0: return true; Chris@0: } Chris@0: Chris@14: if (isset($this->fileMap[$id]) || isset($this->methodMap[$id])) { Chris@0: return true; Chris@0: } Chris@0: Chris@14: if (--$i && $id !== $normalizedId = $this->normalizeId($id)) { Chris@14: $id = $normalizedId; Chris@0: continue; Chris@0: } Chris@0: Chris@0: // We only check the convention-based factory in a compiled container (i.e. a child class other than a ContainerBuilder, Chris@0: // and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper) Chris@0: if (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class && method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service')) { Chris@14: @trigger_error('Generating a dumped container without populating the method map is deprecated since Symfony 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', E_USER_DEPRECATED); Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a service. Chris@0: * Chris@0: * If a service is defined both through a set() method and Chris@0: * with a get{$id}Service() method, the former has always precedence. Chris@0: * Chris@0: * @param string $id The service identifier Chris@0: * @param int $invalidBehavior The behavior when the service does not exist Chris@0: * Chris@0: * @return object The associated service Chris@0: * Chris@0: * @throws ServiceCircularReferenceException When a circular reference is detected Chris@0: * @throws ServiceNotFoundException When the service is not defined Chris@0: * @throws \Exception if an exception has been thrown when the service has been resolved Chris@0: * Chris@0: * @see Reference Chris@0: */ Chris@14: public function get($id, $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERENCE */ 1) Chris@0: { Chris@0: // Attempt to retrieve the service by checking first aliases then Chris@0: // available services. Service IDs are case insensitive, however since Chris@0: // this method can be called thousands of times during a request, avoid Chris@14: // calling $this->normalizeId($id) unless necessary. Chris@0: for ($i = 2;;) { Chris@12: if (isset($this->privates[$id])) { Chris@14: @trigger_error(sprintf('The "%s" service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead.', $id), E_USER_DEPRECATED); Chris@0: } Chris@0: if (isset($this->aliases[$id])) { Chris@0: $id = $this->aliases[$id]; Chris@0: } Chris@12: Chris@0: // Re-use shared service instance if it exists. Chris@0: if (isset($this->services[$id])) { Chris@0: return $this->services[$id]; Chris@0: } Chris@12: if ('service_container' === $id) { Chris@12: return $this; Chris@12: } Chris@0: Chris@0: if (isset($this->loading[$id])) { Chris@17: throw new ServiceCircularReferenceException($id, array_merge(array_keys($this->loading), [$id])); Chris@0: } Chris@0: Chris@0: $this->loading[$id] = true; Chris@0: Chris@0: try { Chris@14: if (isset($this->fileMap[$id])) { Chris@14: return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]); Chris@14: } elseif (isset($this->methodMap[$id])) { Chris@14: return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); Chris@14: } elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) { Chris@14: unset($this->loading[$id]); Chris@14: $id = $normalizedId; Chris@14: continue; Chris@14: } elseif (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class && method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) { Chris@14: // We only check the convention-based factory in a compiled container (i.e. a child class other than a ContainerBuilder, Chris@14: // and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper) Chris@14: @trigger_error('Generating a dumped container without populating the method map is deprecated since Symfony 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', E_USER_DEPRECATED); Chris@14: Chris@14: return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$method}(); Chris@14: } Chris@14: Chris@14: break; Chris@0: } catch (\Exception $e) { Chris@0: unset($this->services[$id]); Chris@0: Chris@0: throw $e; Chris@0: } finally { Chris@0: unset($this->loading[$id]); Chris@0: } Chris@14: } Chris@0: Chris@14: if (/* self::EXCEPTION_ON_INVALID_REFERENCE */ 1 === $invalidBehavior) { Chris@14: if (!$id) { Chris@14: throw new ServiceNotFoundException($id); Chris@14: } Chris@14: if (isset($this->syntheticIds[$id])) { Chris@17: throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is synthetic, it needs to be set at boot time before it can be used.', $id)); Chris@14: } Chris@14: if (isset($this->getRemovedIds()[$id])) { Chris@17: throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.', $id)); Chris@14: } Chris@14: Chris@17: $alternatives = []; Chris@14: foreach ($this->getServiceIds() as $knownId) { Chris@14: $lev = levenshtein($id, $knownId); Chris@17: if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) { Chris@14: $alternatives[] = $knownId; Chris@14: } Chris@14: } Chris@14: Chris@14: throw new ServiceNotFoundException($id, null, null, $alternatives); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns true if the given service has actually been initialized. Chris@0: * Chris@0: * @param string $id The service identifier Chris@0: * Chris@0: * @return bool true if service has already been initialized, false otherwise Chris@0: */ Chris@0: public function initialized($id) Chris@0: { Chris@14: $id = $this->normalizeId($id); Chris@14: Chris@14: if (isset($this->privates[$id])) { Chris@14: @trigger_error(sprintf('Checking for the initialization of the "%s" private service is deprecated since Symfony 3.4 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); Chris@14: } Chris@0: Chris@12: if (isset($this->aliases[$id])) { Chris@12: $id = $this->aliases[$id]; Chris@12: } Chris@12: Chris@0: if ('service_container' === $id) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: return isset($this->services[$id]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function reset() Chris@0: { Chris@17: $this->services = []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets all service ids. Chris@0: * Chris@0: * @return array An array of all defined service ids Chris@0: */ Chris@0: public function getServiceIds() Chris@0: { Chris@17: $ids = []; Chris@0: Chris@0: if (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class) { Chris@0: // We only check the convention-based factory in a compiled container (i.e. a child class other than a ContainerBuilder, Chris@0: // and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper) Chris@14: @trigger_error('Generating a dumped container without populating the method map is deprecated since Symfony 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', E_USER_DEPRECATED); Chris@0: Chris@0: foreach (get_class_methods($this) as $method) { Chris@0: if (preg_match('/^get(.+)Service$/', $method, $match)) { Chris@0: $ids[] = self::underscore($match[1]); Chris@0: } Chris@0: } Chris@0: } Chris@0: $ids[] = 'service_container'; Chris@0: Chris@14: return array_unique(array_merge($ids, array_keys($this->methodMap), array_keys($this->fileMap), array_keys($this->services))); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Gets service ids that existed at compile time. Chris@14: * Chris@14: * @return array Chris@14: */ Chris@14: public function getRemovedIds() Chris@14: { Chris@17: return []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Camelizes a string. Chris@0: * Chris@0: * @param string $id A string to camelize Chris@0: * Chris@0: * @return string The camelized string Chris@0: */ Chris@0: public static function camelize($id) Chris@0: { Chris@17: return strtr(ucwords(strtr($id, ['_' => ' ', '.' => '_ ', '\\' => '_ '])), [' ' => '']); Chris@0: } Chris@0: Chris@0: /** Chris@0: * A string to underscore. Chris@0: * Chris@0: * @param string $id The string to underscore Chris@0: * Chris@0: * @return string The underscored string Chris@0: */ Chris@0: public static function underscore($id) Chris@0: { Chris@17: return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], str_replace('_', '.', $id))); Chris@0: } Chris@0: Chris@0: /** Chris@14: * Creates a service by requiring its factory file. Chris@14: * Chris@14: * @return object The service created by the file Chris@14: */ Chris@14: protected function load($file) Chris@14: { Chris@14: return require $file; Chris@14: } Chris@14: Chris@14: /** Chris@0: * Fetches a variable from the environment. Chris@0: * Chris@14: * @param string $name The name of the environment variable Chris@0: * Chris@14: * @return mixed The value to use for the provided environment variable name Chris@0: * Chris@0: * @throws EnvNotFoundException When the environment variable is not found and has no default value Chris@0: */ Chris@0: protected function getEnv($name) Chris@0: { Chris@14: if (isset($this->resolving[$envName = "env($name)"])) { Chris@14: throw new ParameterCircularReferenceException(array_keys($this->resolving)); Chris@14: } Chris@18: if (isset($this->envCache[$name]) || \array_key_exists($name, $this->envCache)) { Chris@0: return $this->envCache[$name]; Chris@0: } Chris@14: if (!$this->has($id = 'container.env_var_processors_locator')) { Chris@17: $this->set($id, new ServiceLocator([])); Chris@0: } Chris@14: if (!$this->getEnv) { Chris@14: $this->getEnv = new \ReflectionMethod($this, __FUNCTION__); Chris@14: $this->getEnv->setAccessible(true); Chris@14: $this->getEnv = $this->getEnv->getClosure($this); Chris@0: } Chris@14: $processors = $this->get($id); Chris@14: Chris@14: if (false !== $i = strpos($name, ':')) { Chris@14: $prefix = substr($name, 0, $i); Chris@14: $localName = substr($name, 1 + $i); Chris@14: } else { Chris@14: $prefix = 'string'; Chris@14: $localName = $name; Chris@14: } Chris@14: $processor = $processors->has($prefix) ? $processors->get($prefix) : new EnvVarProcessor($this); Chris@14: Chris@14: $this->resolving[$envName] = true; Chris@14: try { Chris@14: return $this->envCache[$name] = $processor->getEnv($prefix, $localName, $this->getEnv); Chris@14: } finally { Chris@14: unset($this->resolving[$envName]); Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns the case sensitive id used at registration time. Chris@14: * Chris@14: * @param string $id Chris@14: * Chris@14: * @return string Chris@14: * Chris@14: * @internal Chris@14: */ Chris@14: public function normalizeId($id) Chris@14: { Chris@14: if (!\is_string($id)) { Chris@14: $id = (string) $id; Chris@14: } Chris@14: if (isset($this->normalizedIds[$normalizedId = strtolower($id)])) { Chris@14: $normalizedId = $this->normalizedIds[$normalizedId]; Chris@14: if ($id !== $normalizedId) { Chris@14: @trigger_error(sprintf('Service identifiers will be made case sensitive in Symfony 4.0. Using "%s" instead of "%s" is deprecated since Symfony 3.3.', $id, $normalizedId), E_USER_DEPRECATED); Chris@14: } Chris@14: } else { Chris@14: $normalizedId = $this->normalizedIds[$normalizedId] = $id; Chris@0: } Chris@0: Chris@14: return $normalizedId; Chris@0: } Chris@0: Chris@0: private function __clone() Chris@0: { Chris@0: } Chris@0: }