Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d4378d7

Browse filesBrowse files
[DI] add syntax to stack decorators
1 parent 168574d commit d4378d7
Copy full SHA for d4378d7

File tree

14 files changed

+438
-1
lines changed
Filter options

14 files changed

+438
-1
lines changed

‎src/Symfony/Component/DependencyInjection/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.1.0
55
-----
66

7+
* added syntax to stack decorators
78
* added support to autowire public typed properties in php 7.4
89
* added support for defining method calls, a configurator, and property setters in `InlineServiceConfigurator`
910
* added possibility to define abstract service arguments

‎src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public function __construct()
5151
$this->optimizationPasses = [[
5252
new AutoAliasServicePass(),
5353
new ValidateEnvPlaceholdersPass(),
54+
new ResolveDecoratorStackPass(),
5455
new ResolveChildDefinitionsPass(),
5556
new RegisterServiceSubscribersPass(),
5657
new ResolveParameterPlaceHoldersPass(false, false),
+79Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ChildDefinition;
15+
use Symfony\Component\DependencyInjection\ContainerInterface;
16+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
17+
18+
/**
19+
* Resolves stacks referenced by decorators or within a stack.
20+
*
21+
* @author Nicolas Grekas <p@tchwork.com>
22+
*/
23+
class ResolveDecoratorStackPass implements CompilerPassInterface
24+
{
25+
private $tag;
26+
27+
public function __construct(string $tag = 'container.stack')
28+
{
29+
$this->tag = $tag;
30+
}
31+
32+
public function process(ContainerInterface $container)
33+
{
34+
$definitions = $container->getDefinitions();
35+
$resolvedDefinitions = [];
36+
37+
foreach ($definitions as $id => $definition) {
38+
if (!$definition instanceof ChildDefinition
39+
|| !$container->has($parent = $definition->getParent())
40+
|| !$nextId = $container->findDefinition($parent)->getTag($this->tag)[0]['next_id'] ?? null
41+
) {
42+
$resolvedDefinitions[$id] = $definition;
43+
44+
continue;
45+
}
46+
47+
if (!$decoration = $definition->getDecoratedService()) {
48+
throw new InvalidArgumentException(sprintf('Service "%s" is neither a stack nor a decorator: it cannot use service "%s" as a "parent".', $id, $parent));
49+
}
50+
51+
$decoration[1] = null;
52+
$resolvedStack = [$id => $definition];
53+
54+
do {
55+
$nextDef = $container->findDefinition($nextId);
56+
$nextDecoration = [$decoration[0]] + ($nextDef->getDecoratedService() ?? [1 => null, 0]);
57+
$nextDecoration[2] = $decoration[2] ?? $nextDecoration[2];
58+
$resolvedStack['.'.ltrim($id, '.').$nextId] = (new ChildDefinition($nextId))->setDecoratedService(...$nextDecoration);
59+
} while (null !== $nextId = $nextDef->getTag($this->tag)[0]['next_id'] ?? null);
60+
61+
$nextDecoration[3] = $decoration[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
62+
end($resolvedStack)->setDecoratedService(...$nextDecoration);
63+
64+
$nextId = $definition->getTag($this->tag)[0]['next_id'] ?? null;
65+
66+
foreach (array_reverse($resolvedStack, true) as $id => $definition) {
67+
if (null !== $nextId) {
68+
$definition->clearTag($this->tag);
69+
$definition->addTag($this->tag, ['next_id' => $nextId]);
70+
}
71+
72+
$nextId = $id;
73+
$resolvedDefinitions[$id] = $definition;
74+
}
75+
}
76+
77+
$container->setDefinitions($resolvedDefinitions);
78+
}
79+
}

‎src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ final public function get(string $id): ServiceConfigurator
8181
return $this->parent->get($id);
8282
}
8383

84+
/**
85+
* Registers a stack of decorator services.
86+
*
87+
* @param InlineServiceConfigurator[] $services
88+
*/
89+
final public function stack(string $id, array $services): ServiceConfigurator
90+
{
91+
$this->__destruct();
92+
93+
return $this->parent->stack($id, $services);
94+
}
95+
8496
/**
8597
* Registers a service.
8698
*/

‎src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\ChildDefinition;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Definition;
18+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1819
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1920
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
2021

@@ -132,6 +133,48 @@ final public function get(string $id): ServiceConfigurator
132133
return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), $allowParent, $this, $definition, $id, []);
133134
}
134135

136+
/**
137+
* Registers a stack of decorator services.
138+
*
139+
* @param InlineServiceConfigurator[] $services
140+
*/
141+
final public function stack(string $id, array $services): ServiceConfigurator
142+
{
143+
$decoratedId = null;
144+
$firstFrameIndex = key($services);
145+
146+
foreach (array_reverse($services, true) as $i => $service) {
147+
if (null === $decoratedId) {
148+
$decoratedId = $stackId = $i !== $firstFrameIndex ? '.'.$id.'.'.$i : $id;
149+
150+
if ($service instanceof ReferenceConfigurator) {
151+
if ($i === $firstFrameIndex) {
152+
throw new InvalidArgumentException(sprintf('Service stack "%s" should define at least two frames.', $id));
153+
}
154+
155+
$this->alias($stackId, (string) $service);
156+
continue;
157+
}
158+
} elseif ($service instanceof InlineServiceConfigurator) {
159+
$service->definition->addTag('container.stack', [
160+
'next_id' => $stackId,
161+
]);
162+
$service->definition->setDecoratedService($decoratedId);
163+
$stackId = $i !== $firstFrameIndex ? '.'.$id.'.'.$i : $id;
164+
}
165+
166+
if (!$service instanceof InlineServiceConfigurator) {
167+
throw new InvalidArgumentException(sprintf('"%s()" expects a list of definitions as returned by "%s()", "%s" given.', __METHOD__, InlineServiceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY.'()' : get_debug_type($service)));
168+
}
169+
170+
$definition = $service->definition;
171+
$service = $this->set($stackId);
172+
$service->definition = $definition;
173+
}
174+
175+
return $service;
176+
}
177+
135178
/**
136179
* Registers a service.
137180
*/

‎src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+34-1Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private function parseDefinitions(\DOMDocument $xml, string $file, array $defaul
117117
$xpath = new \DOMXPath($xml);
118118
$xpath->registerNamespace('container', self::NS);
119119

120-
if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype')) {
120+
if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype|//container:services/container:stack')) {
121121
return;
122122
}
123123
$this->setCurrentDir(\dirname($file));
@@ -131,6 +131,39 @@ private function parseDefinitions(\DOMDocument $xml, string $file, array $defaul
131131

132132
$this->isLoadingInstanceof = false;
133133
foreach ($services as $service) {
134+
if ('stack' === $service->tagName) {
135+
$decoratedId = null;
136+
$id = (string) $service->getAttribute('id');
137+
138+
foreach (array_reverse($this->getChildren($service, 'service'), true) as $i => $service) {
139+
$service->setAttribute('id', $i ? '.'.$id.'.'.($service->getAttribute('id') ?: $i) : $id);
140+
$definition = $this->parseDefinition($service, $file, $defaults);
141+
142+
if (null === $decoratedId) {
143+
$decoratedId = $stackId = $service->getAttribute('id');
144+
145+
if (!$i && !$definition) {
146+
throw new InvalidArgumentException(sprintf('Service stack "%s" should define at least two frames.', $id));
147+
}
148+
} elseif (!$definition) {
149+
throw new InvalidArgumentException(sprintf('Invalid frame at position "%s" in service "%s": you cannot use an alias in the middle of a stack.', $i, $id));
150+
} else {
151+
$definition->addTag('container.stack', [
152+
'name' => 'container.stack',
153+
'next_id' => $stackId,
154+
]);
155+
$definition->setDecoratedService($decoratedId);
156+
$stackId = $service->getAttribute('id');
157+
}
158+
159+
if (null !== $definition) {
160+
$this->setDefinition($stackId, $definition);
161+
}
162+
}
163+
164+
continue;
165+
}
166+
134167
if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
135168
if ('prototype' === $service->tagName) {
136169
$excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue');

‎src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+39Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,45 @@ private function parseDefinition(string $id, $service, string $file, array $defa
342342
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));
343343
}
344344

345+
if (['stack'] === array_keys($service) && \is_array($service['stack']) && $service['stack']) {
346+
$decoratedId = null;
347+
$firstFrameIndex = key($service['stack']);
348+
349+
foreach (array_reverse($service['stack'], true) as $i => $service) {
350+
if (\is_array($service) && 1 === \count($service) && !isset(self::$serviceKeywords[key($service)])) {
351+
$service = [
352+
'class' => key($service),
353+
'arguments' => current($service),
354+
];
355+
}
356+
357+
if (\is_array($service) && \array_key_exists('stack', $service)) {
358+
throw new InvalidArgumentException(sprintf('Service stack "%s" cannot contain another stack in "%s".', $id, $file));
359+
}
360+
361+
if (null === $decoratedId) {
362+
$decoratedId = $stackId = $i !== $firstFrameIndex ? '.'.$id.'.'.$i : $id;
363+
364+
if ($i === $firstFrameIndex && (!\is_array($service) || isset($service['alias']))) {
365+
throw new InvalidArgumentException(sprintf('Service stack "%s" should define at least two frames in "%s".', $id, $file));
366+
}
367+
} elseif (!\is_array($service)) {
368+
throw new InvalidArgumentException(sprintf('A stacked service definition must be an array but "%s" found at position "%s" for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $i, $id, $file));
369+
} else {
370+
$service['tags'][] = [
371+
'name' => 'container.stack',
372+
'next_id' => $stackId,
373+
];
374+
$service['decorates'] = $decoratedId;
375+
$stackId = $i !== $firstFrameIndex ? '.'.$id.'.'.$i : $id;
376+
}
377+
378+
$this->parseDefinition($stackId, $service, $file, $defaults);
379+
}
380+
381+
return;
382+
}
383+
345384
$this->checkDefinition($id, $service, $file);
346385

347386
if (isset($service['alias'])) {

‎src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<xsd:element name="prototype" type="prototype" minOccurs="0" />
5858
<xsd:element name="defaults" type="defaults" minOccurs="0" maxOccurs="1" />
5959
<xsd:element name="instanceof" type="instanceof" minOccurs="0" />
60+
<xsd:element name="stack" type="stack" minOccurs="0" />
6061
</xsd:choice>
6162
</xsd:complexType>
6263

@@ -176,6 +177,13 @@
176177
<xsd:attribute name="autoconfigure" type="boolean" />
177178
</xsd:complexType>
178179

180+
<xsd:complexType name="stack">
181+
<xsd:choice maxOccurs="unbounded">
182+
<xsd:element name="service" type="service" minOccurs="1" />
183+
</xsd:choice>
184+
<xsd:attribute name="id" type="xsd:string" use="required" />
185+
</xsd:complexType>
186+
179187
<xsd:complexType name="tag">
180188
<xsd:attribute name="name" type="xsd:string" use="required" />
181189
<xsd:anyAttribute namespace="##any" processContents="lax" />
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
4+
5+
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo;
6+
7+
return function (ContainerConfigurator $c) {
8+
$services = $c->services();
9+
10+
$services->stack('stack_a', [
11+
service('stdClass')
12+
->property('label', 'A')
13+
->property('inner', ref('.inner')),
14+
service('stdClass')
15+
->property('label', 'B')
16+
->property('inner', ref('.inner')),
17+
service('stdClass')
18+
->property('label', 'C'),
19+
])->public();
20+
21+
$services->stack('stack_abstract', [
22+
service('stdClass')
23+
->property('label', 'A')
24+
->property('inner', ref('.inner')),
25+
service('stdClass')
26+
->property('label', 'B')
27+
->property('inner', ref('.inner')),
28+
])->abstract();
29+
30+
$services->stack('stack_b', [
31+
service()
32+
->parent('stack_abstract'),
33+
service('stdClass')
34+
->property('label', 'C'),
35+
])->public();
36+
37+
$services->stack('stack_c', [
38+
service('stdClass')
39+
->property('label', 'Z')
40+
->property('inner', ref('.inner')),
41+
ref('stack_a'),
42+
])->public();
43+
};
+44Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<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">
3+
<services>
4+
<stack id="stack_a">
5+
<service class="stdClass" public="true">
6+
<property name="label">A</property>
7+
<property name="inner" type="service" id=".inner" />
8+
</service>
9+
<service class="stdClass">
10+
<property name="label">B</property>
11+
<property name="inner" type="service" id=".inner" />
12+
</service>
13+
<service class="stdClass">
14+
<property name="label">C</property>
15+
</service>
16+
</stack>
17+
18+
<stack id="stack_abstract">
19+
<service class="stdClass" abstract="true">
20+
<property name="label">A</property>
21+
<property name="inner" type="service" id=".inner" />
22+
</service>
23+
<service class="stdClass">
24+
<property name="label">B</property>
25+
<property name="inner" type="service" id=".inner" />
26+
</service>
27+
</stack>
28+
29+
<stack id="stack_b">
30+
<service parent="stack_abstract" public="true" />
31+
<service class="stdClass">
32+
<property name="label">C</property>
33+
</service>
34+
</stack>
35+
36+
<stack id="stack_c">
37+
<service class="stdClass" public="true">
38+
<property name="label">Z</property>
39+
<property name="inner" type="service" id=".inner" />
40+
</service>
41+
<service alias="stack_a" />
42+
</stack>
43+
</services>
44+
</container>

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.