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.
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
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.

@@ -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);
}
}
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
@@ -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);
}
}
}
}
Expand Up @@ -57,4 +57,9 @@ public function setRepeat()
{
$this->repeat = true;
}

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

0 comments on commit 803dd58

Please sign in to comment.