add definition inheritance support

This commit is contained in:
Johannes Schmitt 2011-01-27 00:14:31 +01:00 committed by Fabien Potencier
parent 26666a272d
commit 803dd58002
24 changed files with 757 additions and 187 deletions

View File

@ -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());

View File

@ -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
));
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
}
}
}

View File

@ -57,4 +57,9 @@ class RepeatedPass implements CompilerPassInterface
{
$this->repeat = true;
}
public function getPasses()
{
return $this->passes;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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()));
}

View File

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

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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));

View File

@ -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']);
}

View File

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

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

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

View File

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

View File

@ -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
*