[DependencyInjection] Add "instanceof" section for local interface-defined configs
This commit is contained in:
parent
0a3cd973ae
commit
2fb601983f
@ -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
|
||||
|
@ -119,6 +119,16 @@ class ChildDefinition extends Definition
|
||||
return parent::setFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setShared($boolean)
|
||||
{
|
||||
$this->changes['shared'] = true;
|
||||
|
||||
return parent::setShared($boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -139,6 +149,16 @@ class ChildDefinition extends Definition
|
||||
return parent::setLazy($boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAbstract($boolean)
|
||||
{
|
||||
$this->changes['abstract'] = true;
|
||||
|
||||
return parent::setAbstract($boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -42,6 +42,7 @@ class PassConfig
|
||||
$this->beforeOptimizationPasses = array(
|
||||
100 => array(
|
||||
$resolveClassPass = new ResolveClassPass(),
|
||||
new ResolveDefinitionInheritancePass(),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -103,6 +103,26 @@ class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
|
||||
$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'])) {
|
||||
@ -168,26 +188,5 @@ class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -363,6 +364,32 @@ class Definition
|
||||
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.
|
||||
*
|
||||
@ -736,9 +763,7 @@ class Definition
|
||||
*/
|
||||
public function setAutowired($autowired)
|
||||
{
|
||||
$this->autowiredCalls = $autowired ? array('__construct') : array();
|
||||
|
||||
return $this;
|
||||
return $this->setAutowiredCalls($autowired ? array('__construct') : array());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
@ -29,6 +30,8 @@ use Symfony\Component\Finder\Glob;
|
||||
abstract class FileLoader extends BaseFileLoader
|
||||
{
|
||||
protected $container;
|
||||
protected $isLoadingInstanceof = false;
|
||||
protected $instanceof = array();
|
||||
|
||||
/**
|
||||
* @param ContainerBuilder $container A ContainerBuilder instance
|
||||
@ -80,7 +83,22 @@ abstract class FileLoader extends BaseFileLoader
|
||||
$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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,11 @@ class XmlFileLoader extends FileLoader
|
||||
$this->loadFromExtensions($xml);
|
||||
|
||||
// services
|
||||
$this->parseDefinitions($xml, $path);
|
||||
try {
|
||||
$this->parseDefinitions($xml, $path);
|
||||
} finally {
|
||||
$this->instanceof = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,13 +130,21 @@ class XmlFileLoader extends FileLoader
|
||||
}
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,7 +221,9 @@ class XmlFileLoader extends FileLoader
|
||||
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')) {
|
||||
@ -247,7 +261,7 @@ class XmlFileLoader extends FileLoader
|
||||
$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'));
|
||||
|
||||
@ -422,7 +436,7 @@ class XmlFileLoader extends FileLoader
|
||||
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) {
|
||||
|
@ -81,6 +81,22 @@ class YamlFileLoader extends FileLoader
|
||||
'autowire' => 'autowire',
|
||||
);
|
||||
|
||||
private static $instanceofKeywords = array(
|
||||
'shared' => 'shared',
|
||||
'lazy' => 'lazy',
|
||||
'public' => 'public',
|
||||
'abstract' => 'abstract',
|
||||
'deprecated' => 'deprecated',
|
||||
'factory' => 'factory',
|
||||
'arguments' => 'arguments',
|
||||
'properties' => 'properties',
|
||||
'getters' => 'getters',
|
||||
'configurator' => 'configurator',
|
||||
'calls' => 'calls',
|
||||
'tags' => 'tags',
|
||||
'autowire' => 'autowire',
|
||||
);
|
||||
|
||||
private static $defaultsKeywords = array(
|
||||
'public' => 'public',
|
||||
'tags' => 'tags',
|
||||
@ -125,7 +141,11 @@ class YamlFileLoader extends FileLoader
|
||||
|
||||
// services
|
||||
$this->setCurrentDir(dirname($path));
|
||||
$this->parseDefinitions($content, $resource);
|
||||
try {
|
||||
$this->parseDefinitions($content, $resource);
|
||||
} finally {
|
||||
$this->instanceof = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,6 +207,22 @@ class YamlFileLoader extends FileLoader
|
||||
throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file));
|
||||
}
|
||||
|
||||
if ($this->isUnderscoredParamValid($content, '_instanceof', $file)) {
|
||||
$this->instanceof = array();
|
||||
$this->isLoadingInstanceof = true;
|
||||
foreach ($content['services']['_instanceof'] as $id => $service) {
|
||||
if (!$service || !is_array($service)) {
|
||||
throw new InvalidArgumentException(sprintf('Type definition "%s" must be a non-empty array within "_instanceof" in %s. Check your YAML syntax.', $id, $file));
|
||||
}
|
||||
if (is_string($service) && 0 === strpos($service, '@')) {
|
||||
throw new InvalidArgumentException(sprintf('Type definition "%s" cannot be an alias within "_instanceof" in %s. Check your YAML syntax.', $id, $file));
|
||||
}
|
||||
$this->parseDefinition($id, $service, $file, array());
|
||||
}
|
||||
unset($content['services']['_instanceof']);
|
||||
}
|
||||
|
||||
$this->isLoadingInstanceof = false;
|
||||
$defaults = $this->parseDefaults($content, $file);
|
||||
foreach ($content['services'] as $id => $service) {
|
||||
$this->parseDefinition($id, $service, $file, $defaults);
|
||||
@ -203,18 +239,11 @@ class YamlFileLoader extends FileLoader
|
||||
*/
|
||||
private function parseDefaults(array &$content, $file)
|
||||
{
|
||||
if (!isset($content['services']['_defaults'])) {
|
||||
return array();
|
||||
}
|
||||
if (!is_array($defaults = $content['services']['_defaults'])) {
|
||||
throw new InvalidArgumentException(sprintf('Service defaults must be an array, "%s" given in "%s".', gettype($defaults), $file));
|
||||
}
|
||||
if (isset($defaults['alias']) || isset($defaults['class']) || isset($defaults['factory'])) {
|
||||
// @deprecated code path, to be removed in 4.0
|
||||
|
||||
if (!$this->isUnderscoredParamValid($content, '_defaults', $file)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$defaults = $content['services']['_defaults'];
|
||||
unset($content['services']['_defaults']);
|
||||
|
||||
foreach ($defaults as $key => $default) {
|
||||
@ -304,7 +333,7 @@ class YamlFileLoader extends FileLoader
|
||||
throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but %s found for service "%s" in %s. Check your YAML syntax.', gettype($service), $id, $file));
|
||||
}
|
||||
|
||||
static::checkDefinition($id, $service, $file);
|
||||
$this->checkDefinition($id, $service, $file);
|
||||
|
||||
if (isset($service['alias'])) {
|
||||
$public = array_key_exists('public', $service) ? (bool) $service['public'] : (isset($defaults['public']) ? $defaults['public'] : true);
|
||||
@ -319,7 +348,9 @@ class YamlFileLoader extends FileLoader
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($service['parent'])) {
|
||||
if ($this->isLoadingInstanceof) {
|
||||
$definition = new ChildDefinition('');
|
||||
} elseif (isset($service['parent'])) {
|
||||
$definition = new ChildDefinition($service['parent']);
|
||||
|
||||
$inheritTag = isset($service['inherit_tags']) ? $service['inherit_tags'] : (isset($defaults['inherit_tags']) ? $defaults['inherit_tags'] : null);
|
||||
@ -494,7 +525,7 @@ class YamlFileLoader extends FileLoader
|
||||
}
|
||||
$this->registerClasses($definition, $id, $service['resource']);
|
||||
} else {
|
||||
$this->container->setDefinition($id, $definition);
|
||||
$this->setDefinition($id, $definition);
|
||||
}
|
||||
}
|
||||
|
||||
@ -723,12 +754,14 @@ class YamlFileLoader extends FileLoader
|
||||
* @param array $definition The service definition to check
|
||||
* @param string $file The loaded YAML file
|
||||
*/
|
||||
private static function checkDefinition($id, array $definition, $file)
|
||||
private function checkDefinition($id, array $definition, $file)
|
||||
{
|
||||
if ($throw = isset($definition['resource'])) {
|
||||
$keywords = static::$prototypeKeywords;
|
||||
if ($throw = $this->isLoadingInstanceof) {
|
||||
$keywords = self::$instanceofKeywords;
|
||||
} elseif ($throw = isset($definition['resource'])) {
|
||||
$keywords = self::$prototypeKeywords;
|
||||
} else {
|
||||
$keywords = static::$serviceKeywords;
|
||||
$keywords = self::$serviceKeywords;
|
||||
}
|
||||
|
||||
foreach ($definition as $key => $value) {
|
||||
|
@ -56,6 +56,7 @@
|
||||
<xsd:element name="service" type="service" minOccurs="1" />
|
||||
<xsd:element name="prototype" type="prototype" minOccurs="0" />
|
||||
<xsd:element name="defaults" type="defaults" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="instanceof" type="instanceof" minOccurs="0" />
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
|
||||
@ -137,6 +138,26 @@
|
||||
<xsd:attribute name="inherit-tags" type="boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="instanceof">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="configurator" type="callable" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="factory" type="callable" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="deprecated" type="xsd:string" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="getter" type="getter" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xsd:element name="autowire" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="id" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="shared" type="boolean" />
|
||||
<xsd:attribute name="public" type="boolean" />
|
||||
<xsd:attribute name="lazy" type="boolean" />
|
||||
<xsd:attribute name="abstract" type="boolean" />
|
||||
<xsd:attribute name="autowire" type="boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="prototype">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
|
||||
|
Reference in New Issue
Block a user