vendor/symfony/src/Symfony/Component/DependencyInjection/Container.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     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\Exception\ServiceNotFoundException;
       
    15 use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
       
    16 use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
       
    17 use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
       
    18 use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
       
    19 
       
    20 /**
       
    21  * Container is a dependency injection container.
       
    22  *
       
    23  * It gives access to object instances (services).
       
    24  *
       
    25  * Services and parameters are simple key/pair stores.
       
    26  *
       
    27  * Parameter and service keys are case insensitive.
       
    28  *
       
    29  * A service id can contain lowercased letters, digits, underscores, and dots.
       
    30  * Underscores are used to separate words, and dots to group services
       
    31  * under namespaces:
       
    32  *
       
    33  * <ul>
       
    34  *   <li>request</li>
       
    35  *   <li>mysql_session_storage</li>
       
    36  *   <li>symfony.mysql_session_storage</li>
       
    37  * </ul>
       
    38  *
       
    39  * A service can also be defined by creating a method named
       
    40  * getXXXService(), where XXX is the camelized version of the id:
       
    41  *
       
    42  * <ul>
       
    43  *   <li>request -> getRequestService()</li>
       
    44  *   <li>mysql_session_storage -> getMysqlSessionStorageService()</li>
       
    45  *   <li>symfony.mysql_session_storage -> getSymfony_MysqlSessionStorageService()</li>
       
    46  * </ul>
       
    47  *
       
    48  * The container can have three possible behaviors when a service does not exist:
       
    49  *
       
    50  *  * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default)
       
    51  *  * NULL_ON_INVALID_REFERENCE:      Returns null
       
    52  *  * IGNORE_ON_INVALID_REFERENCE:    Ignores the wrapping command asking for the reference
       
    53  *                                    (for instance, ignore a setter if the service does not exist)
       
    54  *
       
    55  * @author Fabien Potencier <fabien@symfony.com>
       
    56  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
       
    57  *
       
    58  * @api
       
    59  */
       
    60 class Container implements ContainerInterface
       
    61 {
       
    62     protected $parameterBag;
       
    63     protected $services;
       
    64     protected $scopes;
       
    65     protected $scopeChildren;
       
    66     protected $scopedServices;
       
    67     protected $scopeStacks;
       
    68     protected $loading = array();
       
    69 
       
    70     /**
       
    71      * Constructor.
       
    72      *
       
    73      * @param ParameterBagInterface $parameterBag A ParameterBagInterface instance
       
    74      *
       
    75      * @api
       
    76      */
       
    77     public function __construct(ParameterBagInterface $parameterBag = null)
       
    78     {
       
    79         $this->parameterBag = null === $parameterBag ? new ParameterBag() : $parameterBag;
       
    80 
       
    81         $this->services       = array();
       
    82         $this->scopes         = array();
       
    83         $this->scopeChildren  = array();
       
    84         $this->scopedServices = array();
       
    85         $this->scopeStacks    = array();
       
    86 
       
    87         $this->set('service_container', $this);
       
    88     }
       
    89 
       
    90     /**
       
    91      * Compiles the container.
       
    92      *
       
    93      * This method does two things:
       
    94      *
       
    95      *  * Parameter values are resolved;
       
    96      *  * The parameter bag is frozen.
       
    97      *
       
    98      * @api
       
    99      */
       
   100     public function compile()
       
   101     {
       
   102         $this->parameterBag->resolve();
       
   103 
       
   104         $this->parameterBag = new FrozenParameterBag($this->parameterBag->all());
       
   105     }
       
   106 
       
   107     /**
       
   108      * Returns true if the container parameter bag are frozen.
       
   109      *
       
   110      * @return Boolean true if the container parameter bag are frozen, false otherwise
       
   111      *
       
   112      * @api
       
   113      */
       
   114     public function isFrozen()
       
   115     {
       
   116         return $this->parameterBag instanceof FrozenParameterBag;
       
   117     }
       
   118 
       
   119     /**
       
   120      * Gets the service container parameter bag.
       
   121      *
       
   122      * @return ParameterBagInterface A ParameterBagInterface instance
       
   123      *
       
   124      * @api
       
   125      */
       
   126     public function getParameterBag()
       
   127     {
       
   128         return $this->parameterBag;
       
   129     }
       
   130 
       
   131     /**
       
   132      * Gets a parameter.
       
   133      *
       
   134      * @param  string $name The parameter name
       
   135      *
       
   136      * @return mixed  The parameter value
       
   137      *
       
   138      * @throws  \InvalidArgumentException if the parameter is not defined
       
   139      *
       
   140      * @api
       
   141      */
       
   142     public function getParameter($name)
       
   143     {
       
   144         return $this->parameterBag->get($name);
       
   145     }
       
   146 
       
   147     /**
       
   148      * Checks if a parameter exists.
       
   149      *
       
   150      * @param  string $name The parameter name
       
   151      *
       
   152      * @return Boolean The presence of parameter in container
       
   153      *
       
   154      * @api
       
   155      */
       
   156     public function hasParameter($name)
       
   157     {
       
   158         return $this->parameterBag->has($name);
       
   159     }
       
   160 
       
   161     /**
       
   162      * Sets a parameter.
       
   163      *
       
   164      * @param string $name  The parameter name
       
   165      * @param mixed  $value The parameter value
       
   166      *
       
   167      * @api
       
   168      */
       
   169     public function setParameter($name, $value)
       
   170     {
       
   171         $this->parameterBag->set($name, $value);
       
   172     }
       
   173 
       
   174     /**
       
   175      * Sets a service.
       
   176      *
       
   177      * @param string $id      The service identifier
       
   178      * @param object $service The service instance
       
   179      * @param string $scope   The scope of the service
       
   180      *
       
   181      * @api
       
   182      */
       
   183     public function set($id, $service, $scope = self::SCOPE_CONTAINER)
       
   184     {
       
   185         if (self::SCOPE_PROTOTYPE === $scope) {
       
   186             throw new \InvalidArgumentException('You cannot set services of scope "prototype".');
       
   187         }
       
   188 
       
   189         $id = strtolower($id);
       
   190 
       
   191         if (self::SCOPE_CONTAINER !== $scope) {
       
   192             if (!isset($this->scopedServices[$scope])) {
       
   193                 throw new \RuntimeException('You cannot set services of inactive scopes.');
       
   194             }
       
   195 
       
   196             $this->scopedServices[$scope][$id] = $service;
       
   197         }
       
   198 
       
   199         $this->services[$id] = $service;
       
   200     }
       
   201 
       
   202     /**
       
   203      * Returns true if the given service is defined.
       
   204      *
       
   205      * @param  string  $id      The service identifier
       
   206      *
       
   207      * @return Boolean true if the service is defined, false otherwise
       
   208      *
       
   209      * @api
       
   210      */
       
   211     public function has($id)
       
   212     {
       
   213         $id = strtolower($id);
       
   214 
       
   215         return isset($this->services[$id]) || method_exists($this, 'get'.strtr($id, array('_' => '', '.' => '_')).'Service');
       
   216     }
       
   217 
       
   218     /**
       
   219      * Gets a service.
       
   220      *
       
   221      * If a service is both defined through a set() method and
       
   222      * with a set*Service() method, the former has always precedence.
       
   223      *
       
   224      * @param  string  $id              The service identifier
       
   225      * @param  integer $invalidBehavior The behavior when the service does not exist
       
   226      *
       
   227      * @return object The associated service
       
   228      *
       
   229      * @throws \InvalidArgumentException if the service is not defined
       
   230      *
       
   231      * @see Reference
       
   232      *
       
   233      * @api
       
   234      */
       
   235     public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE)
       
   236     {
       
   237         $id = strtolower($id);
       
   238 
       
   239         if (isset($this->services[$id])) {
       
   240             return $this->services[$id];
       
   241         }
       
   242 
       
   243         if (isset($this->loading[$id])) {
       
   244             throw new ServiceCircularReferenceException($id, array_keys($this->loading));
       
   245         }
       
   246 
       
   247         if (method_exists($this, $method = 'get'.strtr($id, array('_' => '', '.' => '_')).'Service')) {
       
   248             $this->loading[$id] = true;
       
   249 
       
   250             try {
       
   251                 $service = $this->$method();
       
   252             } catch (\Exception $e) {
       
   253                 unset($this->loading[$id]);
       
   254                 throw $e;
       
   255             }
       
   256 
       
   257             unset($this->loading[$id]);
       
   258 
       
   259             return $service;
       
   260         }
       
   261 
       
   262         if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
       
   263             throw new ServiceNotFoundException($id);
       
   264         }
       
   265     }
       
   266 
       
   267     /**
       
   268      * Gets all service ids.
       
   269      *
       
   270      * @return array An array of all defined service ids
       
   271      */
       
   272     public function getServiceIds()
       
   273     {
       
   274         $ids = array();
       
   275         $r = new \ReflectionClass($this);
       
   276         foreach ($r->getMethods() as $method) {
       
   277             if (preg_match('/^get(.+)Service$/', $method->getName(), $match)) {
       
   278                 $ids[] = self::underscore($match[1]);
       
   279             }
       
   280         }
       
   281 
       
   282         return array_unique(array_merge($ids, array_keys($this->services)));
       
   283     }
       
   284 
       
   285     /**
       
   286      * This is called when you enter a scope
       
   287      *
       
   288      * @param string $name
       
   289      * @return void
       
   290      *
       
   291      * @api
       
   292      */
       
   293     public function enterScope($name)
       
   294     {
       
   295         if (!isset($this->scopes[$name])) {
       
   296             throw new \InvalidArgumentException(sprintf('The scope "%s" does not exist.', $name));
       
   297         }
       
   298 
       
   299         if (self::SCOPE_CONTAINER !== $this->scopes[$name] && !isset($this->scopedServices[$this->scopes[$name]])) {
       
   300             throw new \RuntimeException(sprintf('The parent scope "%s" must be active when entering this scope.', $this->scopes[$name]));
       
   301         }
       
   302 
       
   303         // check if a scope of this name is already active, if so we need to
       
   304         // remove all services of this scope, and those of any of its child
       
   305         // scopes from the global services map
       
   306         if (isset($this->scopedServices[$name])) {
       
   307             $services = array($this->services, $name => $this->scopedServices[$name]);
       
   308             unset($this->scopedServices[$name]);
       
   309 
       
   310             foreach ($this->scopeChildren[$name] as $child) {
       
   311                 $services[$child] = $this->scopedServices[$child];
       
   312                 unset($this->scopedServices[$child]);
       
   313             }
       
   314 
       
   315             // update global map
       
   316             $this->services = call_user_func_array('array_diff_key', $services);
       
   317             array_shift($services);
       
   318 
       
   319             // add stack entry for this scope so we can restore the removed services later
       
   320             if (!isset($this->scopeStacks[$name])) {
       
   321                 $this->scopeStacks[$name] = new \SplStack();
       
   322             }
       
   323             $this->scopeStacks[$name]->push($services);
       
   324         }
       
   325 
       
   326         $this->scopedServices[$name] = array();
       
   327     }
       
   328 
       
   329     /**
       
   330      * This is called to leave the current scope, and move back to the parent
       
   331      * scope.
       
   332      *
       
   333      * @param string $name The name of the scope to leave
       
   334      * @return void
       
   335      * @throws \InvalidArgumentException if the scope is not active
       
   336      *
       
   337      * @api
       
   338      */
       
   339     public function leaveScope($name)
       
   340     {
       
   341         if (!isset($this->scopedServices[$name])) {
       
   342             throw new \InvalidArgumentException(sprintf('The scope "%s" is not active.', $name));
       
   343         }
       
   344 
       
   345         // remove all services of this scope, or any of its child scopes from
       
   346         // the global service map
       
   347         $services = array($this->services, $this->scopedServices[$name]);
       
   348         unset($this->scopedServices[$name]);
       
   349         foreach ($this->scopeChildren[$name] as $child) {
       
   350             if (!isset($this->scopedServices[$child])) {
       
   351                 continue;
       
   352             }
       
   353 
       
   354             $services[] = $this->scopedServices[$child];
       
   355             unset($this->scopedServices[$child]);
       
   356         }
       
   357         $this->services = call_user_func_array('array_diff_key', $services);
       
   358 
       
   359         // check if we need to restore services of a previous scope of this type
       
   360         if (isset($this->scopeStacks[$name]) && count($this->scopeStacks[$name]) > 0) {
       
   361             $services = $this->scopeStacks[$name]->pop();
       
   362             $this->scopedServices += $services;
       
   363 
       
   364             array_unshift($services, $this->services);
       
   365             $this->services = call_user_func_array('array_merge', $services);
       
   366         }
       
   367     }
       
   368 
       
   369     /**
       
   370      * Adds a scope to the container.
       
   371      *
       
   372      * @param ScopeInterface $scope
       
   373      * @return void
       
   374      *
       
   375      * @api
       
   376      */
       
   377     public function addScope(ScopeInterface $scope)
       
   378     {
       
   379         $name = $scope->getName();
       
   380         $parentScope = $scope->getParentName();
       
   381 
       
   382         if (self::SCOPE_CONTAINER === $name || self::SCOPE_PROTOTYPE === $name) {
       
   383             throw new \InvalidArgumentException(sprintf('The scope "%s" is reserved.', $name));
       
   384         }
       
   385         if (isset($this->scopes[$name])) {
       
   386             throw new \InvalidArgumentException(sprintf('A scope with name "%s" already exists.', $name));
       
   387         }
       
   388         if (self::SCOPE_CONTAINER !== $parentScope && !isset($this->scopes[$parentScope])) {
       
   389             throw new \InvalidArgumentException(sprintf('The parent scope "%s" does not exist, or is invalid.', $parentScope));
       
   390         }
       
   391 
       
   392         $this->scopes[$name] = $parentScope;
       
   393         $this->scopeChildren[$name] = array();
       
   394 
       
   395         // normalize the child relations
       
   396         while ($parentScope !== self::SCOPE_CONTAINER) {
       
   397             $this->scopeChildren[$parentScope][] = $name;
       
   398             $parentScope = $this->scopes[$parentScope];
       
   399         }
       
   400     }
       
   401 
       
   402     /**
       
   403      * Returns whether this container has a certain scope
       
   404      *
       
   405      * @param string $name The name of the scope
       
   406      * @return Boolean
       
   407      *
       
   408      * @api
       
   409      */
       
   410     public function hasScope($name)
       
   411     {
       
   412         return isset($this->scopes[$name]);
       
   413     }
       
   414 
       
   415     /**
       
   416      * Returns whether this scope is currently active
       
   417      *
       
   418      * This does not actually check if the passed scope actually exists.
       
   419      *
       
   420      * @param string $name
       
   421      * @return Boolean
       
   422      *
       
   423      * @api
       
   424      */
       
   425     public function isScopeActive($name)
       
   426     {
       
   427         return isset($this->scopedServices[$name]);
       
   428     }
       
   429 
       
   430     /**
       
   431      * Camelizes a string.
       
   432      *
       
   433      * @param string $id A string to camelize
       
   434      * @return string The camelized string
       
   435      */
       
   436     static public function camelize($id)
       
   437     {
       
   438         return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $id);
       
   439     }
       
   440 
       
   441     /**
       
   442      * A string to underscore.
       
   443      *
       
   444      * @param string $id The string to underscore
       
   445      * @return string The underscored string
       
   446      */
       
   447     static public function underscore($id)
       
   448     {
       
   449         return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($id, '_', '.')));
       
   450     }
       
   451 }