-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Rework config hierarchy: defaults > instanceof > service config #22294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8698228
db96c89
24a6a83
6202909
28d7d8d
de0f718
8fd3738
5e39c5d
132201e
d7c4836
a44d92b
a2aea3b
5f9ca7c
2021675
a95de6c
34ad21a
371fec2
82fa3c0
1f2ce11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,6 @@ | |
|
||
namespace Symfony\Component\DependencyInjection\Compiler; | ||
|
||
use Symfony\Component\DependencyInjection\ChildDefinition; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
/** | ||
|
@@ -27,7 +26,7 @@ protected function processValue($value, $isRoot = false) | |
return parent::processValue($value, $isRoot); | ||
} | ||
|
||
$class = $value instanceof ChildDefinition ? $this->resolveDefinition($value) : $value->getClass(); | ||
$class = $value->getClass(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This entire class/file could/should be renamed ( |
||
|
||
if (!$class || false !== strpos($class, '%') || !$instanceof = $value->getInstanceofConditionals()) { | ||
return parent::processValue($value, $isRoot); | ||
|
@@ -39,55 +38,56 @@ protected function processValue($value, $isRoot = false) | |
continue; | ||
} | ||
if ($interface === $class || is_subclass_of($class, $interface)) { | ||
$this->mergeDefinition($value, $definition); | ||
$this->mergeInstanceofDefinition($value, $definition); | ||
} | ||
} | ||
|
||
return parent::processValue($value, $isRoot); | ||
} | ||
|
||
/** | ||
* Populates the class and tags from parent definitions. | ||
*/ | ||
private function resolveDefinition(ChildDefinition $definition) | ||
private function mergeInstanceofDefinition(Definition $def, Definition $instanceofDefinition) | ||
{ | ||
if (!$this->container->has($parent = $definition->getParent())) { | ||
return; | ||
$configured = $def->getChanges(); | ||
$changes = $instanceofDefinition->getChanges(); | ||
if (!isset($configured['shared']) && isset($changes['shared'])) { | ||
$def->setShared($instanceofDefinition->isShared()); | ||
} | ||
|
||
$parentDef = $this->container->findDefinition($parent); | ||
$class = $parentDef instanceof ChildDefinition ? $this->resolveDefinition($parentDef) : $parentDef->getClass(); | ||
$class = $definition->getClass() ?: $class; | ||
|
||
// append parent tags when inheriting is enabled | ||
if ($definition->getInheritTags()) { | ||
$definition->setInheritTags(false); | ||
|
||
foreach ($parentDef->getTags() as $k => $v) { | ||
foreach ($v as $v) { | ||
$definition->addTag($k, $v); | ||
} | ||
} | ||
if (!isset($configured['configurator']) && isset($changes['configurator'])) { | ||
$def->setConfigurator($instanceofDefinition->getConfigurator()); | ||
} | ||
|
||
return $class; | ||
} | ||
|
||
private function mergeDefinition(Definition $def, ChildDefinition $definition) | ||
{ | ||
$changes = $definition->getChanges(); | ||
if (isset($changes['shared'])) { | ||
$def->setShared($definition->isShared()); | ||
if (!isset($configured['public']) && isset($changes['public'])) { | ||
$def->setPublic($instanceofDefinition->isPublic()); | ||
} | ||
if (isset($changes['abstract'])) { | ||
$def->setAbstract($definition->isAbstract()); | ||
if (!isset($configured['lazy']) && isset($changes['lazy'])) { | ||
$def->setLazy($instanceofDefinition->isLazy()); | ||
} | ||
if (!isset($configured['autowired']) && isset($changes['autowired'])) { | ||
$def->setAutowired($instanceofDefinition->isAutowired()); | ||
} | ||
// merge properties | ||
$properties = $def->getProperties(); | ||
foreach ($instanceofDefinition->getProperties() as $k => $v) { | ||
// don't override properties set explicitly on the service | ||
if (!array_key_exists($k, $properties)) { | ||
$def->setProperty($k, $v); | ||
} | ||
} | ||
// merge method calls | ||
if ($instanceofCalls = $instanceofDefinition->getMethodCalls()) { | ||
$currentCallMethods = array_map(function ($call) { | ||
return strtolower($call[0]); | ||
}, $def->getMethodCalls()); | ||
|
||
$uniqueInstanceofCalls = array_filter($instanceofCalls, function ($instanceofCall) use ($currentCallMethods) { | ||
// don't add an instanceof call if it was overridden on the service | ||
return !in_array(strtolower($instanceofCall[0]), $currentCallMethods); | ||
}); | ||
|
||
$def->setMethodCalls(array_merge($uniqueInstanceofCalls, $def->getMethodCalls())); | ||
} | ||
|
||
ResolveDefinitionTemplatesPass::mergeDefinition($def, $definition); | ||
|
||
// prepend instanceof tags | ||
$tailTags = $def->getTags(); | ||
if ($headTags = $definition->getTags()) { | ||
if ($headTags = $instanceofDefinition->getTags()) { | ||
$def->setTags($headTags); | ||
foreach ($tailTags as $k => $v) { | ||
foreach ($v as $v) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,33 +95,27 @@ private function doResolveDefinition(ChildDefinition $definition) | |
if ($parentDef->isDeprecated()) { | ||
$def->setDeprecated(true, $parentDef->getDeprecationMessage('%service_id%')); | ||
} | ||
$def->setFactory($parentDef->getFactory()); | ||
$def->setConfigurator($parentDef->getConfigurator()); | ||
$def->setFile($parentDef->getFile()); | ||
$def->setPublic($parentDef->isPublic()); | ||
$def->setLazy($parentDef->isLazy()); | ||
$def->setAutowired($parentDef->isAutowired()); | ||
|
||
self::mergeDefinition($def, $definition); | ||
|
||
// merge autowiring types | ||
foreach ($definition->getAutowiringTypes(false) as $autowiringType) { | ||
$def->addAutowiringType($autowiringType); | ||
$parentChanges = $parentDef->getChanges(); | ||
if (isset($parentChanges['factory'])) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see one fragility here. services:
_defaults:
autowire: true
parent: ~ services:
Foo\Bar:
parent: parent Then I think the defaults should always be tracked but we should allow to differentiate changes from defaults (maybe just internally, I see no need to do this in userland): if (isset($defaults['autowire'])) {
$definition->setTrackChanges('defaults')
->setAutowired($defaults['autowire'])
->setTrackChanges(true);
} and then use something like the following in the passes needing it: $definition->getChanges($withoutDefaults = true); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could also fix #22345 using this by tracking the detected class name differently (or maybe even by not tracking it). |
||
$def->setFactory($parentDef->getFactory()); | ||
} | ||
if (isset($parentChanges['configurator'])) { | ||
$def->setConfigurator($parentDef->getConfigurator()); | ||
} | ||
if (isset($parentChanges['file'])) { | ||
$def->setFile($parentDef->getFile()); | ||
} | ||
if (isset($parentChanges['public'])) { | ||
$def->setPublic($parentDef->isPublic()); | ||
} | ||
if (isset($parentChanges['lazy'])) { | ||
$def->setLazy($parentDef->isLazy()); | ||
} | ||
if (isset($parentChanges['autowired'])) { | ||
$def->setAutowired($parentDef->isAutowired()); | ||
} | ||
|
||
// these attributes are always taken from the child | ||
$def->setAbstract($definition->isAbstract()); | ||
$def->setShared($definition->isShared()); | ||
$def->setTags($definition->getTags()); | ||
|
||
return $def; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public static function mergeDefinition(Definition $def, ChildDefinition $definition) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class was simply changed to look like it did before #21530 (adjusted for changes since then) |
||
// overwrite with values specified in the decorator | ||
$changes = $definition->getChanges(); | ||
if (isset($changes['class'])) { | ||
|
@@ -182,5 +176,31 @@ public static function mergeDefinition(Definition $def, ChildDefinition $definit | |
if ($calls = $definition->getMethodCalls()) { | ||
$def->setMethodCalls(array_merge($def->getMethodCalls(), $calls)); | ||
} | ||
|
||
// merge autowiring types | ||
foreach ($definition->getAutowiringTypes(false) as $autowiringType) { | ||
$def->addAutowiringType($autowiringType); | ||
} | ||
|
||
// these attributes are always taken from the child | ||
if (isset($changes['abstract'])) { | ||
$def->setAbstract($definition->isAbstract()); | ||
} | ||
if (isset($changes['shared'])) { | ||
$def->setShared($definition->isShared()); | ||
} | ||
$def->setTags($definition->getTags()); | ||
$def->setInstanceofConditionals($definition->getInstanceofConditionals()); | ||
|
||
// append parent tags when inheriting is enabled | ||
if ($definition->getInheritTags()) { | ||
foreach ($parentDef->getTags() as $k => $v) { | ||
foreach ($v as $v) { | ||
$def->addTag($k, $v); | ||
} | ||
} | ||
} | ||
|
||
return $def; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,10 +58,13 @@ protected function processValue($value, $isRoot = false) | |
return $this->bag->resolveValue($value); | ||
} | ||
if ($value instanceof Definition) { | ||
// don't record these as new changes to the Definition | ||
$value->setTrackChanges(false); | ||
$value->setClass($this->bag->resolveValue($value->getClass())); | ||
$value->setFile($this->bag->resolveValue($value->getFile())); | ||
$value->setProperties($this->bag->resolveValue($value->getProperties())); | ||
$value->setMethodCalls($this->bag->resolveValue($value->getMethodCalls())); | ||
$value->setTrackChanges(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a side-effect of the change tracking... changes. This wasn't actually needed to get the new behavior working correctly, it was just needed to get some tests to pass (i.e. the tests were compiling the container and then comparing Definition objects together... one of the Definition objects now had a few extra "changes") |
||
} | ||
|
||
return parent::processValue($value, $isRoot); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this down to be after
ResolveDefinitionTemplatesPass
on purpose: this allows the parent-child definitions to be resolved first. Then, theResolveDefinitionInheritancePass
doesn't need to worry about this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about that, this should be kept here: all following compiler passes need to always win. Moving the pass after all DI extension passes is what makes change-tracking a nightmare.
unless I missed something, the full target order proposed in this PR should be:
defaults > instanceof > parents > service > passes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(vs defaults > parents > service > instanceof > passes as implemented today)