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\Compiler
;
14 use Symfony\Component\DependencyInjection\Definition
;
15 use Symfony\Component\DependencyInjection\ContainerInterface
;
16 use Symfony\Component\DependencyInjection\Reference
;
17 use Symfony\Component\DependencyInjection\ContainerBuilder
;
18 use Symfony\Component\DependencyInjection\Exception\RuntimeException
;
19 use Symfony\Component\DependencyInjection\Exception\ScopeCrossingInjectionException
;
20 use Symfony\Component\DependencyInjection\Exception\ScopeWideningInjectionException
;
23 * Checks the validity of references.
25 * The following checks are performed by this pass:
26 * - target definitions are not abstract
27 * - target definitions are of equal or wider scope
28 * - target definitions are in the same scope hierarchy
30 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
32 class CheckReferenceValidityPass
implements CompilerPassInterface
36 private $currentScope;
37 private $currentScopeAncestors;
38 private $currentScopeChildren;
41 * Processes the ContainerBuilder to validate References.
43 public function process(ContainerBuilder
$container)
45 $this->container
= $container;
47 $children = $this->container
->getScopeChildren(false);
50 $scopes = $this->container
->getScopes(false);
51 foreach ($scopes as $name => $parent) {
52 $ancestors[$name] = array($parent);
54 while (isset($scopes[$parent])) {
55 $ancestors[$name][] = $parent = $scopes[$parent];
59 foreach ($container->getDefinitions() as $id => $definition) {
60 if ($definition->isSynthetic() ||
$definition->isAbstract()) {
64 $this->currentId
= $id;
65 $this->currentScope
= $scope = $definition->getScope(false);
67 if (ContainerInterface
::SCOPE_CONTAINER
=== $scope) {
68 $this->currentScopeChildren
= array_keys($scopes);
69 $this->currentScopeAncestors
= array();
70 } elseif (ContainerInterface
::SCOPE_PROTOTYPE
!== $scope) {
71 $this->currentScopeChildren
= isset($children[$scope]) ?
$children[$scope] : array();
72 $this->currentScopeAncestors
= isset($ancestors[$scope]) ?
$ancestors[$scope] : array();
75 $this->validateReferences($definition->getArguments());
76 $this->validateReferences($definition->getMethodCalls());
77 $this->validateReferences($definition->getProperties());
82 * Validates an array of References.
84 * @param array $arguments An array of Reference objects
86 * @throws RuntimeException when there is a reference to an abstract definition
88 private function validateReferences(array $arguments)
90 foreach ($arguments as $argument) {
91 if (is_array($argument)) {
92 $this->validateReferences($argument);
93 } elseif ($argument instanceof Reference
) {
94 $targetDefinition = $this->getDefinition((string) $argument);
96 if (null !== $targetDefinition && $targetDefinition->isAbstract()) {
97 throw new RuntimeException(sprintf(
98 'The definition "%s" has a reference to an abstract definition "%s". '
99 .'Abstract definitions cannot be the target of references.',
105 $this->validateScope($argument, $targetDefinition);
111 * Validates the scope of a single Reference.
113 * @throws ScopeWideningInjectionException when the definition references a service of a narrower scope
114 * @throws ScopeCrossingInjectionException when the definition references a service of another scope hierarchy
116 private function validateScope(Reference
$reference, Definition
$definition = null)
118 if (ContainerInterface
::SCOPE_PROTOTYPE
=== $this->currentScope
) {
122 if (!$reference->isStrict(false)) {
126 if (null === $definition) {
130 if ($this->currentScope
=== $scope = $definition->getScope(false)) {
134 $id = (string) $reference;
136 if (in_array($scope, $this->currentScopeChildren
, true)) {
137 throw new ScopeWideningInjectionException($this->currentId
, $this->currentScope
, $id, $scope);
140 if (!in_array($scope, $this->currentScopeAncestors
, true)) {
141 throw new ScopeCrossingInjectionException($this->currentId
, $this->currentScope
, $id, $scope);
146 * Returns the Definition given an id.
148 * @param string $id Definition identifier
152 private function getDefinition($id)
154 if (!$this->container
->hasDefinition($id)) {
158 return $this->container
->getDefinition($id);