Skip to content

Commit

Permalink
add definition inheritance support
Browse files Browse the repository at this point in the history
  • Loading branch information
schmittjoh authored and fabpot committed Jan 28, 2011
1 parent 26666a2 commit 803dd58
Show file tree
Hide file tree
Showing 24 changed files with 744 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public function process(ContainerBuilder $container)
$this->graph->clear();

foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isSynthetic() || $definition->isAbstract()) {
continue;
}

$this->currentId = $id;
$this->currentDefinition = $definition;
$this->processArguments($definition->getArguments());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* Later passes can rely on the following, and specifically do not need to
* perform these checks themself:
*
* - non synthetic services always have a class set
* - non synthetic, non abstract services always have a class set
* - synthetic services are always public
* - synthetic services are always of non-prototype scope
*
Expand All @@ -39,8 +39,8 @@ public function process(ContainerBuilder $container)
));
}

// non-synthetic service has class
if (!$definition->isSynthetic() && !$definition->getClass()) {
// non-synthetic, non-abstract service has class
if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass()) {
if ($definition->getFactoryService()) {
throw new \RuntimeException(sprintf(
'Please add the class to service "%s" even if it is constructed '
Expand All @@ -52,8 +52,9 @@ public function process(ContainerBuilder $container)

throw new \RuntimeException(sprintf(
'The definition for "%s" has no class. If you intend to inject '
.'this service dynamically at runtime, please mark it as synthetic=true, '
.'otherwise specify a class to get rid of this error.',
.'this service dynamically at runtime, please mark it as synthetic=true. '
.'If this is an abstract definition solely used by child definitions, '
.'please add abstract=true, otherwise specify a class to get rid of this error.',
$id
));
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

namespace Symfony\Component\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* Checks the validity of references
*
* The following checks are performed by this pass:
* - target definitions are not abstract
* - target definitions are of equal or wider scope
* - target definitions are in the same scope hierarchy
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckReferenceValidityPass implements CompilerPassInterface
{
protected $container;
protected $currentId;
protected $currentDefinition;
protected $currentScope;
protected $currentScopeAncestors;
protected $currentScopeChildren;

public function process(ContainerBuilder $container)
{
$this->container = $container;

$children = $this->container->getScopeChildren();
$ancestors = array();

$scopes = $this->container->getScopes();
foreach ($scopes as $name => $parent) {
$ancestors[$name] = array($parent);

while (isset($scopes[$parent])) {
$ancestors[$name][] = $parent = $scopes[$parent];
}
}

foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isSynthetic() || $definition->isAbstract()) {
continue;
}

$this->currentId = $id;
$this->currentDefinition = $definition;
$this->currentScope = $scope = $definition->getScope();

if (ContainerInterface::SCOPE_CONTAINER === $scope) {
$this->currentScopeChildren = array_keys($scopes);
$this->currentScopeAncestors = array();
} else if (ContainerInterface::SCOPE_PROTOTYPE !== $scope) {
$this->currentScopeChildren = $children[$scope];
$this->currentScopeAncestors = $ancestors[$scope];
}

$this->validateReferences($definition->getArguments());
$this->validateReferences($definition->getMethodCalls());
}
}

protected function validateReferences(array $arguments)
{
foreach ($arguments as $argument) {
if (is_array($argument)) {
$this->validateReferences($argument);
} elseif ($argument instanceof Reference) {
$targetDefinition = $this->getDefinition((string) $argument);

if (null !== $targetDefinition && $targetDefinition->isAbstract()) {
throw new \RuntimeException(sprintf(
'The definition "%s" has a reference to an abstract definition "%s". '
.'Abstract definitions cannot be the target of references.',
$this->currentId,
$argument
));
}

$this->validateScope($argument, $targetDefinition);
}
}
}

protected function validateScope(Reference $reference, Definition $definition = null)
{
if (ContainerInterface::SCOPE_PROTOTYPE === $this->currentScope) {
return;
}

if (!$reference->isStrict()) {
return;
}

if (null === $definition) {
return;
}

if ($this->currentScope === $scope = $definition->getScope()) {
return;
}

$id = (string) $reference;

if (in_array($scope, $this->currentScopeChildren, true)) {
throw new \RuntimeException(sprintf(
'Scope Widening Injection detected: The definition "%s" references the service "%s" which belongs to a narrower scope. '
.'Generally, it is safer to either move "%s" to scope "%s" or alternatively rely on the provider pattern by injecting the container itself, and requesting the service "%s" each time it is needed. '
.'In rare, special cases however that might not be necessary, then you can set the reference to strict=false to get rid of this error.',
$this->currentId,
$id,
$this->currentId,
$scope,
$id
));
}

if (!in_array($scope, $this->currentScopeAncestors, true)) {
throw new \RuntimeException(sprintf(
'Cross-Scope Injection detected: The definition "%s" references the service "%s" which belongs to another scope hierarchy. '
.'This service might not be available consistently. Generally, it is safer to either move the definition "%s" to scope "%s", or '
.'declare "%s" as a child scope of "%s". If you can be sure that the other scope is always active, you can set the reference to strict=false to get rid of this error.',
$this->currentId,
$id,
$this->currentId,
$scope,
$this->currentScope,
$scope
));
}
}

protected function getDefinition($id)
{
if (!$this->container->hasDefinition($id)) {
return null;
}

return $this->container->getDefinition($id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,20 @@ public function __construct()
$this->beforeRemovingPasses = array();

$this->optimizationPasses = array(
new ResolveDefinitionTemplatesPass(),
new ResolveParameterPlaceHoldersPass(),
new CheckDefinitionValidityPass(),
new ResolveReferencesToAliasesPass(),
new ResolveInterfaceInjectorsPass(),
new ResolveInvalidReferencesPass(),
new AnalyzeServiceReferencesPass(true),
new CheckCircularReferencesPass(),
new CheckReferenceScopePass(),
new CheckReferenceValidityPass(),
);

$this->removingPasses = array(
new RemovePrivateAliasesPass(),
new RemoveAbstractDefinitionsPass(),
new ReplaceAliasByActualDefinitionPass(),
new RepeatedPass(array(
new AnalyzeServiceReferencesPass(),
Expand Down Expand Up @@ -87,6 +89,11 @@ public function addPass(CompilerPassInterface $pass, $type = self::TYPE_BEFORE_O
$passes[] = $pass;
}

public function getAfterRemovingPasses()
{
return $this->afterRemovingPasses;
}

public function getBeforeOptimizationPasses()
{
return $this->beforeOptimizationPasses;
Expand Down Expand Up @@ -117,6 +124,11 @@ public function setMergePass(CompilerPassInterface $pass)
$this->mergePass = $pass;
}

public function setAfterRemovingPasses(array $passes)
{
$this->afterRemovingPasses = $passes;
}

public function setBeforeOptimizationPasses(array $passes)
{
$this->beforeOptimizationPasses = $passes;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Symfony\Component\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\ContainerBuilder;

class RemoveAbstractDefinitionsPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isAbstract()) {
$container->remove($id);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,9 @@ public function setRepeat()
{
$this->repeat = true;
}

public function getPasses()
{
return $this->passes;
}
}
Loading

0 comments on commit 803dd58

Please sign in to comment.