add definition inheritance support
This commit is contained in:
parent
26666a272d
commit
803dd58002
@ -50,6 +50,10 @@ class AnalyzeServiceReferencesPass implements RepeatablePassInterface
|
||||
$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());
|
||||
|
@ -12,7 +12,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
* 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
|
||||
*
|
||||
@ -39,8 +39,8 @@ class CheckDefinitionValidityPass implements CompilerPassInterface
|
||||
));
|
||||
}
|
||||
|
||||
// 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 '
|
||||
@ -52,8 +52,9 @@ class CheckDefinitionValidityPass implements CompilerPassInterface
|
||||
|
||||
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
|
||||
));
|
||||
}
|
||||
|
@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Checks the scope of references
|
||||
*
|
||||
* Especially, we disallow services of wider scope to have references to
|
||||
* services of a narrower scope by default since it is generally a sign for a
|
||||
* wrong implementation.
|
||||
*
|
||||
* If someone specifically wants to allow this, then he can set the reference
|
||||
* to strict=false.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class CheckReferenceScopePass implements CompilerPassInterface
|
||||
{
|
||||
protected $container;
|
||||
protected $currentId;
|
||||
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()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->currentId = $id;
|
||||
$this->currentScope = $scope = $definition->getScope();
|
||||
|
||||
if (ContainerInterface::SCOPE_PROTOTYPE === $scope) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ContainerInterface::SCOPE_CONTAINER === $scope) {
|
||||
$this->currentScopeChildren = array_keys($scopes);
|
||||
$this->currentScopeAncestors = array();
|
||||
} else {
|
||||
$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) {
|
||||
if (!$argument->isStrict()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $definition = $this->getDefinition($id = (string) $argument)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->currentScope === $scope = $definition->getScope()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -42,6 +42,7 @@ class PassConfig
|
||||
$this->beforeRemovingPasses = array();
|
||||
|
||||
$this->optimizationPasses = array(
|
||||
new ResolveDefinitionTemplatesPass(),
|
||||
new ResolveParameterPlaceHoldersPass(),
|
||||
new CheckDefinitionValidityPass(),
|
||||
new ResolveReferencesToAliasesPass(),
|
||||
@ -49,11 +50,12 @@ class PassConfig
|
||||
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(),
|
||||
@ -87,6 +89,11 @@ class PassConfig
|
||||
$passes[] = $pass;
|
||||
}
|
||||
|
||||
public function getAfterRemovingPasses()
|
||||
{
|
||||
return $this->afterRemovingPasses;
|
||||
}
|
||||
|
||||
public function getBeforeOptimizationPasses()
|
||||
{
|
||||
return $this->beforeOptimizationPasses;
|
||||
@ -117,6 +124,11 @@ class PassConfig
|
||||
$this->mergePass = $pass;
|
||||
}
|
||||
|
||||
public function setAfterRemovingPasses(array $passes)
|
||||
{
|
||||
$this->afterRemovingPasses = $passes;
|
||||
}
|
||||
|
||||
public function setBeforeOptimizationPasses(array $passes)
|
||||
{
|
||||
$this->beforeOptimizationPasses = $passes;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -57,4 +57,9 @@ class RepeatedPass implements CompilerPassInterface
|
||||
{
|
||||
$this->repeat = true;
|
||||
}
|
||||
|
||||
public function getPasses()
|
||||
{
|
||||
return $this->passes;
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* This replaces all DefinitionDecorator instances with their equivalent fully
|
||||
* merged Definition instance.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ResolveDefinitionTemplatesPass implements CompilerPassInterface
|
||||
{
|
||||
protected $container;
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
foreach (array_keys($container->getDefinitions()) as $id) {
|
||||
// yes, we are specifically fetching the definition from the
|
||||
// container to ensure we are not operating on stale data
|
||||
$definition = $container->getDefinition($id);
|
||||
if (!$definition instanceof DefinitionDecorator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->resolveDefinition($id, $definition);
|
||||
}
|
||||
}
|
||||
|
||||
protected function resolveDefinition($id, DefinitionDecorator $definition)
|
||||
{
|
||||
if (!$this->container->hasDefinition($parent = $definition->getParent())) {
|
||||
throw new \RuntimeException(sprintf('The parent definition "%s" defined for definition "%s" does not exist.', $parent, $id));
|
||||
}
|
||||
|
||||
$parentDef = $this->container->getDefinition($parent);
|
||||
if ($parentDef instanceof DefinitionDecorator) {
|
||||
$parentDef = $this->resolveDefinition($parent, $parentDef);
|
||||
}
|
||||
|
||||
$def = new Definition();
|
||||
|
||||
// merge in parent definition
|
||||
// purposely ignored attributes: scope, abstract, tags
|
||||
$def->setClass($parentDef->getClass());
|
||||
$def->setArguments($parentDef->getArguments());
|
||||
$def->setMethodCalls($parentDef->getMethodCalls());
|
||||
$def->setFactoryService($parentDef->getFactoryService());
|
||||
$def->setFactoryMethod($parentDef->getFactoryMethod());
|
||||
$def->setConfigurator($parentDef->getConfigurator());
|
||||
$def->setFile($parentDef->getFile());
|
||||
$def->setPublic($parentDef->isPublic());
|
||||
|
||||
// overwrite with values specified in the decorator
|
||||
$changes = $definition->getChanges();
|
||||
if (isset($changes['class'])) {
|
||||
$def->setClass($definition->getClass());
|
||||
}
|
||||
if (isset($changes['factory_method'])) {
|
||||
$def->setFactoryMethod($definition->getFactoryMethod());
|
||||
}
|
||||
if (isset($changes['factory_service'])) {
|
||||
$def->setFactoryService($definition->getFactoryService());
|
||||
}
|
||||
if (isset($changes['configurator'])) {
|
||||
$def->setConfigurator($definition->getConfigurator());
|
||||
}
|
||||
if (isset($changes['file'])) {
|
||||
$def->setFile($definition->getFile());
|
||||
}
|
||||
if (isset($changes['public'])) {
|
||||
$def->setPublic($definition->isPublic());
|
||||
}
|
||||
|
||||
// merge arguments
|
||||
foreach ($definition->getArguments() as $k => $v) {
|
||||
if (is_numeric($k)) {
|
||||
$def->addArgument($v);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 !== strpos($k, 'index_')) {
|
||||
throw new \RuntimeException(sprintf('Invalid argument key "%s" found.', $k));
|
||||
}
|
||||
|
||||
$index = (integer) substr($k, strlen('index_'));
|
||||
$def->setArgument($index, $v);
|
||||
}
|
||||
|
||||
// append method calls
|
||||
if (count($calls = $definition->getMethodCalls()) > 0) {
|
||||
$def->setMethodCalls(array_merge($def->getMethodCalls(), $calls));
|
||||
}
|
||||
|
||||
// these attributes are always taken from the child
|
||||
$def->setAbstract($definition->isAbstract());
|
||||
$def->setScope($definition->getScope());
|
||||
$def->setTags($definition->getTags());
|
||||
|
||||
// set new definition on container
|
||||
$this->container->setDefinition($id, $def);
|
||||
|
||||
return $def;
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ class ResolveInterfaceInjectorsPass implements CompilerPassInterface
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
foreach ($container->getDefinitions() as $definition) {
|
||||
if ($definition->isSynthetic()) {
|
||||
if (!$definition->getClass()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
|
||||
{
|
||||
$this->container = $container;
|
||||
foreach ($container->getDefinitions() as $definition) {
|
||||
if ($definition->isSynthetic()) {
|
||||
if ($definition->isSynthetic() || $definition->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -75,6 +75,7 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
|
||||
$invalidBehavior = $argument->getInvalidBehavior();
|
||||
$exists = $this->container->has($id);
|
||||
|
||||
// resolve invalid behavior
|
||||
if ($exists && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
|
||||
$arguments[$k] = new Reference($id);
|
||||
} else if (!$exists && ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
|
||||
|
@ -30,6 +30,10 @@ class ResolveReferencesToAliasesPass implements CompilerPassInterface
|
||||
|
||||
foreach ($container->getDefinitions() as $id => $definition)
|
||||
{
|
||||
if ($definition->isSynthetic() || $definition->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition->setArguments($this->processArguments($definition->getArguments()));
|
||||
$definition->setMethodCalls($this->processArguments($definition->getMethodCalls()));
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ class Definition
|
||||
protected $tags;
|
||||
protected $public;
|
||||
protected $synthetic;
|
||||
protected $abstract;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -45,6 +46,7 @@ class Definition
|
||||
$this->tags = array();
|
||||
$this->public = true;
|
||||
$this->synthetic = false;
|
||||
$this->abstract = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -255,6 +257,20 @@ class Definition
|
||||
return $this->calls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tags for this definition
|
||||
*
|
||||
* @param array $tags
|
||||
*
|
||||
* @return Definition the current instance
|
||||
*/
|
||||
public function setTags(array $tags)
|
||||
{
|
||||
$this->tags = $tags;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tags.
|
||||
*
|
||||
@ -417,6 +433,32 @@ class Definition
|
||||
return $this->synthetic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this definition is abstract, that means it merely serves as a
|
||||
* template for other definitions.
|
||||
*
|
||||
* @param Boolean $boolean
|
||||
*
|
||||
* @return Definition the current instance
|
||||
*/
|
||||
public function setAbstract($boolean)
|
||||
{
|
||||
$this->abstract = (Boolean) $boolean;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this definition is abstract, that means it merely serves as a
|
||||
* template for other definitions.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isAbstract()
|
||||
{
|
||||
return $this->abstract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a configurator to call after the service is fully initialized.
|
||||
*
|
||||
|
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection;
|
||||
|
||||
/**
|
||||
* This definition decorates another definition.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class DefinitionDecorator extends Definition
|
||||
{
|
||||
protected $parent;
|
||||
protected $changes;
|
||||
|
||||
public function __construct($parent)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->parent = $parent;
|
||||
$this->changes = array();
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function getChanges()
|
||||
{
|
||||
return $this->changes;
|
||||
}
|
||||
|
||||
public function setClass($class)
|
||||
{
|
||||
$this->changes['class'] = true;
|
||||
|
||||
return parent::setClass($class);
|
||||
}
|
||||
|
||||
public function setFactoryService($service)
|
||||
{
|
||||
$this->changes['factory_service'] = true;
|
||||
|
||||
return parent::setFactoryService($service);
|
||||
}
|
||||
|
||||
public function setFactoryMethod($method)
|
||||
{
|
||||
$this->changes['factory_method'] = true;
|
||||
|
||||
return parent::setFactoryMethod($method);
|
||||
}
|
||||
|
||||
public function setConfigurator($callable)
|
||||
{
|
||||
$this->changes['configurator'] = true;
|
||||
|
||||
return parent::setConfigurator($callable);
|
||||
}
|
||||
|
||||
public function setFile($file)
|
||||
{
|
||||
$this->changes['file'] = true;
|
||||
|
||||
return parent::setFile($file);
|
||||
}
|
||||
|
||||
public function setPublic($boolean)
|
||||
{
|
||||
$this->changes['public'] = true;
|
||||
|
||||
return parent::setPublic($boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* You should always use this method when overwriting existing arguments
|
||||
* of the parent definition.
|
||||
*
|
||||
* If you directly call setArguments() keep in mind that you must follow
|
||||
* certain conventions when you want to overwrite the arguments of the
|
||||
* parent definition, otherwise your arguments will only be appended.
|
||||
*
|
||||
* @param integer $index
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return DefinitionDecorator the current instance
|
||||
*/
|
||||
public function setArgument($index, $value)
|
||||
{
|
||||
if (!is_int($index)) {
|
||||
throw new \InvalidArgumentException('$index must be an integer.');
|
||||
}
|
||||
|
||||
$this->arguments['index_'.$index] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -494,7 +494,9 @@ EOF;
|
||||
protected function addServices()
|
||||
{
|
||||
$publicServices = $privateServices = $aliasServices = '';
|
||||
foreach ($this->container->getDefinitions() as $id => $definition) {
|
||||
$definitions = $this->container->getDefinitions();
|
||||
ksort($definitions);
|
||||
foreach ($definitions as $id => $definition) {
|
||||
if ($definition->isPublic()) {
|
||||
$publicServices .= $this->addService($id, $definition);
|
||||
} else {
|
||||
@ -502,7 +504,9 @@ EOF;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->container->getAliases() as $alias => $id) {
|
||||
$aliases = $this->container->getAliases();
|
||||
ksort($aliases);
|
||||
foreach ($aliases as $alias => $id) {
|
||||
$aliasServices .= $this->addServiceAlias($alias, $id);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\InterfaceInjector;
|
||||
@ -135,9 +137,13 @@ class XmlFileLoader extends FileLoader
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = new Definition();
|
||||
if (isset($service['parent'])) {
|
||||
$definition = new DefinitionDecorator($service['parent']);
|
||||
} else {
|
||||
$definition = new Definition();
|
||||
}
|
||||
|
||||
foreach (array('class', 'scope', 'public', 'factory-method', 'factory-service', 'synthetic') as $key) {
|
||||
foreach (array('class', 'scope', 'public', 'factory-method', 'factory-service', 'synthetic', 'abstract') as $key) {
|
||||
if (isset($service[$key])) {
|
||||
$method = 'set'.str_replace('-', '', $key);
|
||||
$definition->$method((string) $service->getAttributeAsPhp($key));
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\InterfaceInjector;
|
||||
@ -137,7 +139,11 @@ class YamlFileLoader extends FileLoader
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = new Definition();
|
||||
if (isset($service['parent'])) {
|
||||
$definition = new DefinitionDecorator($service['parent']);
|
||||
} else {
|
||||
$definition = new Definition();
|
||||
}
|
||||
|
||||
if (isset($service['class'])) {
|
||||
$definition->setClass($service['class']);
|
||||
@ -155,6 +161,10 @@ class YamlFileLoader extends FileLoader
|
||||
$definition->setPublic($service['public']);
|
||||
}
|
||||
|
||||
if (isset($service['abstract'])) {
|
||||
$definition->setAbstract($service['abstract']);
|
||||
}
|
||||
|
||||
if (isset($service['factory_method'])) {
|
||||
$definition->setFactoryMethod($service['factory_method']);
|
||||
}
|
||||
|
@ -104,9 +104,11 @@
|
||||
<xsd:attribute name="scope" type="xsd:string" />
|
||||
<xsd:attribute name="public" type="boolean" />
|
||||
<xsd:attribute name="synthetic" type="boolean" />
|
||||
<xsd:attribute name="abstract" type="boolean" />
|
||||
<xsd:attribute name="factory-method" type="xsd:string" />
|
||||
<xsd:attribute name="factory-service" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="parent" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="tag">
|
||||
@ -140,6 +142,7 @@
|
||||
<xsd:attribute name="type" type="argument_type" />
|
||||
<xsd:attribute name="id" type="xsd:string" />
|
||||
<xsd:attribute name="key" type="xsd:string" />
|
||||
<xsd:attribute name="index" type="xsd:integer" />
|
||||
<xsd:attribute name="on-invalid" type="xsd:string" />
|
||||
<xsd:attribute name="strict" type="boolean" />
|
||||
</xsd:complexType>
|
||||
|
@ -34,6 +34,12 @@ class SimpleXMLElement extends \SimpleXMLElement
|
||||
$key = strtolower($key);
|
||||
}
|
||||
|
||||
// this is used by DefinitionDecorator to overwrite a specific
|
||||
// argument of the parent definition
|
||||
if (isset($arg['index'])) {
|
||||
$key = 'index_'.$arg['index'];
|
||||
}
|
||||
|
||||
switch ($arg['type']) {
|
||||
case 'service':
|
||||
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
|
@ -2,12 +2,12 @@
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CheckReferenceScopePass;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CheckReferenceValidityPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class CheckReferenceScopePassTest extends \PHPUnit_Framework_TestCase
|
||||
class CheckReferenceValidityPassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testProcessIgnoresScopeWideningIfNonStrictReference()
|
||||
{
|
||||
@ -57,6 +57,19 @@ class CheckReferenceScopePassTest extends \PHPUnit_Framework_TestCase
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testProcessDetectsReferenceToAbstractDefinition()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container->register('a')->setAbstract(true);
|
||||
$container->register('b')->addArgument(new Reference('a'));
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
public function testProcess()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
@ -68,7 +81,7 @@ class CheckReferenceScopePassTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$pass = new CheckReferenceScopePass();
|
||||
$pass = new CheckReferenceValidityPass();
|
||||
$pass->process($container);
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\ResolveDefinitionTemplatesPass;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testProcess()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('parent', 'foo')->setArguments(array('moo', 'b'));
|
||||
$container->setDefinition('child', new DefinitionDecorator('parent'))
|
||||
->setArgument(0, 'a')
|
||||
->setClass('bar')
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
|
||||
$def = $container->getDefinition('child');
|
||||
$this->assertNotInstanceOf('Symfony\Component\DependencyInjection\DefinitionDecorator', $def);
|
||||
$this->assertEquals('bar', $def->getClass());
|
||||
$this->assertEquals(array('a', 'b'), $def->getArguments());
|
||||
}
|
||||
|
||||
public function testProcessAppendsMethodCallsAlways()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container
|
||||
->register('parent')
|
||||
->addMethodCall('foo', array('bar'))
|
||||
;
|
||||
|
||||
$container
|
||||
->setDefinition('child', new DefinitionDecorator('parent'))
|
||||
->addMethodCall('bar', array('foo'))
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
|
||||
$def = $container->getDefinition('child');
|
||||
$this->assertEquals(array(
|
||||
array('foo', array('bar')),
|
||||
array('bar', array('foo')),
|
||||
), $def->getMethodCalls());
|
||||
}
|
||||
|
||||
public function testProcessDoesNotCopyAbstract()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container
|
||||
->register('parent')
|
||||
->setAbstract(true)
|
||||
;
|
||||
|
||||
$container
|
||||
->setDefinition('child', new DefinitionDecorator('parent'))
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
|
||||
$def = $container->getDefinition('child');
|
||||
$this->assertFalse($def->isAbstract());
|
||||
}
|
||||
|
||||
public function testProcessDoesNotCopyScope()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container
|
||||
->register('parent')
|
||||
->setScope('foo')
|
||||
;
|
||||
|
||||
$container
|
||||
->setDefinition('child', new DefinitionDecorator('parent'))
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
|
||||
$def = $container->getDefinition('child');
|
||||
$this->assertEquals(ContainerInterface::SCOPE_CONTAINER, $def->getScope());
|
||||
}
|
||||
|
||||
public function testProcessDoesNotCopyTags()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container
|
||||
->register('parent')
|
||||
->addTag('foo')
|
||||
;
|
||||
|
||||
$container
|
||||
->setDefinition('child', new DefinitionDecorator('parent'))
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
|
||||
$def = $container->getDefinition('child');
|
||||
$this->assertEquals(array(), $def->getTags());
|
||||
}
|
||||
|
||||
public function testProcessHandlesMultipleInheritance()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container
|
||||
->register('parent', 'foo')
|
||||
->setArguments(array('foo', 'bar', 'c'))
|
||||
;
|
||||
|
||||
$container
|
||||
->setDefinition('child2', new DefinitionDecorator('child1'))
|
||||
->setArgument(1, 'b')
|
||||
;
|
||||
|
||||
$container
|
||||
->setDefinition('child1', new DefinitionDecorator('parent'))
|
||||
->setArgument(0, 'a')
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
|
||||
$def = $container->getDefinition('child2');
|
||||
$this->assertEquals(array('a', 'b', 'c'), $def->getArguments());
|
||||
$this->assertEquals('foo', $def->getClass());
|
||||
}
|
||||
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$pass = new ResolveDefinitionTemplatesPass();
|
||||
$pass->process($container);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
|
||||
class DefinitionDecoratorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testConstructor()
|
||||
{
|
||||
$def = new DefinitionDecorator('foo');
|
||||
|
||||
$this->assertEquals('foo', $def->getParent());
|
||||
$this->assertEquals(array(), $def->getChanges());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getPropertyTests
|
||||
*/
|
||||
public function testSetProperty($property, $changeKey)
|
||||
{
|
||||
$def = new DefinitionDecorator('foo');
|
||||
|
||||
$getter = 'get'.ucfirst($property);
|
||||
$setter = 'set'.ucfirst($property);
|
||||
|
||||
$this->assertNull($def->$getter());
|
||||
$this->assertSame($def, $def->$setter('foo'));
|
||||
$this->assertEquals('foo', $def->$getter());
|
||||
$this->assertEquals(array($changeKey => true), $def->getChanges());
|
||||
}
|
||||
|
||||
public function getPropertyTests()
|
||||
{
|
||||
return array(
|
||||
array('class', 'class'),
|
||||
array('factoryService', 'factory_service'),
|
||||
array('factoryMethod', 'factory_method'),
|
||||
array('configurator', 'configurator'),
|
||||
array('file', 'file'),
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetPublic()
|
||||
{
|
||||
$def = new DefinitionDecorator('foo');
|
||||
|
||||
$this->assertTrue($def->isPublic());
|
||||
$this->assertSame($def, $def->setPublic(false));
|
||||
$this->assertFalse($def->isPublic());
|
||||
$this->assertEquals(array('public' => true), $def->getChanges());
|
||||
}
|
||||
|
||||
public function testSetArgument()
|
||||
{
|
||||
$def = new DefinitionDecorator('foo');
|
||||
|
||||
$this->assertEquals(array(), $def->getArguments());
|
||||
$this->assertSame($def, $def->setArgument(0, 'foo'));
|
||||
$this->assertEquals(array('index_0' => 'foo'), $def->getArguments());
|
||||
}
|
||||
}
|
@ -137,6 +137,18 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertTrue($def->isSynthetic(), '->isSynthetic() returns true if the instance must not be public.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::setAbstract
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::isAbstract
|
||||
*/
|
||||
public function testSetIsAbstract()
|
||||
{
|
||||
$def = new Definition('stdClass');
|
||||
$this->assertFalse($def->isAbstract(), '->isAbstract() returns false by default');
|
||||
$this->assertSame($def, $def->setAbstract(true), '->setAbstract() implements a fluent interface');
|
||||
$this->assertTrue($def->isAbstract(), '->isAbstract() returns true if the instance must not be public.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::setConfigurator
|
||||
* @covers Symfony\Component\DependencyInjection\Definition::getConfigurator
|
||||
|
@ -22,22 +22,6 @@ class ProjectServiceContainer extends Container
|
||||
parent::__construct(new ParameterBag($this->getDefaultParameters()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'foo' service.
|
||||
*
|
||||
* @return FooClass A FooClass instance.
|
||||
*/
|
||||
protected function getFooService()
|
||||
{
|
||||
$instance = call_user_func(array('FooClass', 'getInstance'), 'foo', $this->get('foo.baz'), array($this->getParameter('foo') => 'foo is '.$this->getParameter('foo'), 'bar' => $this->getParameter('foo')), true, $this);
|
||||
|
||||
$instance->setBar($this->get('bar'));
|
||||
$instance->initialize();
|
||||
sc_configure($instance);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'bar' service.
|
||||
*
|
||||
@ -55,6 +39,35 @@ class ProjectServiceContainer extends Container
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'factory_service' service.
|
||||
*
|
||||
* This service is shared.
|
||||
* This method always returns the same instance of the service.
|
||||
*
|
||||
* @return Object An instance returned by foo.baz::getInstance().
|
||||
*/
|
||||
protected function getFactoryServiceService()
|
||||
{
|
||||
return $this->services['factory_service'] = $this->get('foo.baz')->getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'foo' service.
|
||||
*
|
||||
* @return FooClass A FooClass instance.
|
||||
*/
|
||||
protected function getFooService()
|
||||
{
|
||||
$instance = call_user_func(array('FooClass', 'getInstance'), 'foo', $this->get('foo.baz'), array($this->getParameter('foo') => 'foo is '.$this->getParameter('foo'), 'bar' => $this->getParameter('foo')), true, $this);
|
||||
|
||||
$instance->setBar($this->get('bar'));
|
||||
$instance->initialize();
|
||||
sc_configure($instance);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'foo.baz' service.
|
||||
*
|
||||
@ -112,19 +125,6 @@ class ProjectServiceContainer extends Container
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'factory_service' service.
|
||||
*
|
||||
* This service is shared.
|
||||
* This method always returns the same instance of the service.
|
||||
*
|
||||
* @return Object An instance returned by foo.baz::getInstance().
|
||||
*/
|
||||
protected function getFactoryServiceService()
|
||||
{
|
||||
return $this->services['factory_service'] = $this->get('foo.baz')->getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alias_for_foo service alias.
|
||||
*
|
||||
|
@ -22,21 +22,6 @@ class ProjectServiceContainer extends Container
|
||||
parent::__construct(new ParameterBag($this->getDefaultParameters()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'barfactory' service.
|
||||
*
|
||||
* This service is shared.
|
||||
* This method always returns the same instance of the service.
|
||||
*
|
||||
* @return BarClassFactory A BarClassFactory instance.
|
||||
*/
|
||||
protected function getBarfactoryService()
|
||||
{
|
||||
return $this->services['barfactory'] = new \BarClassFactory();
|
||||
|
||||
$this->applyInterfaceInjectors($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'bar' service.
|
||||
*
|
||||
@ -52,6 +37,21 @@ class ProjectServiceContainer extends Container
|
||||
$this->applyInterfaceInjectors($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'barfactory' service.
|
||||
*
|
||||
* This service is shared.
|
||||
* This method always returns the same instance of the service.
|
||||
*
|
||||
* @return BarClassFactory A BarClassFactory instance.
|
||||
*/
|
||||
protected function getBarfactoryService()
|
||||
{
|
||||
return $this->services['barfactory'] = new \BarClassFactory();
|
||||
|
||||
$this->applyInterfaceInjectors($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all known interface injection calls
|
||||
*
|
||||
|
Reference in New Issue
Block a user