8000 [DI] Rework config hierarchy: defaults > instanceof > service config by nicolas-grekas · Pull Request #22356 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[DI] Rework config hierarchy: defaults > instanceof > service config #22356

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

Merged
merged 3 commits into from
Apr 11, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[DI] Rework config hierarchy: defaults > instanceof > service config
  • Loading branch information
nicolas-grekas committed Apr 10, 2017
commit ab86457b12db9b6cedb158fd6a54d6aa34219b2c
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public function __construct()
$this->beforeOptimizationPasses = array(
100 => array(
$resolveClassPass = new ResolveClassPass(),
new ResolveDefinitionInheritancePass(),
new ResolveInstanceofConditionalsPass(),
new ResolveTagsInheritancePass(),
),
);

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private function doResolveDefinition(ChildDefinition $definition)
$def = new Definition();

// merge in parent definition
// purposely ignored attributes: abstract, tags
// purposely ignored attributes: abstract, shared, tags
$def->setClass($parentDef->getClass());
$def->setArguments($parentDef->getArguments());
$def->setMethodCalls($parentDef->getMethodCalls());
Expand All @@ -101,27 +101,8 @@ private function doResolveDefinition(ChildDefinition $definition)
$def->setPublic($parentDef->isPublic());
$def->setLazy($parentDef->isLazy());
$def->setAutowired($parentDef->isAutowired());
$def->setChanges($parentDef->getChanges());

self::mergeDefinition($def, $definition);

// merge autowiring types
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {
$def->addAutowiringType($autowiringType);
}

// 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)
{
// overwrite with values specified in the decorator
$changes = $definition->getChanges();
if (isset($changes['class'])) {
Expand All @@ -148,6 +129,9 @@ public static function mergeDefinition(Definition $def, ChildDefinition $definit
if (isset($changes['autowired'])) {
$def->setAutowired($definition->isAutowired());
}
if (isset($changes['shared'])) {
$def->setShared($definition->isShared());
}
if (isset($changes['decorated_service'])) {
$decoratedService = $definition->getDecoratedService();
if (null === $decoratedService) {
Expand Down Expand Up @@ -182,5 +166,16 @@ 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
$def->setAbstract($definition->isAbstract());
$def->setTags($definition->getTags());

return $def;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

/**
* Applies instanceof conditionals to definitions.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveInstanceofConditionalsPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$didProcess = false;
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition instanceof ChildDefinition) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a one-line comment here:

Don't apply instanceof to children definition (but it will still be applied to their parent)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment added

continue;
}
if ($definition !== $processedDefinition = $this->processDefinition($container, $id, $definition)) {
$didProcess = true;
$container->setDefinition($id, $processedDefinition);
}
}
if ($didProcess) {
$container->register('abstract.'.__CLASS__, '')->setAbstract(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the goal of this ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's the base abstract service all "instanceof" chains inherit from. See L56 below.

}
}

private function processDefinition(ContainerBuilder $container, $id, Definition $definition)
{
if (!$instanceofConditionals = $definition->getInstanceofConditionals()) {
return $definition;
}
if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
return $definition;
}

$definition->setInstanceofConditionals(array());
$instanceofParent = null;
$parent = 'abstract.'.__CLASS__;
$shared = null;

foreach ($instanceofConditionals as $interface => $instanceofDef) {
if ($interface !== $class && (!$container->getReflectionClass($interface) || !$container->getReflectionClass($class))) {
continue;
}
if ($interface === $class || is_subclass_of($class, $interface)) {
$instanceofParent = clone $instanceofDef;
$instanceofParent->setAbstract(true)->setInheritTags(true)->setParent($parent);
$parent = 'instanceof.'.$interface.'.'.$id;
$container->setDefinition($parent, $instanceofParent);

if (isset($instanceofParent->getChanges()['shared'])) {
$shared = $instanceofParent->isShared();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does shared need to be treated special? It looks like ResolveDefinitionTemplatesPass now treats it like any other field, so wouldn't that take care of this without any special code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F438

shared is not handled as the other fields (that'd be a BC break): it is never inherited from the parent, as before (see lines after "purposely ignored attributes": there is no "setShared" call there)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right - and the integration test proves this. I didn't see it at first :)

}
}

if ($instanceofParent) {
// cast Definition to ChildDefinition
$definition = serialize($definition);
$definition = substr_replace($definition, '53', 2, 2);
$definition = substr_replace($definition, 'Child', 44, 0);
$definition = unserialize($definition);
$definition->setInheritTags(true)->setParent($parent);

if (null !== $shared && !isset($definition->getChanges()['shared'])) {
$definition->setShared($shared);
}
}

return $definition;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\ChildDefinition;

/**
* Applies tags inheritance to definitions.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveTagsInheritancePass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, $isRoot = false)
{
if (!$value instanceof ChildDefinition || !$value->getInheritTags()) {
return parent::processValue($value, $isRoot);
}
$value->setInheritTags(false);

if (!$this->container->has($parent = $value->getParent())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This surprised me at first - it looks like we're silencing invalid parent ids. But, you've done this simply so that the later ResolveDefinitionTemplatesPass can throw the proper exception. Maybe add a one-line comment mentioning a later pass checks for parent validity? I want to leave some breadcrumbs for the future :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, I made it throw, so that tag inheritance is made incompatible with dynamically created parents and save a few surprises (since tags would not be inherited for them, despite the field)

return parent::processValue($value, $isRoot);
}

$parentDef = $this->container->findDefinition($parent);

if ($parentDef instanceof ChildDefinition) {
$this->processValue($parentDef);
}

foreach ($parentDef->getTags() as $k => $v) {
foreach ($v as $v) {
$value->addTag($k, $v);
}
}

return parent::processValue($value, $isRoot);
}
}
Loading
0