Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 89979ca

Browse files
committed
feature #22563 Not allowing autoconfigure, instanceofConditionals or defaults for ChildDefinition (weaverryan)
This PR was merged into the 3.3-dev branch. Discussion ---------- Not allowing autoconfigure, instanceofConditionals or defaults for ChildDefinition | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes (removing risky behavior) | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | see #22530 | License | MIT | Doc PR | n/a This PR *prohibits* using `autoconfigure`, `_instanceof` and `_defaults` for ChildDefinition. Additionally, I added many "integration" test cases: we need to test and prove all edge cases. These are in the `integration/` directory: the `main.yml` file is parsed and compared to `expected.yml`. Both are in YAML to ease comparing the before/after. We need to check these out and make sure they're right and we're not missing anything else. This PR removes MANY of the "wtf" cases, but there are still 4 that I know of... and of course they all deal with parent-child stuff :). A) [MAJOR] [autoconfigure_parent_child_tags](https://github.com/symfony/symfony/pull/22563/files#diff-fd6cf15470c5abd40156e4e7dc4e7f6d) `instanceof` tags from autoconfigure are NEVER applied to the child (you can't set `autoconfigure` directly on a Child, but you still can set it on a parent and inherit it... sneaky). We could throw an Exception I suppose to prevent this `autoconfigure` from cascading from parent to child... but it's tricky due to `instanceof`. B( [MAJOR] [instanceof_parent_child](https://github.com/symfony/symfony/pull/22563/files#diff-14666e9a25322d44b3c2c583b6814dc2) `instanceof` tags that are applied to the parent, are not applied to the child. Again, you can't set `instanceof` directly on a Child, but you *can* set it on a parent, and have that cascade to the child. Like before, we could maybe throw an exception to prevent this. C) [MINOR] ([autoconfigure_child_not_applied](https://github.com/symfony/symfony/pull/22563/files#diff-3372a1dcaf3af30d14a7d0a6c8bfa988)) automatic `instanceof` will not be applied to the child when the parent class has a different (non-instanceof-ed) class. If we could throw an exception for (A), then it would cover this too. D) `_tags` from defaults are never used (unless you have inherit_tags) - fixed in #22530 A, B & C are effectively caused by there being a "sneaky" way to re-enable `autoconfigure` and `instanceof` for ChildDefinition... which opens up wtf cases. ## Wait, why not support `_defaults`, `autoconfigure` and `_instanceof` for child definitions? 1 big reason: reduction of wtf moments where we arbitrarily decide override logic. PLUS, since `_defaults`, `instanceof` and `autoconfigure` *are* applied to parent definitions, in practice (other than tags), this makes no difference: the configuration will still pass from parent down to child. Also, using parent-child definitions is already an edge case, and this *simply* prevents *just* those services from using the new features. ## Longer reasons why The reason behind this is that parent-child definitions are a different mechanism for "inheritance" than `_instanceof` and `_defaults`... creating some edge cases when trying to figure out which settings "win". For example: ```yml # file1.yml services: _defaults: public: false ChildService: parent: parent_service # file2.yml services: _defaults: public: true ParentService: ~ ``` Is `ChildDefinition` `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 ParentService is explicitly set to `public: true`, should that override the `public: false` of ChildService (which it got from its `_defaults`)? On one hand, ParentService is being explicitly set. On the other hand, ChildService is explicitly in a file settings `_defaults` `public: false` There's no correct answer. There are also problems with `_instanceof`. The importance goes: > defaults < instanceof < service definition But how do 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. Commits ------- a943b96 Not allowing autoconfigure, instanceofConditionals or defaults for ChildDefinition
2 parents 8806628 + a943b96 commit 89979ca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+496
-193
lines changed

src/Symfony/Component/DependencyInjection/ChildDefinition.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection;
1313

14+
use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
1415
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1516
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
1617

@@ -134,6 +135,22 @@ public function replaceArgument($index, $value)
134135

135136
return $this;
136137
}
138+
139+
/**
140+
* @internal
141+
*/
142+
public function setAutoconfigured($autoconfigured)
143+
{
144+
throw new BadMethodCallException('A ChildDefinition cannot be autoconfigured.');
145+
}
146+
147+
/**
148+
* @internal
149+
*/
150+
public function setInstanceofConditionals(array $instanceof)
151+
{
152+
throw new BadMethodCallException('A ChildDefinition cannot have instanceof conditionals set on it.');
153+
}
137154
}
138155

139156
class_alias(ChildDefinition::class, DefinitionDecorator::class);

src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ private function doResolveDefinition(ChildDefinition $definition)
101101
$def->setPublic($parentDef->isPublic());
102102
$def->setLazy($parentDef->isLazy());
103103
$def->setAutowired($parentDef->isAutowired());
104-
$def->setAutoconfigured($parentDef->isAutoconfigured());
105104
$def->setChanges($parentDef->getChanges());
106105

107106
// overwrite with values specified in the decorator
@@ -130,9 +129,6 @@ private function doResolveDefinition(ChildDefinition $definition)
130129
if (isset($changes['autowired'])) {
131130
$def->setAutowired($definition->isAutowired());
132131
}
133-
if (isset($changes['autoconfigured'])) {
134-
$def->setAutoconfigured($definition->isAutoconfigured());
135-
}
136132
if (isset($changes['shared'])) {
137133
$def->setShared($definition->isShared());
138134
}
@@ -174,6 +170,9 @@ private function doResolveDefinition(ChildDefinition $definition)
174170
// these attributes are always taken from the child
175171
$def->setAbstract($definition->isAbstract());
176172
$def->setTags($definition->getTags());
173+
// autoconfigure is never taken from parent (on purpose)
174+
// and it's not legal on an instanceof
175+
$def->setAutoconfigured($definition->isAutoconfigured());
177176

178177
return $def;
179178
}

src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\DependencyInjection\Definition;
1717
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
18+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1819

1920
/**
2021
* Applies instanceof conditionals to definitions.
@@ -28,6 +29,15 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
2829
*/
2930
public function process(ContainerBuilder $container)
3031
{
32+
foreach ($container->getAutoconfiguredInstanceof() as $interface => $definition) {
33+
if ($definition->getArguments()) {
34+
throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines arguments but these are not supported and should be removed.', $interface));
35+
}
36+
if ($definition->getMethodCalls()) {
37+
throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines method calls but these are not supported and should be removed.', $interface));
38+
}
39+
}
40+
3141
foreach ($container->getDefinitions() as $id => $definition) {
3242
if ($definition instanceof ChildDefinition) {
3343
// don't apply "instanceof" to children: it will be applied to their parent
@@ -40,16 +50,16 @@ public function process(ContainerBuilder $container)
4050
private function processDefinition(ContainerBuilder $container, $id, Definition $definition)
4151
{
4252
$instanceofConditionals = $definition->getInstanceofConditionals();
43-
$automaticInstanceofConditionals = $definition->isAutoconfigured() ? $container->getAutomaticInstanceofDefinitions() : array();
44-
if (!$instanceofConditionals && !$automaticInstanceofConditionals) {
53+
$autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : array();
54+
if (!$instanceofConditionals && !$autoconfiguredInstanceof) {
4555
return $definition;
4656
}
4757

4858
if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
4959
return $definition;
5060
}
5161

52-
$conditionals = $this->mergeConditionals($automaticInstanceofConditionals, $instanceofConditionals, $container);
62+
$conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container);
5363

5464
$definition->setInstanceofConditionals(array());
5565
$parent = $shared = null;
@@ -113,18 +123,18 @@ private function processDefinition(ContainerBuilder $container, $id, Definition
113123
return $definition;
114124
}
115125

116-
private function mergeConditionals(array $automaticInstanceofConditionals, array $instanceofConditionals, ContainerBuilder $container)
126+
private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container)
117127
{
118128
// make each value an array of ChildDefinition
119-
$conditionals = array_map(function ($childDef) { return array($childDef); }, $automaticInstanceofConditionals);
129+
$conditionals = array_map(function ($childDef) { return array($childDef); }, $autoconfiguredInstanceof);
120130

121131
foreach ($instanceofConditionals as $interface => $instanceofDef) {
122132
// make sure the interface/class exists (but don't validate automaticInstanceofConditionals)
123133
if (!$container->getReflectionClass($interface)) {
124134
throw new RuntimeException(sprintf('"%s" is set as an "instanceof" conditional, but it does not exist.', $interface));
125135
}
126136

127-
if (!isset($automaticInstanceofConditionals[$interface])) {
137+
if (!isset($autoconfiguredInstanceof[$interface])) {
128138
$conditionals[$interface] = array();
129139
}
130140

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
118118
*/
119119
private $vendors;
120120

121-
private $automaticInstanceofDefinitions = array();
121+
private $autoconfiguredInstanceof = array();
122122

123123
public function __construct(ParameterBagInterface $parameterBag = null)
124124
{
@@ -641,12 +641,12 @@ public function merge(ContainerBuilder $container)
641641
}
642642
}
643643

644-
foreach ($container->getAutomaticInstanceofDefinitions() as $interface => $childDefinition) {
645-
if (isset($this->automaticInstanceofDefinitions[$interface])) {
644+
foreach ($container->getAutoconfiguredInstanceof() as $interface => $childDefinition) {
645+
if (isset($this->autoconfiguredInstanceof[$interface])) {
646646
throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface));
647647
}
648648

649-
$this->automaticInstanceofDefinitions[$interface] = $childDefinition;
649+
$this->autoconfiguredInstanceof[$interface] = $childDefinition;
650650
}
651651
}
652652

@@ -1272,25 +1272,26 @@ public function getExpressionLanguageProviders()
12721272
* Returns a ChildDefinition that will be used for autoconfiguring the interface/class.
12731273
*
12741274
* @param string $interface The class or interface to match
1275+
*
12751276
* @return ChildDefinition
12761277
*/
12771278
public function registerForAutoconfiguration($interface)
12781279
{
1279-
if (!isset($this->automaticInstanceofDefinitions[$interface])) {
1280-
$this->automaticInstanceofDefinitions[$interface] = new ChildDefinition('');
1280+
if (!isset($this->autoconfiguredInstanceof[$interface])) {
1281+
$this->autoconfiguredInstanceof[$interface] = new ChildDefinition('');
12811282
}
12821283

1283-
return $this->automaticInstanceofDefinitions[$interface];
1284+
return $this->autoconfiguredInstanceof[$interface];
12841285
}
12851286

12861287
/**
12871288
* Returns an array of ChildDefinition[] keyed by interface.
12881289
*
12891290
* @return ChildDefinition[]
12901291
*/
1291-
public function getAutomaticInstanceofDefinitions()
1292+
public function getAutoconfiguredInstanceof()
12921293
{
1293-
return $this->automaticInstanceofDefinitions;
1294+
return $this->autoconfiguredInstanceof;
12941295
}
12951296

12961297
/**

src/Symfony/Component/DependencyInjection/Loader/FileLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ protected function setDefinition($id, Definition $definition)
7979
}
8080
$this->instanceof[$id] = $definition;
8181
} else {
82-
$this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof));
82+
$this->container->setDefinition($id, $definition instanceof ChildDefinition ? $definition : $definition->setInstanceofConditionals($this->instanceof));
8383
}
8484
}
8585

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,14 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
215215
if ($this->isLoadingInstanceof) {
216216
$definition = new ChildDefinition('');
217217
} elseif ($parent = $service->getAttribute('parent')) {
218+
if (!empty($this->instanceof)) {
219+
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')));
220+
}
221+
222+
if (!empty($defaults)) {
223+
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')));
224+
}
225+
218226
$definition = new ChildDefinition($parent);
219227

220228
if ($value = $service->getAttribute('inherit-tags')) {
@@ -255,7 +263,11 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
255263
}
256264

257265
if ($value = $service->getAttribute('autoconfigure')) {
258-
$definition->setAutoconfigured(XmlUtils::phpize($value));
266+
if (!$definition instanceof ChildDefinition) {
267+
$definition->setAutoconfigured(XmlUtils::phpize($value));
268+
} elseif ($value = XmlUtils::phpize($value)) {
269+
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')));
270+
}
259271
}
260272

261273
if ($files = $this->getChildren($service, 'file')) {

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ class YamlFileLoader extends FileLoader
8888
'calls' => 'calls',
8989
'tags' => 'tags',
9090
'autowire' => 'autowire',
91-
'autoconfigure' => 'autoconfigure',
9291
);
9392

9493
private static $defaultsKeywords = array(
@@ -357,6 +356,14 @@ private function parseDefinition($id, $service, $file, array $defaults)
357356
if ($this->isLoadingInstanceof) {
358357
$definition = new ChildDefinition('');
359358
} elseif (isset($service['parent'])) {
359+
if (!empty($this->instanceof)) {
360+
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));
361+
}
362+
363+
if (!empty($defaults)) {
364+
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));
365+
}
366+
360367
$definition = new ChildDefinition($service['parent']);
361368

362369
$inheritTag = isset($service['inherit_tags']) ? $service['inherit_tags'] : (isset($defaults['inherit_tags']) ? $defaults['inherit_tags'] : null);
@@ -518,7 +525,11 @@ private function parseDefinition($id, $service, $file, array $defaults)
518525
}
519526

520527
if (isset($service['autoconfigure'])) {
521-
$definition->setAutoconfigured($service['autoconfigure']);
528+
if (!$definition instanceof ChildDefinition) {
529+
$definition->setAutoconfigured($service['autoconfigure']);
530+
} elseif ($service['autoconfigure']) {
531+
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting "autoconfigure: false" for the service.', $id));
532+
}
522533
}
523534

524535
if (array_key_exists('resource', $service)) {

src/Symfony/Component/DependencyInjection/Tests/ChildDefinitionTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,22 @@ public function testDefinitionDecoratorAliasExistsForBackwardsCompatibility()
132132
{
133133
$this->assertInstanceOf(ChildDefinition::class, new DefinitionDecorator('foo'));
134134
}
135+
136+
/**
137+
* @expectedException \Symfony\Component\DependencyInjection\Exception\BadMethodCallException
138+
*/
139+
public function testCannotCallSetAutoconfigured()
140+
{
141+
$def = new ChildDefinition('foo');
142+
$def->setAutoconfigured(true);
143+
}
144+
145+
/**
146+
* @expectedException \Symfony\Component\DependencyInjection\Exception\BadMethodCallException
147+
*/
148+
public function testCannotCallSetInstanceofConditionals()
149+
{
150+
$def = new ChildDefinition('foo');
151+
$def->setInstanceofConditionals(array('Foo' => new ChildDefinition('')));
152+
}
135153
}

0 commit comments

Comments
 (0)