feature #21530 [DependencyInjection] Add "instanceof" section for local interface-defined configs (nicolas-grekas, dunglas)
This PR was merged into the 3.3-dev branch. Discussion ---------- [DependencyInjection] Add "instanceof" section for local interface-defined configs | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - This is a direction follow up of #21357 on which we're working together with @dunglas. From the description posted there: There is some work being done to include features of [DunglasActionBundle](https://github.com/dunglas/DunglasActionBundle) in the core of Symfony. The goal of all those PRs is to improve the developper experience of the framework, allow to develop faster while preserving all benefits of using Symfony (strictness, modularity, extensibility...) and make it easier to learn for newcomers. This PR implements the tagging feature of ActionBundle in a more generic way. It will help to get rid of `AppBundle` in the the standard edition and to register automatically some classes including commands. Here is an example of config (that can be embedded in the standard edition) to enable those features: ```yaml # config/services.yml services: _defaults: autowire: ['get*', 'set*'] # Enable constructor, getter and setter autowiring for all services defined in this file _instanceof: Symfony\Component\Console\Command: # Add the console.command tag to all services defined in this file having this type tags: ['console.command'] # Set tags but also other settings like "public", "autowire" or "shared" here Twig_ExtensionInterface: tags: ['twig.extension'] Symfony\Component\EventDispatcher\EventSubscriberInterface: tags: ['kernel.event_subscriber'] App\: # Register all classes in the src/Controller directory as services psr4: ../src/{Controller,Command,Twig,EventSubscriber} ``` It's part of our 0 config initiative: controllers and commands will be automatically registered as services and "autowired", allowing the user to create and inject new services without having to write a single line of YAML or XML. When refactoring changes are also automatically updated and don't require to update config files. It's a big win for rapid application development and prototyping. Of course, this is fully compatible with the actual way of defining services and it's possible to switch (or mix) approaches very easily. It's even possible to start prototyping using 0config features then switch to explicit services definitions when the project becomes mature. Commits -------773eca7794
[DependencyInjection] Tests + refacto for "instanceof" definitions2fb601983f
[DependencyInjection] Add "instanceof" section for local interface-defined configs
This commit is contained in:
commit
d47571f5ec
@ -4,6 +4,7 @@ CHANGELOG
|
|||||||
3.3.0
|
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 "service-locator" argument for lazy loading a set of identified values and services
|
||||||
* [EXPERIMENTAL] added prototype services for PSR4-based discovery and registration
|
* [EXPERIMENTAL] added prototype services for PSR4-based discovery and registration
|
||||||
* added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info
|
* added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info
|
||||||
|
@ -119,6 +119,16 @@ class ChildDefinition extends Definition
|
|||||||
return parent::setFile($file);
|
return parent::setFile($file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setShared($boolean)
|
||||||
|
{
|
||||||
|
$this->changes['shared'] = true;
|
||||||
|
|
||||||
|
return parent::setShared($boolean);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -139,6 +149,16 @@ class ChildDefinition extends Definition
|
|||||||
return parent::setLazy($boolean);
|
return parent::setLazy($boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setAbstract($boolean)
|
||||||
|
{
|
||||||
|
$this->changes['abstract'] = true;
|
||||||
|
|
||||||
|
return parent::setAbstract($boolean);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -42,6 +42,7 @@ class PassConfig
|
|||||||
$this->beforeOptimizationPasses = array(
|
$this->beforeOptimizationPasses = array(
|
||||||
100 => array(
|
100 => array(
|
||||||
$resolveClassPass = new ResolveClassPass(),
|
$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->setLazy($parentDef->isLazy());
|
||||||
$def->setAutowiredCalls($parentDef->getAutowiredCalls());
|
$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
|
// overwrite with values specified in the decorator
|
||||||
$changes = $definition->getChanges();
|
$changes = $definition->getChanges();
|
||||||
if (isset($changes['class'])) {
|
if (isset($changes['class'])) {
|
||||||
@ -168,26 +188,5 @@ class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
|
|||||||
foreach ($definition->getOverriddenGetters() as $k => $v) {
|
foreach ($definition->getOverriddenGetters() as $k => $v) {
|
||||||
$def->setOverriddenGetter($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 $properties = array();
|
||||||
private $calls = array();
|
private $calls = array();
|
||||||
private $getters = array();
|
private $getters = array();
|
||||||
|
private $instanceof = array();
|
||||||
private $configurator;
|
private $configurator;
|
||||||
private $tags = array();
|
private $tags = array();
|
||||||
private $public = true;
|
private $public = true;
|
||||||
@ -363,6 +364,32 @@ class Definition
|
|||||||
return $this->getters;
|
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.
|
* Sets tags for this definition.
|
||||||
*
|
*
|
||||||
@ -736,9 +763,7 @@ class Definition
|
|||||||
*/
|
*/
|
||||||
public function setAutowired($autowired)
|
public function setAutowired($autowired)
|
||||||
{
|
{
|
||||||
$this->autowiredCalls = $autowired ? array('__construct') : array();
|
return $this->setAutowiredCalls($autowired ? array('__construct') : array());
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\DependencyInjection\Loader;
|
namespace Symfony\Component\DependencyInjection\Loader;
|
||||||
|
|
||||||
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
|
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
|
||||||
|
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Definition;
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
@ -29,6 +30,8 @@ use Symfony\Component\Finder\Glob;
|
|||||||
abstract class FileLoader extends BaseFileLoader
|
abstract class FileLoader extends BaseFileLoader
|
||||||
{
|
{
|
||||||
protected $container;
|
protected $container;
|
||||||
|
protected $isLoadingInstanceof = false;
|
||||||
|
protected $instanceof = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ContainerBuilder $container A ContainerBuilder instance
|
* @param ContainerBuilder $container A ContainerBuilder instance
|
||||||
@ -80,7 +83,22 @@ abstract class FileLoader extends BaseFileLoader
|
|||||||
$prototype = serialize($prototype);
|
$prototype = serialize($prototype);
|
||||||
|
|
||||||
foreach ($classes as $class) {
|
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);
|
$this->loadFromExtensions($xml);
|
||||||
|
|
||||||
// services
|
// services
|
||||||
|
try {
|
||||||
$this->parseDefinitions($xml, $path);
|
$this->parseDefinitions($xml, $path);
|
||||||
|
} finally {
|
||||||
|
$this->instanceof = array();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,13 +130,21 @@ class XmlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
$this->setCurrentDir(dirname($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);
|
$defaults = $this->getServiceDefaults($xml, $file);
|
||||||
foreach ($services as $service) {
|
foreach ($services as $service) {
|
||||||
if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
|
if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
|
||||||
if ('prototype' === $service->tagName) {
|
if ('prototype' === $service->tagName) {
|
||||||
$this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'));
|
$this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'));
|
||||||
} else {
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($parent = $service->getAttribute('parent')) {
|
if ($this->isLoadingInstanceof) {
|
||||||
|
$definition = new ChildDefinition('');
|
||||||
|
} elseif ($parent = $service->getAttribute('parent')) {
|
||||||
$definition = new ChildDefinition($parent);
|
$definition = new ChildDefinition($parent);
|
||||||
|
|
||||||
if ($value = $service->getAttribute('inherit-tags')) {
|
if ($value = $service->getAttribute('inherit-tags')) {
|
||||||
@ -247,7 +261,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
$definition->setDeprecated(true, $deprecated[0]->nodeValue ?: null);
|
$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->setProperties($this->getArgumentsAsPhp($service, 'property'));
|
||||||
$definition->setOverriddenGetters($this->getArgumentsAsPhp($service, 'getter'));
|
$definition->setOverriddenGetters($this->getArgumentsAsPhp($service, 'getter'));
|
||||||
|
|
||||||
@ -422,7 +436,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
uksort($definitions, 'strnatcmp');
|
uksort($definitions, 'strnatcmp');
|
||||||
foreach (array_reverse($definitions) as $id => list($domElement, $file, $wild)) {
|
foreach (array_reverse($definitions) as $id => list($domElement, $file, $wild)) {
|
||||||
if (null !== $definition = $this->parseDefinition($domElement, $file)) {
|
if (null !== $definition = $this->parseDefinition($domElement, $file)) {
|
||||||
$this->container->setDefinition($id, $definition);
|
$this->setDefinition($id, $definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true === $wild) {
|
if (true === $wild) {
|
||||||
|
@ -81,6 +81,22 @@ class YamlFileLoader extends FileLoader
|
|||||||
'autowire' => 'autowire',
|
'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(
|
private static $defaultsKeywords = array(
|
||||||
'public' => 'public',
|
'public' => 'public',
|
||||||
'tags' => 'tags',
|
'tags' => 'tags',
|
||||||
@ -125,7 +141,11 @@ class YamlFileLoader extends FileLoader
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
$this->setCurrentDir(dirname($path));
|
$this->setCurrentDir(dirname($path));
|
||||||
|
try {
|
||||||
$this->parseDefinitions($content, $resource);
|
$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));
|
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);
|
$defaults = $this->parseDefaults($content, $file);
|
||||||
foreach ($content['services'] as $id => $service) {
|
foreach ($content['services'] as $id => $service) {
|
||||||
$this->parseDefinition($id, $service, $file, $defaults);
|
$this->parseDefinition($id, $service, $file, $defaults);
|
||||||
@ -203,18 +239,11 @@ class YamlFileLoader extends FileLoader
|
|||||||
*/
|
*/
|
||||||
private function parseDefaults(array &$content, $file)
|
private function parseDefaults(array &$content, $file)
|
||||||
{
|
{
|
||||||
if (!isset($content['services']['_defaults'])) {
|
if (!$this->isUnderscoredParamValid($content, '_defaults', $file)) {
|
||||||
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
|
|
||||||
|
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$defaults = $content['services']['_defaults'];
|
||||||
unset($content['services']['_defaults']);
|
unset($content['services']['_defaults']);
|
||||||
|
|
||||||
foreach ($defaults as $key => $default) {
|
foreach ($defaults as $key => $default) {
|
||||||
@ -254,6 +283,21 @@ class YamlFileLoader extends FileLoader
|
|||||||
return $defaults;
|
return $defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isUnderscoredParamValid($content, $name, $file)
|
||||||
|
{
|
||||||
|
if (!isset($content['services'][$name])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($underscoreParam = $content['services'][$name])) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Service "%s" key must be an array, "%s" given in "%s".', $name, gettype($underscoreParam), $file));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @deprecated condition, to be removed in 4.0
|
||||||
|
|
||||||
|
return !isset($underscoreParam['alias']) && !isset($underscoreParam['class']) && !isset($underscoreParam['factory']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $service
|
* @param array $service
|
||||||
*
|
*
|
||||||
@ -304,7 +348,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));
|
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'])) {
|
if (isset($service['alias'])) {
|
||||||
$public = array_key_exists('public', $service) ? (bool) $service['public'] : (isset($defaults['public']) ? $defaults['public'] : true);
|
$public = array_key_exists('public', $service) ? (bool) $service['public'] : (isset($defaults['public']) ? $defaults['public'] : true);
|
||||||
@ -319,7 +363,9 @@ class YamlFileLoader extends FileLoader
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($service['parent'])) {
|
if ($this->isLoadingInstanceof) {
|
||||||
|
$definition = new ChildDefinition('');
|
||||||
|
} elseif (isset($service['parent'])) {
|
||||||
$definition = new ChildDefinition($service['parent']);
|
$definition = new ChildDefinition($service['parent']);
|
||||||
|
|
||||||
$inheritTag = isset($service['inherit_tags']) ? $service['inherit_tags'] : (isset($defaults['inherit_tags']) ? $defaults['inherit_tags'] : null);
|
$inheritTag = isset($service['inherit_tags']) ? $service['inherit_tags'] : (isset($defaults['inherit_tags']) ? $defaults['inherit_tags'] : null);
|
||||||
@ -494,7 +540,7 @@ class YamlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
$this->registerClasses($definition, $id, $service['resource']);
|
$this->registerClasses($definition, $id, $service['resource']);
|
||||||
} else {
|
} else {
|
||||||
$this->container->setDefinition($id, $definition);
|
$this->setDefinition($id, $definition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -723,12 +769,14 @@ class YamlFileLoader extends FileLoader
|
|||||||
* @param array $definition The service definition to check
|
* @param array $definition The service definition to check
|
||||||
* @param string $file The loaded YAML file
|
* @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'])) {
|
if ($throw = $this->isLoadingInstanceof) {
|
||||||
$keywords = static::$prototypeKeywords;
|
$keywords = self::$instanceofKeywords;
|
||||||
|
} elseif ($throw = isset($definition['resource'])) {
|
||||||
|
$keywords = self::$prototypeKeywords;
|
||||||
} else {
|
} else {
|
||||||
$keywords = static::$serviceKeywords;
|
$keywords = self::$serviceKeywords;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($definition as $key => $value) {
|
foreach ($definition as $key => $value) {
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
<xsd:element name="service" type="service" minOccurs="1" />
|
<xsd:element name="service" type="service" minOccurs="1" />
|
||||||
<xsd:element name="prototype" type="prototype" minOccurs="0" />
|
<xsd:element name="prototype" type="prototype" minOccurs="0" />
|
||||||
<xsd:element name="defaults" type="defaults" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="defaults" type="defaults" minOccurs="0" maxOccurs="1" />
|
||||||
|
<xsd:element name="instanceof" type="instanceof" minOccurs="0" />
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
@ -137,6 +138,26 @@
|
|||||||
<xsd:attribute name="inherit-tags" type="boolean" />
|
<xsd:attribute name="inherit-tags" type="boolean" />
|
||||||
</xsd:complexType>
|
</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:complexType name="prototype">
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
|
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
@ -22,6 +22,7 @@ class PassConfigTest extends \PHPUnit_Framework_TestCase
|
|||||||
public function testPassOrdering()
|
public function testPassOrdering()
|
||||||
{
|
{
|
||||||
$config = new PassConfig();
|
$config = new PassConfig();
|
||||||
|
$config->setBeforeOptimizationPasses(array());
|
||||||
|
|
||||||
$pass1 = $this->getMockBuilder(CompilerPassInterface::class)->getMock();
|
$pass1 = $this->getMockBuilder(CompilerPassInterface::class)->getMock();
|
||||||
$config->addPass($pass1, PassConfig::TYPE_BEFORE_OPTIMIZATION, 10);
|
$config->addPass($pass1, PassConfig::TYPE_BEFORE_OPTIMIZATION, 10);
|
||||||
@ -30,7 +31,7 @@ class PassConfigTest extends \PHPUnit_Framework_TestCase
|
|||||||
$config->addPass($pass2, PassConfig::TYPE_BEFORE_OPTIMIZATION, 30);
|
$config->addPass($pass2, PassConfig::TYPE_BEFORE_OPTIMIZATION, 30);
|
||||||
|
|
||||||
$passes = $config->getBeforeOptimizationPasses();
|
$passes = $config->getBeforeOptimizationPasses();
|
||||||
$this->assertSame($pass2, $passes[1]);
|
$this->assertSame($pass2, $passes[0]);
|
||||||
$this->assertSame($pass1, $passes[2]);
|
$this->assertSame($pass1, $passes[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||||
|
<services>
|
||||||
|
<instanceof id="Symfony\Component\DependencyInjection\Tests\Loader\BarInterface" lazy="true">
|
||||||
|
<autowire>set*</autowire>
|
||||||
|
<tag name="foo" />
|
||||||
|
<tag name="bar" />
|
||||||
|
</instanceof>
|
||||||
|
|
||||||
|
<service id="Symfony\Component\DependencyInjection\Tests\Loader\Bar" class="Symfony\Component\DependencyInjection\Tests\Loader\Bar" autowire="true" />
|
||||||
|
</services>
|
||||||
|
</container>
|
@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
_instanceof:
|
||||||
|
Symfony\Component\DependencyInjection\Tests\Loader\FooInterface:
|
||||||
|
autowire: true
|
||||||
|
lazy: true
|
||||||
|
tags:
|
||||||
|
- { name: foo }
|
||||||
|
- { name: bar }
|
||||||
|
|
||||||
|
Symfony\Component\DependencyInjection\Tests\Loader\Foo: ~
|
@ -719,4 +719,25 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals(array(null, 'ABCD'), $container->getDefinition(NamedArgumentsDummy::class)->getArguments());
|
$this->assertEquals(array(null, 'ABCD'), $container->getDefinition(NamedArgumentsDummy::class)->getArguments());
|
||||||
$this->assertEquals(array(array('setApiKey', array('123'))), $container->getDefinition(NamedArgumentsDummy::class)->getMethodCalls());
|
$this->assertEquals(array(array('setApiKey', array('123'))), $container->getDefinition(NamedArgumentsDummy::class)->getMethodCalls());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testInstanceof()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
|
||||||
|
$loader->load('services_instanceof.xml');
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$definition = $container->getDefinition(Bar::class);
|
||||||
|
$this->assertSame(array('__construct', 'set*'), $definition->getAutowiredCalls());
|
||||||
|
$this->assertTrue($definition->isLazy());
|
||||||
|
$this->assertSame(array('foo' => array(array()), 'bar' => array(array())), $definition->getTags());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BarInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bar implements BarInterface
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
@ -469,6 +469,19 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals(array(array('setApiKey', array('123'))), $container->getDefinition('another_one')->getMethodCalls());
|
$this->assertEquals(array(array('setApiKey', array('123'))), $container->getDefinition('another_one')->getMethodCalls());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testInstanceof()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
|
||||||
|
$loader->load('services_instanceof.yml');
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$definition = $container->getDefinition(Foo::class);
|
||||||
|
$this->assertTrue($definition->isAutowired());
|
||||||
|
$this->assertTrue($definition->isLazy());
|
||||||
|
$this->assertSame(array('foo' => array(array()), 'bar' => array(array())), $definition->getTags());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
|
||||||
* @expectedExceptionMessage The value of the "decorates" option for the "bar" service must be the id of the service without the "@" prefix (replace "@foo" with "foo").
|
* @expectedExceptionMessage The value of the "decorates" option for the "bar" service must be the id of the service without the "@" prefix (replace "@foo" with "foo").
|
||||||
@ -500,3 +513,11 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$loader->load('services_underscore.yml');
|
$loader->load('services_underscore.yml');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FooInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo implements FooInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user