Not allowing autoconfigure, instanceofConditionals or defaults for ChildDefinition

Also, not allowing arguments or method calls for autoconfigure. This is a safety
mechanism, since we don't have merging logic. It will allow us to add this in the
future if we want to.

The reason is that parent-child definitions are a different mechanism for "inheritance"
than instanceofConditionas and defaults... creating some edge cases when trying to
figure out which settings "win". For example:

Suppose a child and parent definitions are defined in different YAML files. The
child receives public: false from its _defaults, and the parent receives public: true
from its _defaults. Should the final child definition be public: true (so the parent
overrides the child, even though it only came from _defaults) or public: false (where
the child wins... even though it was only set from its _defaults). Or, if the parent
is explicitly set to public: true, should that override the public: false of the
child (which it got from its _defaults)? On one hand, the parent is being explicitly
set. On the other hand, the child is explicitly in a file settings _defaults public
to false. There's no correct answer.

There are also problems with instanceof. The importance goes:
  defaults < instanceof < service definition

But how does parent-child relationships fit into that? If a child has public: false
from an _instanceof, but the parent explicitly sets public: true, which wins? Should
we assume the parent definition wins because it's explicitly set? Or would the
_instanceof win, because that's being explicitly applied to the child definition's
class by an _instanceof that lives in the same file as that class (whereas the parent
definition may live in a different file).

Because of this, @nicolas-grekas and I (we also talked a bit to Fabien) decided that
the complexity was growing too much. The solution is to not allow any of these
new feature to be used by ChildDefinition objects. In other words, when you want some
sort of "inheritance" for your service, you should *either* giving your service a
parent *or* using defaults and instanceof. And instead of silently not applying
defaults and instanceof to child definitions, I think it's better to scream that it's
not supported.
This commit is contained in:
Ryan Weaver 2017-04-27 11:48:07 -04:00
parent f8133cbd4e
commit a943b96d42
42 changed files with 493 additions and 190 deletions

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
@ -134,6 +135,22 @@ class ChildDefinition extends Definition
return $this;
}
/**
* @internal
*/
public function setAutoconfigured($autoconfigured)
{
throw new BadMethodCallException('A ChildDefinition cannot be autoconfigured.');
}
/**
* @internal
*/
public function setInstanceofConditionals(array $instanceof)
{
throw new BadMethodCallException('A ChildDefinition cannot have instanceof conditionals set on it.');
}
}
class_alias(ChildDefinition::class, DefinitionDecorator::class);

View File

@ -101,7 +101,6 @@ class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
$def->setPublic($parentDef->isPublic());
$def->setLazy($parentDef->isLazy());
$def->setAutowired($parentDef->isAutowired());
$def->setAutoconfigured($parentDef->isAutoconfigured());
$def->setChanges($parentDef->getChanges());
// overwrite with values specified in the decorator
@ -130,9 +129,6 @@ class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
if (isset($changes['autowired'])) {
$def->setAutowired($definition->isAutowired());
}
if (isset($changes['autoconfigured'])) {
$def->setAutoconfigured($definition->isAutoconfigured());
}
if (isset($changes['shared'])) {
$def->setShared($definition->isShared());
}
@ -174,6 +170,9 @@ class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
// these attributes are always taken from the child
$def->setAbstract($definition->isAbstract());
$def->setTags($definition->getTags());
// autoconfigure is never taken from parent (on purpose)
// and it's not legal on an instanceof
$def->setAutoconfigured($definition->isAutoconfigured());
return $def;
}

View File

@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* Applies instanceof conditionals to definitions.
@ -28,6 +29,15 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getAutoconfiguredInstanceof() as $interface => $definition) {
if ($definition->getArguments()) {
throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines arguments but these are not supported and should be removed.', $interface));
}
if ($definition->getMethodCalls()) {
throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines method calls but these are not supported and should be removed.', $interface));
}
}
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition instanceof ChildDefinition) {
// don't apply "instanceof" to children: it will be applied to their parent
@ -40,8 +50,8 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
private function processDefinition(ContainerBuilder $container, $id, Definition $definition)
{
$instanceofConditionals = $definition->getInstanceofConditionals();
$automaticInstanceofConditionals = $definition->isAutoconfigured() ? $container->getAutomaticInstanceofDefinitions() : array();
if (!$instanceofConditionals && !$automaticInstanceofConditionals) {
$autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : array();
if (!$instanceofConditionals && !$autoconfiguredInstanceof) {
return $definition;
}
@ -49,7 +59,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
return $definition;
}
$conditionals = $this->mergeConditionals($automaticInstanceofConditionals, $instanceofConditionals, $container);
$conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container);
$definition->setInstanceofConditionals(array());
$parent = $shared = null;
@ -113,10 +123,10 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
return $definition;
}
private function mergeConditionals(array $automaticInstanceofConditionals, array $instanceofConditionals, ContainerBuilder $container)
private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container)
{
// make each value an array of ChildDefinition
$conditionals = array_map(function ($childDef) { return array($childDef); }, $automaticInstanceofConditionals);
$conditionals = array_map(function ($childDef) { return array($childDef); }, $autoconfiguredInstanceof);
foreach ($instanceofConditionals as $interface => $instanceofDef) {
// make sure the interface/class exists (but don't validate automaticInstanceofConditionals)
@ -124,7 +134,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
throw new RuntimeException(sprintf('"%s" is set as an "instanceof" conditional, but it does not exist.', $interface));
}
if (!isset($automaticInstanceofConditionals[$interface])) {
if (!isset($autoconfiguredInstanceof[$interface])) {
$conditionals[$interface] = array();
}

View File

@ -118,7 +118,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
private $vendors;
private $automaticInstanceofDefinitions = array();
private $autoconfiguredInstanceof = array();
public function __construct(ParameterBagInterface $parameterBag = null)
{
@ -641,12 +641,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
}
foreach ($container->getAutomaticInstanceofDefinitions() as $interface => $childDefinition) {
if (isset($this->automaticInstanceofDefinitions[$interface])) {
foreach ($container->getAutoconfiguredInstanceof() as $interface => $childDefinition) {
if (isset($this->autoconfiguredInstanceof[$interface])) {
throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface));
}
$this->automaticInstanceofDefinitions[$interface] = $childDefinition;
$this->autoconfiguredInstanceof[$interface] = $childDefinition;
}
}
@ -1272,15 +1272,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* Returns a ChildDefinition that will be used for autoconfiguring the interface/class.
*
* @param string $interface The class or interface to match
*
* @return ChildDefinition
*/
public function registerForAutoconfiguration($interface)
{
if (!isset($this->automaticInstanceofDefinitions[$interface])) {
$this->automaticInstanceofDefinitions[$interface] = new ChildDefinition('');
if (!isset($this->autoconfiguredInstanceof[$interface])) {
$this->autoconfiguredInstanceof[$interface] = new ChildDefinition('');
}
return $this->automaticInstanceofDefinitions[$interface];
return $this->autoconfiguredInstanceof[$interface];
}
/**
@ -1288,9 +1289,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*
* @return ChildDefinition[]
*/
public function getAutomaticInstanceofDefinitions()
public function getAutoconfiguredInstanceof()
{
return $this->automaticInstanceofDefinitions;
return $this->autoconfiguredInstanceof;
}
/**

View File

@ -79,7 +79,7 @@ abstract class FileLoader extends BaseFileLoader
}
$this->instanceof[$id] = $definition;
} else {
$this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof));
$this->container->setDefinition($id, $definition instanceof ChildDefinition ? $definition : $definition->setInstanceofConditionals($this->instanceof));
}
}

View File

@ -215,6 +215,14 @@ class XmlFileLoader extends FileLoader
if ($this->isLoadingInstanceof) {
$definition = new ChildDefinition('');
} elseif ($parent = $service->getAttribute('parent')) {
if (!empty($this->instanceof)) {
throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "instanceof" configuration is defined as using both is not supported. Try moving your child definitions to a different file.', $service->getAttribute('id')));
}
if (!empty($defaults)) {
throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "defaults" configuration is defined as using both is not supported. Try moving your child definitions to a different file.', $service->getAttribute('id')));
}
$definition = new ChildDefinition($parent);
if ($value = $service->getAttribute('inherit-tags')) {
@ -255,7 +263,11 @@ class XmlFileLoader extends FileLoader
}
if ($value = $service->getAttribute('autoconfigure')) {
$definition->setAutoconfigured(XmlUtils::phpize($value));
if (!$definition instanceof ChildDefinition) {
$definition->setAutoconfigured(XmlUtils::phpize($value));
} elseif ($value = XmlUtils::phpize($value)) {
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting autoconfigure="false" for the service.', $service->getAttribute('id')));
}
}
if ($files = $this->getChildren($service, 'file')) {

View File

@ -88,7 +88,6 @@ class YamlFileLoader extends FileLoader
'calls' => 'calls',
'tags' => 'tags',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
);
private static $defaultsKeywords = array(
@ -357,6 +356,14 @@ class YamlFileLoader extends FileLoader
if ($this->isLoadingInstanceof) {
$definition = new ChildDefinition('');
} elseif (isset($service['parent'])) {
if (!empty($this->instanceof)) {
throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "_instanceof" configuration is defined as using both is not supported. Try moving your child definitions to a different file.', $id));
}
if (!empty($defaults)) {
throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "_defaults" configuration is defined as using both is not supported. Try moving your child definitions to a different file.', $id));
}
$definition = new ChildDefinition($service['parent']);
$inheritTag = isset($service['inherit_tags']) ? $service['inherit_tags'] : (isset($defaults['inherit_tags']) ? $defaults['inherit_tags'] : null);
@ -518,7 +525,11 @@ class YamlFileLoader extends FileLoader
}
if (isset($service['autoconfigure'])) {
$definition->setAutoconfigured($service['autoconfigure']);
if (!$definition instanceof ChildDefinition) {
$definition->setAutoconfigured($service['autoconfigure']);
} elseif ($service['autoconfigure']) {
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting "autoconfigure: false" for the service.', $id));
}
}
if (array_key_exists('resource', $service)) {

View File

@ -132,4 +132,22 @@ class ChildDefinitionTest extends TestCase
{
$this->assertInstanceOf(ChildDefinition::class, new DefinitionDecorator('foo'));
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\BadMethodCallException
*/
public function testCannotCallSetAutoconfigured()
{
$def = new ChildDefinition('foo');
$def->setAutoconfigured(true);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\BadMethodCallException
*/
public function testCannotCallSetInstanceofConditionals()
{
$def = new ChildDefinition('foo');
$def->setInstanceofConditionals(array('Foo' => new ChildDefinition('')));
}
}

View File

@ -117,64 +117,86 @@ class IntegrationTest extends TestCase
$this->assertFalse($container->hasDefinition('c'), 'Service C was not inlined.');
}
public function testInstanceofDefaultsAndParentDefinitionResolution()
/**
* @dataProvider getYamlCompileTests
*/
public function testYamlContainerCompiles($directory, $actualServiceId, $expectedServiceId, ContainerBuilder $mainContainer = null)
{
// allow a container to be passed in, which might have autoconfigure settings
$container = $mainContainer ? $mainContainer : new ContainerBuilder();
$container->setResourceTracking(false);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Fixtures/yaml/integration/'.$directory));
$loader->load('main.yml');
$container->compile();
$actualService = $container->getDefinition($actualServiceId);
// create a fresh ContainerBuilder, to avoid autoconfigure stuff
$container = new ContainerBuilder();
$container->setResourceTracking(false);
// loading YAML with an expressive test-case in that file
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Fixtures/yaml'));
$loader->load('services_defaults_instanceof_parent.yml');
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Fixtures/yaml/integration/'.$directory));
$loader->load('expected.yml');
$container->compile();
$expectedService = $container->getDefinition($expectedServiceId);
// instanceof overrides defaults
$simpleService = $container->getDefinition('service_simple');
$this->assertFalse($simpleService->isAutowired());
$this->assertFalse($simpleService->isAutoconfigured());
$this->assertFalse($simpleService->isShared());
// reset changes, we don't care if these differ
$actualService->setChanges(array());
$expectedService->setChanges(array());
// all tags are kept
$this->assertEquals(
array(
'foo_tag' => array(array('tag_option' => 'from_service'), array('tag_option' => 'from_instanceof')),
'bar_tag' => array(array()),
),
$simpleService->getTags()
$this->assertEquals($expectedService, $actualService);
}
public function getYamlCompileTests()
{
$container = new ContainerBuilder();
$container->registerForAutoconfiguration(IntegrationTestStub::class);
yield array(
'autoconfigure_child_not_applied',
'child_service',
'child_service_expected',
$container,
);
// calls are all kept, but service-level calls are last
$this->assertEquals(
array(
// from instanceof
array('setSunshine', array('bright')),
// from service
array('enableSummer', array(true)),
array('setSunshine', array('warm')),
),
$simpleService->getMethodCalls()
$container = new ContainerBuilder();
$container->registerForAutoconfiguration(IntegrationTestStub::class);
yield array(
'autoconfigure_parent_child',
'child_service',
'child_service_expected',
$container,
);
// service override instanceof
$overrideService = $container->getDefinition('service_override_instanceof');
$this->assertTrue($overrideService->isAutowired());
$this->assertTrue($overrideService->isAutoconfigured());
$container = new ContainerBuilder();
$container->registerForAutoconfiguration(IntegrationTestStub::class)
->addTag('from_autoconfigure');
yield array(
'autoconfigure_parent_child_tags',
'child_service',
'child_service_expected',
$container,
);
// children definitions get no instanceof
$childDef = $container->getDefinition('child_service');
$this->assertEmpty($childDef->getTags());
yield array(
'child_parent',
'child_service',
'child_service_expected',
);
$childDef2 = $container->getDefinition('child_service_with_parent_instanceof');
// taken from instanceof applied to parent
$this->assertFalse($childDef2->isAutowired());
// override the instanceof
$this->assertTrue($childDef2->isShared());
// tags inherit like normal
$this->assertEquals(
array(
'foo_tag' => array(array('tag_option' => 'from_child_def'), array('tag_option' => 'from_parent_def'), array('tag_option' => 'from_instanceof')),
'bar_tag' => array(array()),
),
$childDef2->getTags()
yield array(
'defaults_instanceof_importance',
'main_service',
'main_service_expected',
);
yield array(
'defaults_parent_child',
'child_service',
'child_service_expected',
);
yield array(
'instanceof_parent_child',
'child_service',
'child_service_expected',
);
}
}

View File

@ -381,23 +381,6 @@ class ResolveDefinitionTemplatesPassTest extends TestCase
$this->assertSame(array(2, 1, 'foo' => 3), $def->getArguments());
}
public function testSetAutoconfiguredOnServiceHasParent()
{
$container = new ContainerBuilder();
$container->register('parent', 'stdClass')
->setAutoconfigured(true)
;
$container->setDefinition('child1', new ChildDefinition('parent'))
->setAutoconfigured(false)
;
$this->process($container);
$this->assertFalse($container->getDefinition('child1')->isAutoconfigured());
}
public function testSetAutoconfiguredOnServiceIsParent()
{
$container = new ContainerBuilder();
@ -410,7 +393,7 @@ class ResolveDefinitionTemplatesPassTest extends TestCase
$this->process($container);
$this->assertTrue($container->getDefinition('child1')->isAutoconfigured());
$this->assertFalse($container->getDefinition('child1')->isAutoconfigured());
}
protected function process(ContainerBuilder $container)

View File

@ -56,9 +56,6 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
));
$def = (new ChildDefinition('parent'))->setClass(self::class);
$def->setInstanceofConditionals(array(
parent::class => (new ChildDefinition(''))->addMethodCall('foo', array('baz')),
));
$container->setDefinition('child', $def);
(new ResolveInstanceofConditionalsPass())->process($container);
@ -108,7 +105,7 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
$this->assertTrue($def->isShared());
}
public function testProcessUsesAutomaticInstanceofDefinitions()
public function testProcessUsesAutoconfiguredInstanceof()
{
$container = new ContainerBuilder();
$def = $container->register('normal_service', self::class);
@ -119,24 +116,24 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
));
$def->setAutoconfigured(true);
$container->registerForAutoconfiguration(parent::class)
->addTag('automatic_instanceof_tag')
->addTag('autoconfigured_tag')
->setAutowired(true)
->setFactory('automatically_set_factory');
->setFactory('autoconfigured_factory');
(new ResolveInstanceofConditionalsPass())->process($container);
(new ResolveTagsInheritancePass())->process($container);
(new ResolveDefinitionTemplatesPass())->process($container);
$def = $container->getDefinition('normal_service');
// autowired thanks to the automatic instanceof
// autowired thanks to the autoconfigured instanceof
$this->assertTrue($def->isAutowired());
// factory from the specific instanceof overrides global one
$this->assertEquals('locally_set_factory', $def->getFactory());
// tags are merged, the locally set one is first
$this->assertSame(array('local_instanceof_tag' => array(array()), 'automatic_instanceof_tag' => array(array())), $def->getTags());
$this->assertSame(array('local_instanceof_tag' => array(array()), 'autoconfigured_tag' => array(array())), $def->getTags());
}
public function testProcessDoesNotUseAutomaticInstanceofDefinitionsIfNotEnabled()
public function testProcessDoesNotUseAutoconfiguredInstanceofIfNotEnabled()
{
$container = new ContainerBuilder();
$def = $container->register('normal_service', self::class);
@ -151,7 +148,6 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
(new ResolveDefinitionTemplatesPass())->process($container);
$def = $container->getDefinition('normal_service');
// no automatic_tag, it was not enabled on the Definition
$this->assertFalse($def->isAutowired());
}
@ -171,7 +167,7 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
(new ResolveInstanceofConditionalsPass())->process($container);
}
public function testBadInterfaceForAutomaticInstanceofIsOkException()
public function testBadInterfaceForAutomaticInstanceofIsOk()
{
$container = new ContainerBuilder();
$container->register('normal_service', self::class)
@ -182,4 +178,30 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
(new ResolveInstanceofConditionalsPass())->process($container);
$this->assertTrue($container->hasDefinition('normal_service'));
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Autoconfigured instanceof for type "PHPUnit\Framework\TestCase" defines method calls but these are not supported and should be removed.
*/
public function testProcessThrowsExceptionForAutoconfiguredCalls()
{
$container = new ContainerBuilder();
$container->registerForAutoconfiguration(parent::class)
->addMethodCall('setFoo');
(new ResolveInstanceofConditionalsPass())->process($container);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Autoconfigured instanceof for type "PHPUnit\Framework\TestCase" defines arguments but these are not supported and should be removed.
*/
public function testProcessThrowsExceptionForArguments()
{
$container = new ContainerBuilder();
$container->registerForAutoconfiguration(parent::class)
->addArgument('bar');
(new ResolveInstanceofConditionalsPass())->process($container);
}
}

View File

@ -573,7 +573,7 @@ class ContainerBuilderTest extends TestCase
$childDefA = $container->registerForAutoconfiguration('AInterface');
$childDefB = $config->registerForAutoconfiguration('BInterface');
$container->merge($config);
$this->assertSame(array('AInterface' => $childDefA, 'BInterface' => $childDefB), $container->getAutomaticInstanceofDefinitions());
$this->assertSame(array('AInterface' => $childDefA, 'BInterface' => $childDefB), $container->getAutoconfiguredInstanceof());
}
/**
@ -1123,7 +1123,7 @@ class ContainerBuilderTest extends TestCase
$container = new ContainerBuilder();
$childDefA = $container->registerForAutoconfiguration('AInterface');
$childDefB = $container->registerForAutoconfiguration('BInterface');
$this->assertSame(array('AInterface' => $childDefA, 'BInterface' => $childDefB), $container->getAutomaticInstanceofDefinitions());
$this->assertSame(array('AInterface' => $childDefA, 'BInterface' => $childDefB), $container->getAutoconfiguredInstanceof());
// when called multiple times, the same instance is returned
$this->assertSame($childDefA, $container->registerForAutoconfiguration('AInterface'));

View File

@ -8,11 +8,5 @@
<service id="with_defaults" class="Foo" />
<service id="no_defaults" class="Foo" public="true" autowire="false" inherit-tags="false">
</service>
<service id="no_defaults_child" class="Foo" parent="no_defaults">
<tag name="bar" />
</service>
<service id="with_defaults_child" class="Foo" parent="with_defaults" public="true" inherit-tags="true">
<tag name="baz" />
</service>
</services>
</container>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="parent_service" class="stdClass" />
<service id="child_service" class="stdClass" parent="parent_service" autoconfigure="true" />
</services>
</container>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="true" />
<service id="parent_service" class="stdClass" />
<service id="child_service" parent="parent_service" />
</services>
</container>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<instanceof id="FooInterface" autowire="true" />
<service id="parent_service" class="stdClass" />
<service id="child_service" class="stdClass" parent="parent_service" />
</services>
</container>

View File

@ -0,0 +1,4 @@
services:
child_service:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
parent: parent_service

View File

@ -0,0 +1,10 @@
services:
child_service_expected:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
# the parent has autoconfigure true, but that does not cascade to the child
autoconfigure: false
# an autoconfigured "instanceof" is setup for IntegrationTestStub
# but its calls are NOT added, because the class was only
# set on the parent, not the child
#calls:
# - [enableSummer, [true]]

View File

@ -0,0 +1,7 @@
imports:
- { resource: _child.yml }
services:
parent_service:
autoconfigure: true
abstract: true

View File

@ -0,0 +1,3 @@
services:
child_service:
parent: parent_service

View File

@ -0,0 +1,5 @@
services:
child_service_expected:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
# autoconfigure is set on the parent, but not on the child
autoconfigure: false

View File

@ -0,0 +1,7 @@
imports:
- { resource: _child.yml }
services:
parent_service:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
autoconfigure: true

View File

@ -0,0 +1,3 @@
services:
child_service:
parent: parent_service

View File

@ -0,0 +1,6 @@
services:
child_service_expected:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
# from an autoconfigured "instanceof" applied to parent class
# but NOT inherited down to child
# tags: [from_autoconfigure]

View File

@ -0,0 +1,7 @@
imports:
- { resource: _child.yml }
services:
parent_service:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
autoconfigure: true

View File

@ -0,0 +1,9 @@
services:
# child_service in the end should be identical to this
child_service_expected:
class: stdClass
autowire: false
public: true
lazy: true
# ONLY the child tag, the parent tag does not inherit
tags: [from_child]

View File

@ -0,0 +1,13 @@
services:
parent_service:
abstract: true
lazy: true
autowire: false
public: false
tags: [from_parent]
child_service:
class: stdClass
parent: parent_service
public: true
tags: [from_child]

View File

@ -0,0 +1,27 @@
services:
# main_service should look like this in the end
main_service_expected:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
# _instanceof overrides _defaults
autowire: false
# inherited from _defaults
autoconfigure: true
# from _instanceof
shared: false
# service definition overrides _instanceof
public: true
tags:
- { name: foo_tag, tag_option: from_service }
# these 2 are from instanceof
- { name: foo_tag, tag_option: from_instanceof }
- { name: bar_tag }
# the tag from defaults do NOT cascade (but see #22530)
# - { name: from_defaults }
# calls from instanceof are kept, but this comes later
calls:
# first call is from instanceof
- [setSunshine, [bright]]
#
- [enableSummer, [true]]
- [setSunshine, [warm]]

View File

@ -0,0 +1,30 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: true
tags: [from_defaults]
_instanceof:
Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStubParent:
autowire: false
shared: false
public: false
tags:
- { name: foo_tag, tag_option: from_instanceof }
calls:
- [setSunshine, [bright]]
Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub:
tags:
- { name: bar_tag }
main_service:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
public: true
tags:
- { name: foo_tag, tag_option: from_service }
# calls from instanceof are kept, but this comes later
calls:
- [enableSummer, [true]]
- [setSunshine, [warm]]

View File

@ -0,0 +1,4 @@
services:
# loaded here to avoid defaults in other file
child_service:
parent: parent_service

View File

@ -0,0 +1,6 @@
services:
child_service_expected:
class: stdClass
# _defaults is applied to the parent, but autoconfigure: true
# does not cascade to the child
autoconfigure: false

View File

@ -0,0 +1,9 @@
imports:
- { resource: _child.yml }
services:
_defaults:
autoconfigure: true
parent_service:
class: stdClass

View File

@ -0,0 +1,4 @@
services:
# loaded here to avoid defaults in other file
child_service:
parent: parent_service

View File

@ -0,0 +1,7 @@
services:
child_service_expected:
class: stdClass
# applied to _instanceof of parent
autowire: true
# from _instanceof, applies to parent, but does NOT inherit to here
# tags: [from_instanceof]

View File

@ -0,0 +1,11 @@
imports:
- { resource: _child.yml }
services:
_instanceof:
stdClass:
autowire: true
tags: [from_instanceof]
parent_service:
class: stdClass

View File

@ -22,19 +22,6 @@ services:
autowire: false
tags: []
no_defaults_child:
parent: no_defaults
autowire: ~
tags:
- name: bar
with_defaults_child:
parent: with_defaults
public: true
inherit_tags: true
tags:
- name: baz
with_defaults_aliased:
alias: with_defaults

View File

@ -0,0 +1,8 @@
services:
parent_service:
class: stdClass
child_service:
class: stdClass
autoconfigure: true
parent: parent_service

View File

@ -1,57 +0,0 @@
services:
_defaults:
autowire: true
autoconfigure: true
_instanceof:
Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStubParent:
# should override _defaults
autowire: false
autoconfigure: false
shared: false
tags:
- { name: foo_tag, tag_option: from_instanceof }
calls:
- [setSunshine, [bright]]
# a second instanceof that will be applied
Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub:
tags:
- { name: bar_tag }
service_simple:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
tags:
- { name: foo_tag, tag_option: from_service }
# calls from instanceof are kept, but this comes later
calls:
- [enableSummer, [true]]
- [setSunshine, [warm]]
service_override_instanceof:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
# override instanceof
autowire: true
autoconfigure: true
parent_service:
abstract: true
lazy: true
# instanceof will not be applied to this
child_service:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
parent: parent_service
parent_service_with_class:
abstract: true
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
tags:
- { name: foo_tag, tag_option: from_parent_def }
child_service_with_parent_instanceof:
parent: parent_service_with_class
shared: true
inherit_tags: true
tags:
- { name: foo_tag, tag_option: from_child_def }

View File

@ -0,0 +1,10 @@
services:
_defaults:
autowire: true
parent_service:
class: stdClass
child_service:
class: stdClass
parent: parent_service

View File

@ -0,0 +1,11 @@
services:
_instanceof:
FooInterface:
autowire: true
parent_service:
class: stdClass
child_service:
class: stdClass
parent: parent_service

View File

@ -651,20 +651,13 @@ class XmlFileLoaderTest extends TestCase
$this->assertArrayNotHasKey('public', $container->getDefinition('with_defaults')->getChanges());
$this->assertArrayNotHasKey('autowire', $container->getDefinition('with_defaults')->getChanges());
$this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges());
$this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges());
$container->compile();
$this->assertTrue($container->getDefinition('no_defaults')->isPublic());
$this->assertTrue($container->getDefinition('no_defaults_child')->isPublic());
$this->assertSame(array(), $container->getDefinition('no_defaults')->getTags());
$this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults_child')->getTags());
$this->assertSame(array('baz' => array(array()), 'foo' => array(array())), $container->getDefinition('with_defaults_child')->getTags());
$this->assertFalse($container->getDefinition('no_defaults')->isAutowired());
$this->assertFalse($container->getDefinition('no_defaults_child')->isAutowired());
}
public function testNamedArguments()
@ -694,6 +687,42 @@ class XmlFileLoaderTest extends TestCase
$this->assertSame(array('foo' => array(array()), 'bar' => array(array())), $definition->getTags());
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage The service "child_service" cannot use the "parent" option in the same file where "instanceof" configuration is defined as using both is not supported. Try moving your child definitions to a different file.
*/
public function testInstanceOfAndChildDefinitionNotAllowed()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_instanceof_with_parent.xml');
$container->compile();
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage The service "child_service" cannot have a "parent" and also have "autoconfigure". Try setting autoconfigure="false" for the service.
*/
public function testAutoConfigureAndChildDefinitionNotAllowed()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_autoconfigure_with_parent.xml');
$container->compile();
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage The service "child_service" cannot use the "parent" option in the same file where "defaults" configuration is defined as using both is not supported. Try moving your child definitions to a different file.
*/
public function testDefaultsAndChildDefinitionNotAllowed()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_defaults_with_parent.xml');
$container->compile();
}
public function testAutoConfigureInstanceof()
{
$container = new ContainerBuilder();

View File

@ -403,9 +403,6 @@ class YamlFileLoaderTest extends TestCase
$this->assertFalse($container->getAlias('with_defaults_aliased')->isPublic());
$this->assertFalse($container->getAlias('with_defaults_aliased_short')->isPublic());
$this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges());
$this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges());
$this->assertFalse($container->getDefinition('Acme\WithShortCutArgs')->isPublic());
$this->assertSame(array('foo' => array(array())), $container->getDefinition('Acme\WithShortCutArgs')->getTags());
$this->assertTrue($container->getDefinition('Acme\WithShortCutArgs')->isAutowired());
@ -414,16 +411,12 @@ class YamlFileLoaderTest extends TestCase
$this->assertTrue($container->getDefinition('with_null')->isPublic());
$this->assertTrue($container->getDefinition('no_defaults')->isPublic());
$this->assertTrue($container->getDefinition('no_defaults_child')->isPublic());
$this->assertSame(array(), $container->getDefinition('with_null')->getTags());
$this->assertSame(array(), $container->getDefinition('no_defaults')->getTags());
$this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults_child')->getTags());
$this->assertSame(array('baz' => array(array()), 'foo' => array(array())), $container->getDefinition('with_defaults_child')->getTags());
$this->assertTrue($container->getDefinition('with_null')->isAutowired());
$this->assertFalse($container->getDefinition('no_defaults')->isAutowired());
$this->assertFalse($container->getDefinition('no_defaults_child')->isAutowired());
}
public function testNamedArguments()
@ -455,6 +448,42 @@ class YamlFileLoaderTest extends TestCase
$this->assertSame(array('foo' => array(array()), 'bar' => array(array())), $definition->getTags());
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage The service "child_service" cannot use the "parent" option in the same file where "_instanceof" configuration is defined as using both is not supported. Try moving your child definitions to a different file.
*/
public function testInstanceOfAndChildDefinitionNotAllowed()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('services_instanceof_with_parent.yml');
$container->compile();
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage The service "child_service" cannot have a "parent" and also have "autoconfigure". Try setting "autoconfigure: false" for the service.
*/
public function testAutoConfigureAndChildDefinitionNotAllowed()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('services_autoconfigure_with_parent.yml');
$container->compile();
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage The service "child_service" cannot use the "parent" option in the same file where "_defaults" configuration is defined as using both is not supported. Try moving your child definitions to a different file.
*/
public function testDefaultsAndChildDefinitionNotAllowed()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('services_defaults_with_parent.yml');
$container->compile();
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage The value of the "decorates" option for the "bar" service must be the id of the service without the "@" prefix (replace "@foo" with "foo").