[DI] add syntax to stack decorators

This commit is contained in:
Nicolas Grekas 2020-04-07 01:30:59 +02:00
parent b2f210f1f3
commit 98eeeae3d1
14 changed files with 559 additions and 62 deletions

View File

@ -38,6 +38,7 @@ class UnusedTagsPass implements CompilerPassInterface
'container.service_locator',
'container.service_locator_context',
'container.service_subscriber',
'container.stack',
'controller.argument_value_resolver',
'controller.service_arguments',
'data_collector',

View File

@ -51,6 +51,7 @@ class PassConfig
$this->optimizationPasses = [[
new AutoAliasServicePass(),
new ValidateEnvPlaceholdersPass(),
new ResolveDecoratorStackPass(),
new ResolveChildDefinitionsPass(),
new RegisterServiceSubscribersPass(),
new ResolveParameterPlaceHoldersPass(false, false),

View File

@ -0,0 +1,127 @@
<?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\Alias;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveDecoratorStackPass implements CompilerPassInterface
{
private $tag;
public function __construct(string $tag = 'container.stack')
{
$this->tag = $tag;
}
public function process(ContainerBuilder $container)
{
$stacks = [];
foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) {
$definition = $container->getDefinition($id);
if (!$definition instanceof ChildDefinition) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag));
}
if (!$stack = $definition->getArguments()) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": the stack of decorators is empty.', $id));
}
$stacks[$id] = $stack;
}
if (!$stacks) {
return;
}
$resolvedDefinitions = [];
foreach ($container->getDefinitions() as $id => $definition) {
if (!isset($stacks[$id])) {
$resolvedDefinitions[$id] = $definition;
continue;
}
foreach (array_reverse($this->resolveStack($stacks, [$id]), true) as $k => $v) {
$resolvedDefinitions[$k] = $v;
}
$alias = $container->setAlias($id, $k);
if ($definition->getChanges()['public'] ?? false) {
$alias->setPublic($definition->isPublic());
}
if ($definition->isDeprecated()) {
$alias->setDeprecated(...array_values($definition->getDeprecation('%alias_id%')));
}
}
$container->setDefinitions($resolvedDefinitions);
}
private function resolveStack(array $stacks, array $path): array
{
$definitions = [];
$id = end($path);
$prefix = '.'.$id.'.';
if (!isset($stacks[$id])) {
return [$id => new ChildDefinition($id)];
}
if (key($path) !== $searchKey = array_search($id, $path)) {
throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey));
}
foreach ($stacks[$id] as $k => $definition) {
if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) {
$path[] = $definition->getParent();
$definition = unserialize(serialize($definition)); // deep clone
} elseif ($definition instanceof Definition) {
$definitions[$decoratedId = $prefix.$k] = $definition;
continue;
} elseif ($definition instanceof Reference || $definition instanceof Alias) {
$path[] = (string) $definition;
} else {
throw new InvalidArgumentException(sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, get_debug_type($definition)));
}
$p = $prefix.$k;
foreach ($this->resolveStack($stacks, $path) as $k => $v) {
$definitions[$decoratedId = $p.$k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k);
$definition = null;
}
array_pop($path);
}
if (1 === \count($path)) {
foreach ($definitions as $k => $definition) {
$definition->setPublic(false)->setTags([])->setDecoratedService($decoratedId);
}
$definition->setDecoratedService(null);
}
return $definitions;
}
}

View File

@ -81,6 +81,18 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator
return $this->parent->get($id);
}
/**
* Registers a stack of decorator services.
*
* @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services
*/
final public function stack(string $id, array $services): AliasConfigurator
{
$this->__destruct();
return $this->parent->stack($id, $services);
}
/**
* Registers a service.
*/

View File

@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
@ -131,6 +132,39 @@ class ServicesConfigurator extends AbstractConfigurator
return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), true, $this, $definition, $id, []);
}
/**
* Registers a stack of decorator services.
*
* @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services
*/
final public function stack(string $id, array $services): AliasConfigurator
{
foreach ($services as $i => $service) {
if ($service instanceof InlineServiceConfigurator) {
$definition = $service->definition->setInstanceofConditionals($this->instanceof);
$changes = $definition->getChanges();
$definition->setAutowired((isset($changes['autowired']) ? $definition : $this->defaults)->isAutowired());
$definition->setAutoconfigured((isset($changes['autoconfigured']) ? $definition : $this->defaults)->isAutoconfigured());
$definition->setBindings(array_merge($this->defaults->getBindings(), $definition->getBindings()));
$definition->setChanges($changes);
$services[$i] = $definition;
} elseif (!$service instanceof ReferenceConfigurator) {
throw new InvalidArgumentException(sprintf('"%s()" expects a list of definitions as returned by "%s()" or "%s()", "%s" given at index "%s" for service "%s".', __METHOD__, InlineServiceConfigurator::FACTORY, ReferenceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY.'()' : get_debug_type($service)), $i, $id);
}
}
$alias = $this->alias($id, '');
$alias->definition = $this->set($id)
->parent('')
->args($services)
->tag('container.stack')
->definition;
return $alias;
}
/**
* Registers a service.
*/

View File

@ -112,12 +112,12 @@ class XmlFileLoader extends FileLoader
}
}
private function parseDefinitions(\DOMDocument $xml, string $file, array $defaults)
private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults)
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype')) {
if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype|//container:services/container:stack')) {
return;
}
$this->setCurrentDir(\dirname($file));
@ -126,12 +126,34 @@ class XmlFileLoader extends FileLoader
$this->isLoadingInstanceof = true;
$instanceof = $xpath->query('//container:services/container:instanceof');
foreach ($instanceof as $service) {
$this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, []));
$this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition()));
}
$this->isLoadingInstanceof = false;
foreach ($services as $service) {
if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
if ('stack' === $service->tagName) {
$service->setAttribute('parent', '-');
$definition = $this->parseDefinition($service, $file, $defaults)
->setTags(array_merge_recursive(['container.stack' => [[]]], $defaults->getTags()))
;
$this->setDefinition($id = (string) $service->getAttribute('id'), $definition);
$stack = [];
foreach ($this->getChildren($service, 'service') as $k => $frame) {
$k = $frame->getAttribute('id') ?: $k;
$frame->setAttribute('id', $id.'" at index "'.$k);
if ($alias = $frame->getAttribute('alias')) {
$this->validateAlias($frame, $file);
$stack[$k] = new Reference($alias);
} else {
$stack[$k] = $this->parseDefinition($frame, $file, $defaults)
->setInstanceofConditionals($this->instanceof);
}
}
$definition->setArguments($stack);
} elseif (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
if ('prototype' === $service->tagName) {
$excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue');
if ($service->hasAttribute('exclude')) {
@ -148,51 +170,24 @@ class XmlFileLoader extends FileLoader
}
}
/**
* Get service defaults.
*/
private function getServiceDefaults(\DOMDocument $xml, string $file): array
private function getServiceDefaults(\DOMDocument $xml, string $file): Definition
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) {
return [];
return new Definition();
}
$bindings = [];
foreach ($this->getArgumentsAsPhp($defaultsNode, 'bind', $file) as $argument => $value) {
$bindings[$argument] = new BoundArgument($value, true, BoundArgument::DEFAULTS_BINDING, $file);
}
$defaultsNode->setAttribute('id', '<defaults>');
$defaults = [
'tags' => $this->getChildren($defaultsNode, 'tag'),
'bind' => $bindings,
];
foreach ($defaults['tags'] as $tag) {
if ('' === $tag->getAttribute('name')) {
throw new InvalidArgumentException(sprintf('The tag name for tag "<defaults>" in "%s" must be a non-empty string.', $file));
}
}
if ($defaultsNode->hasAttribute('autowire')) {
$defaults['autowire'] = XmlUtils::phpize($defaultsNode->getAttribute('autowire'));
}
if ($defaultsNode->hasAttribute('public')) {
$defaults['public'] = XmlUtils::phpize($defaultsNode->getAttribute('public'));
}
if ($defaultsNode->hasAttribute('autoconfigure')) {
$defaults['autoconfigure'] = XmlUtils::phpize($defaultsNode->getAttribute('autoconfigure'));
}
return $defaults;
return $this->parseDefinition($defaultsNode, $file, new Definition());
}
/**
* Parses an individual Definition.
*/
private function parseDefinition(\DOMElement $service, string $file, array $defaults): ?Definition
private function parseDefinition(\DOMElement $service, string $file, Definition $defaults): ?Definition
{
if ($alias = $service->getAttribute('alias')) {
$this->validateAlias($service, $file);
@ -200,8 +195,8 @@ class XmlFileLoader extends FileLoader
$this->container->setAlias((string) $service->getAttribute('id'), $alias = new Alias($alias));
if ($publicAttr = $service->getAttribute('public')) {
$alias->setPublic(XmlUtils::phpize($publicAttr));
} elseif (isset($defaults['public'])) {
$alias->setPublic($defaults['public']);
} elseif ($defaults->getChanges()['public'] ?? false) {
$alias->setPublic($defaults->isPublic());
}
if ($deprecated = $this->getChildren($service, 'deprecated')) {
@ -231,16 +226,11 @@ class XmlFileLoader extends FileLoader
$definition = new Definition();
}
if (isset($defaults['public'])) {
$definition->setPublic($defaults['public']);
if ($defaults->getChanges()['public'] ?? false) {
$definition->setPublic($defaults->isPublic());
}
if (isset($defaults['autowire'])) {
$definition->setAutowired($defaults['autowire']);
}
if (isset($defaults['autoconfigure'])) {
$definition->setAutoconfigured($defaults['autoconfigure']);
}
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
$definition->setChanges([]);
foreach (['class', 'public', 'shared', 'synthetic', 'abstract'] as $key) {
@ -324,10 +314,6 @@ class XmlFileLoader extends FileLoader
$tags = $this->getChildren($service, 'tag');
if (!empty($defaults['tags'])) {
$tags = array_merge($tags, $defaults['tags']);
}
foreach ($tags as $tag) {
$parameters = [];
foreach ($tag->attributes as $name => $node) {
@ -349,16 +335,17 @@ class XmlFileLoader extends FileLoader
$definition->addTag($tag->getAttribute('name'), $parameters);
}
$definition->setTags(array_merge_recursive($definition->getTags(), $defaults->getTags()));
$bindings = $this->getArgumentsAsPhp($service, 'bind', $file);
$bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING;
foreach ($bindings as $argument => $value) {
$bindings[$argument] = new BoundArgument($value, true, $bindingType, $file);
}
if (isset($defaults['bind'])) {
// deep clone, to avoid multiple process of the same instance in the passes
$bindings = array_merge(unserialize(serialize($defaults['bind'])), $bindings);
}
// deep clone, to avoid multiple process of the same instance in the passes
$bindings = array_merge(unserialize(serialize($defaults->getBindings())), $bindings);
if ($bindings) {
$definition->setBindings($bindings);
}
@ -443,7 +430,7 @@ class XmlFileLoader extends FileLoader
// resolve definitions
uksort($definitions, 'strnatcmp');
foreach (array_reverse($definitions) as $id => list($domElement, $file)) {
if (null !== $definition = $this->parseDefinition($domElement, $file, [])) {
if (null !== $definition = $this->parseDefinition($domElement, $file, new Definition())) {
$this->setDefinition($id, $definition);
}
}

View File

@ -315,19 +315,20 @@ class YamlFileLoader extends FileLoader
*
* @throws InvalidArgumentException When tags are invalid
*/
private function parseDefinition(string $id, $service, string $file, array $defaults)
private function parseDefinition(string $id, $service, string $file, array $defaults, bool $return = false)
{
if (preg_match('/^_[a-zA-Z0-9_]*$/', $id)) {
throw new InvalidArgumentException(sprintf('Service names that start with an underscore are reserved. Rename the "%s" service or define it in XML instead.', $id));
}
if (\is_string($service) && 0 === strpos($service, '@')) {
$this->container->setAlias($id, $alias = new Alias(substr($service, 1)));
$alias = new Alias(substr($service, 1));
if (isset($defaults['public'])) {
$alias->setPublic($defaults['public']);
}
return;
return $return ? $alias : $this->container->setAlias($id, $alias);
}
if (\is_array($service) && $this->isUsingShortSyntax($service)) {
@ -342,10 +343,52 @@ 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.', get_debug_type($service), $id, $file));
}
if (isset($service['stack'])) {
if (!\is_array($service['stack'])) {
throw new InvalidArgumentException(sprintf('A stack must be an array of definitions, "%s" given for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $id, $file));
}
$stack = [];
foreach ($service['stack'] as $k => $frame) {
if (\is_array($frame) && 1 === \count($frame) && !isset(self::$serviceKeywords[key($frame)])) {
$frame = [
'class' => key($frame),
'arguments' => current($frame),
];
}
if (\is_array($frame) && isset($frame['stack'])) {
throw new InvalidArgumentException(sprintf('Service stack "%s" cannot contain another stack in "%s".', $id, $file));
}
$definition = $this->parseDefinition($id.'" at index "'.$k, $frame, $file, $defaults, true);
if ($definition instanceof Definition) {
$definition->setInstanceofConditionals($this->instanceof);
}
$stack[$k] = $definition;
}
if ($diff = array_diff(array_keys($service), ['stack', 'public', 'deprecated'])) {
throw new InvalidArgumentException(sprintf('Invalid attribute "%s"; supported ones are "public" and "deprecated" for service "%s" in "%s". Check your YAML syntax.', implode('", "', $diff), $id, $file));
}
$service = [
'parent' => '',
'arguments' => $stack,
'tags' => ['container.stack'],
'public' => $service['public'] ?? null,
'deprecated' => $service['deprecated'] ?? null,
];
}
$this->checkDefinition($id, $service, $file);
if (isset($service['alias'])) {
$this->container->setAlias($id, $alias = new Alias($service['alias']));
$alias = new Alias($service['alias']);
if (isset($service['public'])) {
$alias->setPublic($service['public']);
} elseif (isset($defaults['public'])) {
@ -372,7 +415,7 @@ class YamlFileLoader extends FileLoader
}
}
return;
return $return ? $alias : $this->container->setAlias($id, $alias);
}
if ($this->isLoadingInstanceof) {
@ -426,7 +469,7 @@ class YamlFileLoader extends FileLoader
$definition->setAbstract($service['abstract']);
}
if (\array_key_exists('deprecated', $service)) {
if (isset($service['deprecated'])) {
$deprecation = \is_array($service['deprecated']) ? $service['deprecated'] : ['message' => $service['deprecated']];
if (!isset($deprecation['package'])) {
@ -601,6 +644,14 @@ class YamlFileLoader extends FileLoader
throw new InvalidArgumentException(sprintf('A "resource" attribute must be set when the "namespace" attribute is set for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}
if ($return) {
if (\array_key_exists('resource', $service)) {
throw new InvalidArgumentException(sprintf('Invalid "resource" attribute found for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}
return $definition;
}
if (\array_key_exists('resource', $service)) {
if (!\is_string($service['resource'])) {
throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.', $id, $file));

View File

@ -57,6 +57,7 @@
<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:element name="stack" type="stack" minOccurs="0" />
</xsd:choice>
</xsd:complexType>
@ -176,6 +177,15 @@
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="stack">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="service" type="service" minOccurs="1" />
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="public" type="boolean" />
</xsd:complexType>
<xsd:complexType name="tag">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:anyAttribute namespace="##any" processContents="lax" />

View File

@ -0,0 +1,50 @@
<?php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo;
return function (ContainerConfigurator $c) {
$services = $c->services();
$services->stack('stack_a', [
service('stdClass')
->property('label', 'A')
->property('inner', ref('.inner')),
service('stdClass')
->property('label', 'B')
->property('inner', ref('.inner')),
service('stdClass')
->property('label', 'C'),
])->public();
$services->stack('stack_abstract', [
service('stdClass')
->property('label', 'A')
->property('inner', ref('.inner')),
service('stdClass')
->property('label', 'B')
->property('inner', ref('.inner')),
]);
$services->stack('stack_b', [
ref('stack_abstract'),
service('stdClass')
->property('label', 'C'),
])->public();
$services->stack('stack_c', [
service('stdClass')
->property('label', 'Z')
->property('inner', ref('.inner')),
ref('stack_a'),
])->public();
$services->stack('stack_d', [
service()
->parent('stack_abstract')
->property('label', 'Z'),
service('stdClass')
->property('label', 'C'),
])->public();
};

View File

@ -0,0 +1,53 @@
<?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 https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<stack id="stack_a" public="true">
<service class="stdClass">
<property name="label">A</property>
<property name="inner" type="service" id=".inner" />
</service>
<service class="stdClass">
<property name="label">B</property>
<property name="inner" type="service" id=".inner" />
</service>
<service class="stdClass">
<property name="label">C</property>
</service>
</stack>
<stack id="stack_abstract">
<service class="stdClass" abstract="true">
<property name="label">A</property>
<property name="inner" type="service" id=".inner" />
</service>
<service class="stdClass">
<property name="label">B</property>
<property name="inner" type="service" id=".inner" />
</service>
</stack>
<stack id="stack_b" public="true">
<service alias="stack_abstract" />
<service class="stdClass">
<property name="label">C</property>
</service>
</stack>
<stack id="stack_c" public="true">
<service class="stdClass">
<property name="label">Z</property>
<property name="inner" type="service" id=".inner" />
</service>
<service alias="stack_a" />
</stack>
<stack id="stack_d" public="true">
<service parent="stack_abstract">
<property name="label">Z</property>
</service>
<service class="stdClass">
<property name="label">C</property>
</service>
</stack>
</services>
</container>

View File

@ -0,0 +1,67 @@
services:
stack_short:
stack:
- stdClass: [1, 2]
stack_a:
public: true
stack:
- class: stdClass
properties:
label: A
inner: '@.inner'
- class: stdClass
properties:
label: B
inner: '@.inner'
- class: stdClass
properties:
label: C
stack_abstract:
stack:
- class: stdClass
abstract: true
properties:
label: A
inner: '@.inner'
- class: stdClass
properties:
label: B
inner: '@.inner'
stack_b:
public: true
stack:
- alias: 'stack_abstract'
- class: stdClass
properties:
label: C
stack_c:
public: true
stack:
- class: stdClass
properties:
label: Z
inner: '@.inner'
- '@stack_a'
stack_d:
public: true
stack:
- parent: 'stack_abstract'
properties:
label: 'Z'
- class: stdClass
properties:
label: C
stack_e:
public: true
stack:
- class: stdClass
properties:
label: Y
inner: '@.inner'
- '@stack_d'

View File

@ -101,6 +101,38 @@ class PhpFileLoaderTest extends TestCase
$container->compile();
}
public function testStack()
{
$container = new ContainerBuilder();
$loader = new PhpFileLoader($container, new FileLocator(realpath(__DIR__.'/../Fixtures').'/config'));
$loader->load('stack.php');
$container->compile();
$expected = (object) [
'label' => 'A',
'inner' => (object) [
'label' => 'B',
'inner' => (object) [
'label' => 'C',
],
],
];
$this->assertEquals($expected, $container->get('stack_a'));
$this->assertEquals($expected, $container->get('stack_b'));
$expected = (object) [
'label' => 'Z',
'inner' => $expected,
];
$this->assertEquals($expected, $container->get('stack_c'));
$expected = $expected->inner;
$expected->label = 'Z';
$this->assertEquals($expected, $container->get('stack_d'));
}
/**
* @group legacy
* @expectedDeprecation Since symfony/dependency-injection 5.1: The signature of method "Symfony\Component\DependencyInjection\Loader\Configurator\Traits\DeprecateTrait::deprecate()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.

View File

@ -1039,4 +1039,36 @@ class XmlFileLoaderTest extends TestCase
$arguments = $container->getDefinition(FooWithAbstractArgument::class)->getArguments();
$this->assertInstanceOf(AbstractArgument::class, $arguments['$baz']);
}
public function testStack()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('stack.xml');
$container->compile();
$expected = (object) [
'label' => 'A',
'inner' => (object) [
'label' => 'B',
'inner' => (object) [
'label' => 'C',
],
],
];
$this->assertEquals($expected, $container->get('stack_a'));
$this->assertEquals($expected, $container->get('stack_b'));
$expected = (object) [
'label' => 'Z',
'inner' => $expected,
];
$this->assertEquals($expected, $container->get('stack_c'));
$expected = $expected->inner;
$expected->label = 'Z';
$this->assertEquals($expected, $container->get('stack_d'));
}
}

View File

@ -958,4 +958,44 @@ class YamlFileLoaderTest extends TestCase
$this->assertSame($expected, $container->getDefinition('foo')->getMethodCalls());
}
public function testStack()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('stack.yaml');
$this->assertSame([1, 2], $container->getDefinition('stack_short')->getArguments()[0]->getArguments());
$container->compile();
$expected = (object) [
'label' => 'A',
'inner' => (object) [
'label' => 'B',
'inner' => (object) [
'label' => 'C',
],
],
];
$this->assertEquals($expected, $container->get('stack_a'));
$this->assertEquals($expected, $container->get('stack_b'));
$expected = (object) [
'label' => 'Z',
'inner' => $expected,
];
$this->assertEquals($expected, $container->get('stack_c'));
$expected = $expected->inner;
$expected->label = 'Z';
$this->assertEquals($expected, $container->get('stack_d'));
$expected = (object) [
'label' => 'Y',
'inner' => $expected,
];
$this->assertEquals($expected, $container->get('stack_e'));
}
}