Skip to content

Commit

Permalink
[DependencyInjection] Add "instanceof" section for local interface-de…
Browse files Browse the repository at this point in the history
…fined configs
  • Loading branch information
nicolas-grekas committed Feb 17, 2017
1 parent 1f314db commit 6ed5440
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
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 ChildDefinition.php
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
1 change: 1 addition & 0 deletions Compiler/PassConfig.php
Expand Up @@ -42,6 +42,7 @@ public function __construct()
$this->beforeOptimizationPasses = array(
100 => array(
$resolveClassPass = new ResolveClassPass(),
new ResolveDefinitionInheritancePass(),
),
);

Expand Down
106 changes: 106 additions & 0 deletions Compiler/ResolveDefinitionInheritancePass.php
@@ -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);
}
}
}
}
41 changes: 20 additions & 21 deletions Compiler/ResolveDefinitionTemplatesPass.php
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 Definition.php
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 Loader/FileLoader.php
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 Loader/XmlFileLoader.php
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

0 comments on commit 6ed5440

Please sign in to comment.