Skip to content

Commit a1749b9

Browse files
[DI] add syntax to stack decorators
1 parent bfe6b6f commit a1749b9

24 files changed

+182
-153
lines changed

UPGRADE-5.1.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ DependencyInjection
2121
* The signature of method `DeprecateTrait::deprecate()` has been updated to `DeprecateTrait::deprecation(string $package, string $version, string $message)`.
2222
* Deprecated the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service,
2323
configure them explicitly instead.
24+
* The `inline()` function from the PHP-DSL has been deprecated, use `service()` instead
2425

2526
Dotenv
2627
------

UPGRADE-6.0.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ DependencyInjection
2121
* The signature of method `DeprecateTrait::deprecate()` has been updated to `DeprecateTrait::deprecation(string $package, string $version, string $message)`.
2222
* Removed the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service,
2323
configure them explicitly instead.
24+
* The `inline()` function from the PHP-DSL has been removed, use `service()` instead
2425

2526
Dotenv
2627
------

src/Symfony/Component/DependencyInjection/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ CHANGELOG
1414
configure them explicitly instead
1515
* added class `Symfony\Component\DependencyInjection\Dumper\Preloader` to help with preloading on PHP 7.4+
1616
* added tags `container.preload`/`.no_preload` to declare extra classes to preload/services to not preload
17+
* added syntax to stack decorators easily
18+
* deprecated PHP-DSL's `inline()` function, use `service()` instead
1719

1820
5.0.0
1921
-----

src/Symfony/Component/DependencyInjection/ChildDefinition.php

-16
Original file line numberDiff line numberDiff line change
@@ -105,20 +105,4 @@ public function replaceArgument($index, $value)
105105

106106
return $this;
107107
}
108-
109-
/**
110-
* @internal
111-
*/
112-
public function setAutoconfigured(bool $autoconfigured): self
113-
{
114-
throw new BadMethodCallException('A ChildDefinition cannot be autoconfigured.');
115-
}
116-
117-
/**
118-
* @internal
119-
*/
120-
public function setInstanceofConditionals(array $instanceof): self
121-
{
122-
throw new BadMethodCallException('A ChildDefinition cannot have instanceof conditionals set on it.');
123-
}
124108
}

src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php

+21-1
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@
2424
* @author Fabien Potencier <fabien@symfony.com>
2525
* @author Diego Saint Esteben <diego@saintesteben.me>
2626
*/
27-
class DecoratorServicePass implements CompilerPassInterface
27+
class DecoratorServicePass extends AbstractRecursivePass
2828
{
29+
private $innerId = '.inner';
30+
31+
public function __construct(?string $innerId = '.inner')
32+
{
33+
$this->innerId = $innerId;
34+
}
35+
2936
public function process(ContainerBuilder $container)
3037
{
3138
$definitions = new \SplPriorityQueue();
@@ -49,6 +56,10 @@ public function process(ContainerBuilder $container)
4956
if (!$renamedId) {
5057
$renamedId = $id.'.inner';
5158
}
59+
60+
$this->currentId = $renamedId;
61+
$this->processValue($definition);
62+
5263
$definition->innerServiceId = $renamedId;
5364
$definition->decorationOnInvalid = $invalidBehavior;
5465

@@ -96,4 +107,13 @@ public function process(ContainerBuilder $container)
96107
$container->setAlias($inner, $id)->setPublic($public)->setPrivate($private);
97108
}
98109
}
110+
111+
protected function processValue($value, bool $isRoot = false)
112+
{
113+
if ($value instanceof Reference && $this->innerId === (string) $value) {
114+
return new Reference($this->currentId, $value->getInvalidBehavior());
115+
}
116+
117+
return parent::processValue($value, $isRoot);
118+
}
99119
}

src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php

+8-9
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ public function process(ContainerBuilder $container)
3636
}
3737

3838
foreach ($container->getDefinitions() as $id => $definition) {
39-
if ($definition instanceof ChildDefinition) {
40-
// don't apply "instanceof" to children: it will be applied to their parent
41-
continue;
42-
}
4339
$container->setDefinition($id, $this->processDefinition($container, $id, $definition));
4440
}
4541
}
@@ -59,11 +55,12 @@ private function processDefinition(ContainerBuilder $container, string $id, Defi
5955
$conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container);
6056

6157
$definition->setInstanceofConditionals([]);
62-
$parent = $shared = null;
58+
$shared = null;
6359
$instanceofTags = [];
6460
$instanceofCalls = [];
6561
$instanceofBindings = [];
6662
$reflectionClass = null;
63+
$parent = $definition instanceof ChildDefinition ? $definition->getParent() : null;
6764

6865
foreach ($conditionals as $interface => $instanceofDefs) {
6966
if ($interface !== $class && !(null === $reflectionClass ? $reflectionClass = ($container->getReflectionClass($class, false) ?: false) : $reflectionClass)) {
@@ -100,12 +97,14 @@ private function processDefinition(ContainerBuilder $container, string $id, Defi
10097
if ($parent) {
10198
$bindings = $definition->getBindings();
10299
$abstract = $container->setDefinition('.abstract.instanceof.'.$id, $definition);
103-
104-
// cast Definition to ChildDefinition
105100
$definition->setBindings([]);
106101
$definition = serialize($definition);
107-
$definition = substr_replace($definition, '53', 2, 2);
108-
$definition = substr_replace($definition, 'Child', 44, 0);
102+
103+
if (Definition::class === \get_class($abstract)) {
104+
// cast Definition to ChildDefinition
105+
$definition = substr_replace($definition, '53', 2, 2);
106+
$definition = substr_replace($definition, 'Child', 44, 0);
107+
}
109108
/** @var ChildDefinition $definition */
110109
$definition = unserialize($definition);
111110
$definition->setParent($parent);

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

+29
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,35 @@ 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): ServicesConfigurator
90+
{
91+
$this->__destruct();
92+
93+
$stackId = null;
94+
95+
foreach (array_reverse($services, true) as $i => $definition) {
96+
if (!$definition instanceof InlineServiceConfigurator) {
97+
throw new InvalidArgumentException(sprintf('"%s()" expects a list of definitions as returned by "%s()", "%s" given.', __METHOD__, namespace\service::class, get_debug_type($definition)));
98+
}
99+
100+
if (null === $stackId) {
101+
$stackId = $id;
102+
} else {
103+
$stackId = '.stack.'.$id.'.'.$i;
104+
$definition->decorates($id);
105+
}
106+
107+
$this->parent->set($stackId)->definition = $definition->definition;
108+
}
109+
110+
return $this->parent;
111+
}
112+
84113
/**
85114
* Registers a service.
86115
*/

src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php

+12
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,20 @@ function ref(string $id): ReferenceConfigurator
9292

9393
/**
9494
* Creates an inline service.
95+
*
96+
* @deprecated since Symfony 5.1, use service() instead.
9597
*/
9698
function inline(string $class = null): InlineServiceConfigurator
99+
{
100+
trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "service()" instead.', __FUNCTION__);
101+
102+
return new InlineServiceConfigurator(new Definition($class));
103+
}
104+
105+
/**
106+
* Creates an inline service.
107+
*/
108+
function service(string $class = null): InlineServiceConfigurator
97109
{
98110
return new InlineServiceConfigurator(new Definition($class));
99111
}

src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php

+1-7
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14-
use Symfony\Component\DependencyInjection\ChildDefinition;
1514
use Symfony\Component\DependencyInjection\ContainerBuilder;
1615
use Symfony\Component\DependencyInjection\Definition;
1716

@@ -62,11 +61,6 @@ public function __destruct()
6261
parent::__destruct();
6362

6463
$this->container->removeBindings($this->id);
65-
66-
if (!$this->definition instanceof ChildDefinition) {
67-
$this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof));
68-
} else {
69-
$this->container->setDefinition($this->id, $this->definition);
70-
}
64+
$this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof));
7165
}
7266
}

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

+3-8
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ final public function instanceof(string $fqcn): InstanceofConfigurator
7272
final public function set(?string $id, string $class = null): ServiceConfigurator
7373
{
7474
$defaults = $this->defaults;
75-
$allowParent = !$defaults->getChanges() && empty($this->instanceof);
76-
7775
$definition = new Definition();
7876

7977
if (null === $id) {
@@ -92,7 +90,7 @@ final public function set(?string $id, string $class = null): ServiceConfigurato
9290
$definition->setBindings($defaults->getBindings());
9391
$definition->setChanges([]);
9492

95-
$configurator = new ServiceConfigurator($this->container, $this->instanceof, $allowParent, $this, $definition, $id, $defaults->getTags(), $this->path);
93+
$configurator = new ServiceConfigurator($this->container, $this->instanceof, true, $this, $definition, $id, $defaults->getTags(), $this->path);
9694

9795
return null !== $class ? $configurator->class($class) : $configurator;
9896
}
@@ -114,9 +112,7 @@ final public function alias(string $id, string $referencedId): AliasConfigurator
114112
*/
115113
final public function load(string $namespace, string $resource): PrototypeConfigurator
116114
{
117-
$allowParent = !$this->defaults->getChanges() && empty($this->instanceof);
118-
119-
return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, $allowParent);
115+
return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, true);
120116
}
121117

122118
/**
@@ -126,10 +122,9 @@ final public function load(string $namespace, string $resource): PrototypeConfig
126122
*/
127123
final public function get(string $id): ServiceConfigurator
128124
{
129-
$allowParent = !$this->defaults->getChanges() && empty($this->instanceof);
130125
$definition = $this->container->getDefinition($id);
131126

132-
return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), $allowParent, $this, $definition, $id, []);
127+
return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), true, $this, $definition, $id, []);
133128
}
134129

135130
/**

src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/AutoconfigureTrait.php

-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
1313

14-
use Symfony\Component\DependencyInjection\ChildDefinition;
1514
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1615

1716
trait AutoconfigureTrait
@@ -25,9 +24,6 @@ trait AutoconfigureTrait
2524
*/
2625
final public function autoconfigure(bool $autoconfigured = true): self
2726
{
28-
if ($autoconfigured && $this->definition instanceof ChildDefinition) {
29-
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try disabling autoconfiguration for the service.', $this->id));
30-
}
3127
$this->definition->setAutoconfigured($autoconfigured);
3228

3329
return $this;

src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/ParentTrait.php

-4
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ final public function parent(string $parent): self
3131

3232
if ($this->definition instanceof ChildDefinition) {
3333
$this->definition->setParent($parent);
34-
} elseif ($this->definition->isAutoconfigured()) {
35-
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try disabling autoconfiguration for the service.', $this->id));
36-
} elseif ($this->definition->getBindings()) {
37-
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also "bind" arguments.', $this->id));
3834
} else {
3935
// cast Definition to ChildDefinition
4036
$definition = serialize($this->definition);

src/Symfony/Component/DependencyInjection/Loader/FileLoader.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ protected function setDefinition($id, Definition $definition)
147147
}
148148
$this->instanceof[$id] = $definition;
149149
} else {
150-
$this->container->setDefinition($id, $definition instanceof ChildDefinition ? $definition : $definition->setInstanceofConditionals($this->instanceof));
150+
$this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof));
151151
}
152152
}
153153

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

+31-39
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,24 @@ 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+
$stackId = null;
136+
$id = (string) $service->getAttribute('id');
137+
138+
foreach (array_reverse($this->getChildren($service, 'service'), true) as $i => $definition) {
139+
if (null === $stackId) {
140+
$stackId = $id;
141+
} else {
142+
$stackId = '.stack.'.$id.'.'.($definition->getAttribute('id') ?: $i);
143+
$definition->setAttribute('decorates', $id);
144+
}
145+
146+
$this->setDefinition($stackId, $definition);
147+
}
148+
149+
continue;
150+
}
151+
134152
if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
135153
if ('prototype' === $service->tagName) {
136154
$excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue');
@@ -226,44 +244,22 @@ private function parseDefinition(\DOMElement $service, string $file, array $defa
226244
if ($this->isLoadingInstanceof) {
227245
$definition = new ChildDefinition('');
228246
} elseif ($parent = $service->getAttribute('parent')) {
229-
if (!empty($this->instanceof)) {
230-
throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "instanceof" configuration is defined as using both is not supported. Move your child definitions to a separate file.', $service->getAttribute('id')));
231-
}
232-
233-
foreach ($defaults as $k => $v) {
234-
if ('tags' === $k) {
235-
// since tags are never inherited from parents, there is no confusion
236-
// thus we can safely add them as defaults to ChildDefinition
237-
continue;
238-
}
239-
if ('bind' === $k) {
240-
if ($defaults['bind']) {
241-
throw new InvalidArgumentException(sprintf('Bound values on service "%s" cannot be inherited from "defaults" when a "parent" is set. Move your child definitions to a separate file.', $service->getAttribute('id')));
242-
}
243-
244-
continue;
245-
}
246-
if (!$service->hasAttribute($k)) {
247-
throw new InvalidArgumentException(sprintf('Attribute "%s" on service "%s" cannot be inherited from "defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly.', $k, $service->getAttribute('id')));
248-
}
249-
}
250-
251247
$definition = new ChildDefinition($parent);
252248
} else {
253249
$definition = new Definition();
250+
}
254251

255-
if (isset($defaults['public'])) {
256-
$definition->setPublic($defaults['public']);
257-
}
258-
if (isset($defaults['autowire'])) {
259-
$definition->setAutowired($defaults['autowire']);
260-
}
261-
if (isset($defaults['autoconfigure'])) {
262-
$definition->setAutoconfigured($defaults['autoconfigure']);
263-
}
264-
265-
$definition->setChanges([]);
252+
if (isset($defaults['public'])) {
253+
$definition->setPublic($defaults['public']);
254+
}
255+
if (isset($defaults['autowire'])) {
256+
$definition->setAutowired($defaults['autowire']);
266257
}
258+
if (isset($defaults['autoconfigure'])) {
259+
$definition->setAutoconfigured($defaults['autoconfigure']);
260+
}
261+
262+
$definition->setChanges([]);
267263

268264
foreach (['class', 'public', 'shared', 'synthetic', 'abstract'] as $key) {
269265
if ($value = $service->getAttribute($key)) {
@@ -284,11 +280,7 @@ private function parseDefinition(\DOMElement $service, string $file, array $defa
284280
}
285281

286282
if ($value = $service->getAttribute('autoconfigure')) {
287-
if (!$definition instanceof ChildDefinition) {
288-
$definition->setAutoconfigured(XmlUtils::phpize($value));
289-
} elseif ($value = XmlUtils::phpize($value)) {
290-
throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting autoconfigure="false" for the service.', $service->getAttribute('id')));
291-
}
283+
$definition->setAutoconfigured(XmlUtils::phpize($value));
292284
}
293285

294286
if ($files = $this->getChildren($service, 'file')) {

0 commit comments

Comments
 (0)