Skip to content

[DependencyInjection] Add "instanceof" section for local interface-defined configs #21530

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 2 commits into from
Feb 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
3.3.0
-----

* [EXPERIMENTAL] added "instanceof" section for local interface-defined configs
* [EXPERIMENTAL] added "service-locator" argument for lazy loading a set of identified values and services
* [EXPERIMENTAL] added prototype services for PSR4-based discovery and registration
* added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info
Expand Down
20 changes: 20 additions & 0 deletions src/Symfony/Component/DependencyInjection/ChildDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ public function setFile($file)
return parent::setFile($file);
}

/**
* {@inheritdoc}
*/
public function setShared($boolean)
{
$this->changes['shared'] = true;

return parent::setShared($boolean);
}

/**
* {@inheritdoc}
*/
Expand All @@ -139,6 +149,16 @@ public function setLazy($boolean)
return parent::setLazy($boolean);
}

/**
* {@inheritdoc}
*/
public function setAbstract($boolean)
{
$this->changes['abstract'] = true;

return parent::setAbstract($boolean);
}

/**
* {@inheritdoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public function __construct()
$this->beforeOptimizationPasses = array(
100 => array(
$resolveClassPass = new ResolveClassPass(),
new ResolveDefinitionInheritancePass(),
),
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?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\Definition;

/**
* Applies tags and instanceof inheritance to definitions.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveDefinitionInheritancePass extends AbstractRecursivePass
{
protected function processValue($value, $isRoot = false)
{
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
if ($value instanceof ChildDefinition) {
$this->resolveDefinition($value);
}
$class = $value->getClass();
if (!$class || false !== strpos($class, '%') || !$instanceof = $value->getInstanceofConditionals()) {
return parent::processValue($value, $isRoot);
}

foreach ($instanceof as $interface => $definition) {
if ($interface !== $class && (!$this->container->getReflectionClass($interface) || !$this->container->getReflectionClass($class))) {
continue;
}
if ($interface === $class || is_subclass_of($class, $interface)) {
$this->mergeDefinition($value, $definition);
}
}

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

/**
* Populates the class and tags from parent definitions.
*/
private function resolveDefinition(ChildDefinition $definition)
{
if (!$this->container->has($parent = $definition->getParent())) {
return;
}

$parentDef = $this->container->findDefinition($parent);
if ($parentDef instanceof ChildDefinition) {
$this->resolveDefinition($parentDef);
}

if (!isset($definition->getChanges()['class'])) {
$definition->setClass($parentDef->getClass());
}

// append parent tags when inheriting is enabled
if ($definition->getInheritTags()) {
foreach ($parentDef->getTags() as $k => $v) {
foreach ($v as $v) {
$definition->addTag($k, $v);
}
}
}

$definition->setInheritTags(false);
}

private function mergeDefinition(Definition $def, ChildDefinition $definition)
{
$changes = $definition->getChanges();
if (isset($changes['shared'])) {
$def->setShared($definition->isShared());
}
if (isset($changes['abstract'])) {
$def->setAbstract($definition->isAbstract());
}
if (isset($changes['autowired_calls'])) {
$autowiredCalls = $def->getAutowiredCalls();
}

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

// merge autowired calls
if (isset($changes['autowired_calls'])) {
$def->setAutowiredCalls(array_merge($autowiredCalls, $def->getAutowiredCalls()));
}

// merge tags
foreach ($definition->getTags() as $k => $v) {
foreach ($v as $v) {
$def->addTag($k, $v);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,26 @@ private function doResolveDefinition(ChildDefinition $definition)
$def->setLazy($parentDef->isLazy());
$def->setAutowiredCalls($parentDef->getAutowiredCalls());

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 Down Expand Up @@ -168,26 +188,5 @@ private function doResolveDefinition(ChildDefinition $definition)
foreach ($definition->getOverriddenGetters() as $k => $v) {
$def->setOverriddenGetter($k, $v);
}

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

// 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;
}
}
31 changes: 28 additions & 3 deletions src/Symfony/Component/DependencyInjection/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Definition
private $properties = array();
private $calls = array();
private $getters = array();
private $instanceof = array();
private $configurator;
private $tags = array();
private $public = true;
Expand Down Expand Up @@ -363,6 +364,32 @@ public function getOverriddenGetters()
return $this->getters;
}

/**
* Sets the definition templates to conditionally apply on the current definition, keyed by parent interface/class.
*
* @param $instanceof ChildDefinition[]
*
* @experimental in version 3.3
*/
public function setInstanceofConditionals(array $instanceof)
{
$this->instanceof = $instanceof;

return $this;
}

/**
* Gets the definition templates to conditionally apply on the current definition, keyed by parent interface/class.
*
* @return ChildDefinition[]
*
* @experimental in version 3.3
*/
public function getInstanceofConditionals()
{
return $this->instanceof;
}

/**
* Sets tags for this definition.
*
Expand Down Expand Up @@ -736,9 +763,7 @@ public function getAutowiredCalls()
*/
public function setAutowired($autowired)
{
$this->autowiredCalls = $autowired ? array('__construct') : array();

return $this;
return $this->setAutowiredCalls($autowired ? array('__construct') : array());
}

/**
Expand Down
20 changes: 19 additions & 1 deletion src/Symfony/Component/DependencyInjection/Loader/FileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Loader;

use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
Expand All @@ -29,6 +30,8 @@
abstract class FileLoader extends BaseFileLoader
{
protected $container;
protected $isLoadingInstanceof = false;
protected $instanceof = array();

/**
* @param ContainerBuilder $container A ContainerBuilder instance
Expand Down Expand Up @@ -80,7 +83,22 @@ public function registerClasses(Definition $prototype, $namespace, $resource)
$prototype = serialize($prototype);

foreach ($classes as $class) {
$this->container->setDefinition($class, unserialize($prototype));
$this->setDefinition($class, unserialize($prototype));
}
}

/**
* @experimental in version 3.3
*/
protected function setDefinition($id, Definition $definition)
{
if ($this->isLoadingInstanceof) {
if (!$definition instanceof ChildDefinition) {
throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, get_class($definition)));
}
$this->instanceof[$id] = $definition;
} else {
$this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof));
}
}

Expand Down
24 changes: 19 additions & 5 deletions src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ public function load($resource, $type = null)
$this->loadFromExtensions($xml);

// services
$this->parseDefinitions($xml, $path);
try {
$this->parseDefinitions($xml, $path);
} finally {
$this->instanceof = array();
}
}

/**
Expand Down Expand Up @@ -126,13 +130,21 @@ private function parseDefinitions(\DOMDocument $xml, $file)
}
$this->setCurrentDir(dirname($file));

$this->instanceof = array();
$this->isLoadingInstanceof = true;
$instanceof = $xpath->query('//container:services/container:instanceof');
foreach ($instanceof as $service) {
$this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, array()));
}

$this->isLoadingInstanceof = false;
$defaults = $this->getServiceDefaults($xml, $file);
foreach ($services as $service) {
if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
if ('prototype' === $service->tagName) {
$this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'));
} else {
$this->container->setDefinition((string) $service->getAttribute('id'), $definition);
$this->setDefinition((string) $service->getAttribute('id'), $definition);
}
}
}
Expand Down Expand Up @@ -209,7 +221,9 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
return;
}

if ($parent = $service->getAttribute('parent')) {
if ($this->isLoadingInstanceof) {
$definition = new ChildDefinition('');
} elseif ($parent = $service->getAttribute('parent')) {
$definition = new ChildDefinition($parent);

if ($value = $service->getAttribute('inherit-tags')) {
Expand Down Expand Up @@ -247,7 +261,7 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
$definition->setDeprecated(true, $deprecated[0]->nodeValue ?: null);
}

$definition->setArguments($this->getArgumentsAsPhp($service, 'argument', false, (bool) $parent));
$definition->setArguments($this->getArgumentsAsPhp($service, 'argument', false, $definition instanceof ChildDefinition));
$definition->setProperties($this->getArgumentsAsPhp($service, 'property'));
$definition->setOverriddenGetters($this->getArgumentsAsPhp($service, 'getter'));

Expand Down Expand Up @@ -422,7 +436,7 @@ private function processAnonymousServices(\DOMDocument $xml, $file)
uksort($definitions, 'strnatcmp');
foreach (array_reverse($definitions) as $id => list($domElement, $file, $wild)) {
if (null !== $definition = $this->parseDefinition($domElement, $file)) {
$this->container->setDefinition($id, $definition);
$this->setDefinition($id, $definition);
}

if (true === $wild) {
Expand Down
Loading