Skip to content

Commit 71873fc

Browse files
committed
feature #33854 [DI] Add ability to choose behavior of decorations on non existent decorated services (mtarld)
This PR was merged into the 4.4 branch. Discussion ---------- [DI] Add ability to choose behavior of decorations on non existent decorated services | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | #33522 | License | MIT | Doc PR | symfony/symfony-docs#12442 # Handling decorations on non existent decorated services Handle decorations on non existent decorated services by either throwing the service not found exception, silently ignoring services (decorator & decorated) all together or leave the decorated service to null (current behavior) Something almost similar to how missing services as parameters are handles. ## Yaml configuration ```yaml decorator: decorates: decorated decoration_on_invalid: ignore ``` Available values: `exception`, `ignore`, `null`. `exception` if nothing is specified. ## Xml configuration ```xml <service id="decorator" decorates="decorated" decoration-on-invalid="ignore" /> ``` Available values: `exception`, `ignore`, `null`. `exception` if nothing is specified. ## Behavior - `exception`: Throws a `ServiceNotFoundException` telling that the decorator's dependency is missing - `ignore`: Remove decorator definition. Decorator and decorated will not be available at all. - `null`: Keep decorator but set decorated to null. Therefore, decorator `__construct` should be written with a nullable decorated dependency (`public function __contruct(?DecoratedInterface $decorated) {}`) and check should be done in other methods Commits ------- f167c77 Handle non existent decorated services
2 parents bbbbb21 + f167c77 commit 71873fc

26 files changed

+290
-28
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ CHANGELOG
1515
* added support for improved syntax to define method calls in Yaml
1616
* added `LazyString` for lazy computation of string values injected into services
1717
* made the `%env(base64:...)%` processor able to decode base64url
18+
* added ability to choose behavior of decorations on non existent decorated services
1819

1920
4.3.0
2021
-----

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
use Symfony\Component\DependencyInjection\Alias;
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\ContainerInterface;
17+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
18+
use Symfony\Component\DependencyInjection\Reference;
1619

1720
/**
1821
* Overwrites a service but keeps the overridden one.
@@ -37,14 +40,17 @@ public function process(ContainerBuilder $container)
3740
$decoratingDefinitions = [];
3841

3942
foreach ($definitions as list($id, $definition)) {
40-
list($inner, $renamedId) = $definition->getDecoratedService();
43+
$decoratedService = $definition->getDecoratedService();
44+
list($inner, $renamedId) = $decoratedService;
45+
$invalidBehavior = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
4146

4247
$definition->setDecoratedService(null);
4348

4449
if (!$renamedId) {
4550
$renamedId = $id.'.inner';
4651
}
4752
$definition->innerServiceId = $renamedId;
53+
$definition->decorationOnInvalid = $invalidBehavior;
4854

4955
// we create a new alias/service for the service we are replacing
5056
// to be able to reference it in the new one
@@ -53,13 +59,21 @@ public function process(ContainerBuilder $container)
5359
$public = $alias->isPublic();
5460
$private = $alias->isPrivate();
5561
$container->setAlias($renamedId, new Alias((string) $alias, false));
56-
} else {
62+
} elseif ($container->hasDefinition($inner)) {
5763
$decoratedDefinition = $container->getDefinition($inner);
5864
$public = $decoratedDefinition->isPublic();
5965
$private = $decoratedDefinition->isPrivate();
6066
$decoratedDefinition->setPublic(false);
6167
$container->setDefinition($renamedId, $decoratedDefinition);
6268
$decoratingDefinitions[$inner] = $decoratedDefinition;
69+
} elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) {
70+
$container->removeDefinition($id);
71+
continue;
72+
} elseif (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
73+
$public = $definition->isPublic();
74+
$private = $definition->isPrivate();
75+
} else {
76+
throw new ServiceNotFoundException($inner, $id);
6377
}
6478

6579
if (isset($decoratingDefinitions[$inner])) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\ChildDefinition;
15+
use Symfony\Component\DependencyInjection\ContainerInterface;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\Exception\ExceptionInterface;
1718
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
@@ -149,7 +150,7 @@ private function doResolveDefinition(ChildDefinition $definition): Definition
149150
if (null === $decoratedService) {
150151
$def->setDecoratedService($decoratedService);
151152
} else {
152-
$def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2]);
153+
$def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2], $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
153154
}
154155
}
155156

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ public function process(ContainerBuilder $container)
4242
$this->signalingException = new RuntimeException('Invalid reference.');
4343

4444
try {
45-
$this->processValue($container->getDefinitions(), 1);
45+
foreach ($container->getDefinitions() as $this->currentId => $definition) {
46+
$this->processValue($definition);
47+
}
4648
} finally {
4749
$this->container = $this->signalingException = null;
4850
}
@@ -72,9 +74,6 @@ private function processValue($value, int $rootLevel = 0, int $level = 0)
7274
$i = 0;
7375

7476
foreach ($value as $k => $v) {
75-
if (!$rootLevel) {
76-
$this->currentId = $k;
77-
}
7877
try {
7978
if (false !== $i && $k !== $i++) {
8079
$i = false;
@@ -101,6 +100,14 @@ private function processValue($value, int $rootLevel = 0, int $level = 0)
101100
if ($this->container->has($id = (string) $value)) {
102101
return $value;
103102
}
103+
104+
$currentDefinition = $this->container->getDefinition($this->currentId);
105+
106+
// resolve decorated service behavior depending on decorator service
107+
if ($currentDefinition->innerServiceId === $id && ContainerInterface::NULL_ON_INVALID_REFERENCE === $currentDefinition->decorationOnInvalid) {
108+
return null;
109+
}
110+
104111
$invalidBehavior = $value->getInvalidBehavior();
105112

106113
if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior && $value instanceof TypedReference && !$this->container->has($id)) {

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ class Definition
5656
*/
5757
public $innerServiceId;
5858

59+
/**
60+
* @internal
61+
*
62+
* Used to store the behavior to follow when using service decoration and the decorated service is invalid
63+
*/
64+
public $decorationOnInvalid;
65+
5966
/**
6067
* @param string|null $class The service class
6168
* @param array $arguments An array of arguments to pass to the service constructor
@@ -127,26 +134,33 @@ public function getFactory()
127134
/**
128135
* Sets the service that this service is decorating.
129136
*
130-
* @param string|null $id The decorated service id, use null to remove decoration
131-
* @param string|null $renamedId The new decorated service id
132-
* @param int $priority The priority of decoration
137+
* @param string|null $id The decorated service id, use null to remove decoration
138+
* @param string|null $renamedId The new decorated service id
139+
* @param int $priority The priority of decoration
140+
* @param int $invalidBehavior The behavior to adopt when decorated is invalid
133141
*
134142
* @return $this
135143
*
136144
* @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
137145
*/
138-
public function setDecoratedService($id, $renamedId = null, $priority = 0)
146+
public function setDecoratedService($id, $renamedId = null, $priority = 0/*, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE*/)
139147
{
140148
if ($renamedId && $id === $renamedId) {
141149
throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id));
142150
}
143151

152+
$invalidBehavior = 3 < \func_num_args() ? (int) func_get_arg(3) : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
153+
144154
$this->changes['decorated_service'] = true;
145155

146156
if (null === $id) {
147157
$this->decoratedService = null;
148158
} else {
149159
$this->decoratedService = [$id, $renamedId, (int) $priority];
160+
161+
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
162+
$this->decoratedService[] = $invalidBehavior;
163+
}
150164
}
151165

152166
return $this;

src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,15 @@ private function addService(Definition $definition, ?string $id, \DOMElement $pa
116116
if ($definition->isLazy()) {
117117
$service->setAttribute('lazy', 'true');
118118
}
119-
if (null !== $decorated = $definition->getDecoratedService()) {
120-
list($decorated, $renamedId, $priority) = $decorated;
119+
if (null !== $decoratedService = $definition->getDecoratedService()) {
120+
list($decorated, $renamedId, $priority) = $decoratedService;
121121
$service->setAttribute('decorates', $decorated);
122+
123+
$decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
124+
if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE], true)) {
125+
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore';
126+
$service->setAttribute('decoration-on-invalid', $invalidBehavior);
127+
}
122128
if (null !== $renamedId) {
123129
$service->setAttribute('decoration-inner-name', $renamedId);
124130
}

src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,21 @@ private function addService(string $id, Definition $definition): string
131131
$code .= " shared: false\n";
132132
}
133133

134-
if (null !== $decorated = $definition->getDecoratedService()) {
135-
list($decorated, $renamedId, $priority) = $decorated;
134+
if (null !== $decoratedService = $definition->getDecoratedService()) {
135+
list($decorated, $renamedId, $priority) = $decoratedService;
136136
$code .= sprintf(" decorates: %s\n", $decorated);
137137
if (null !== $renamedId) {
138138
$code .= sprintf(" decoration_inner_name: %s\n", $renamedId);
139139
}
140140
if (0 !== $priority) {
141141
$code .= sprintf(" decoration_priority: %s\n", $priority);
142142
}
143+
144+
$decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
145+
if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE])) {
146+
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore';
147+
$code .= sprintf(" decoration_on_invalid: %s\n", $invalidBehavior);
148+
}
143149
}
144150

145151
if ($callable = $definition->getFactory()) {

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,26 @@
1111

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

14+
use Symfony\Component\DependencyInjection\ContainerInterface;
1415
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1516

1617
trait DecorateTrait
1718
{
1819
/**
1920
* Sets the service that this service is decorating.
2021
*
21-
* @param string|null $id The decorated service id, use null to remove decoration
22+
* @param string|null $id The decorated service id, use null to remove decoration
23+
* @param string|null $renamedId The new decorated service id
24+
* @param int $priority The priority of decoration
25+
* @param int $invalidBehavior The behavior to adopt when decorated is invalid
2226
*
2327
* @return $this
2428
*
2529
* @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
2630
*/
27-
final public function decorate(?string $id, string $renamedId = null, int $priority = 0): self
31+
final public function decorate(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): self
2832
{
29-
$this->definition->setDecoratedService($id, $renamedId, $priority);
33+
$this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior);
3034

3135
return $this;
3236
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,10 +362,22 @@ private function parseDefinition(\DOMElement $service, string $file, array $defa
362362
$definition->setBindings($bindings);
363363
}
364364

365-
if ($value = $service->getAttribute('decorates')) {
365+
if ($decorates = $service->getAttribute('decorates')) {
366+
$decorationOnInvalid = $service->getAttribute('decoration-on-invalid') ?: 'exception';
367+
if ('exception' === $decorationOnInvalid) {
368+
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
369+
} elseif ('ignore' === $decorationOnInvalid) {
370+
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
371+
} elseif ('null' === $decorationOnInvalid) {
372+
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
373+
} else {
374+
throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration-on-invalid" on service "%s". Did you mean "exception", "ignore" or "null" in "%s"?', $decorationOnInvalid, (string) $service->getAttribute('id'), $file));
375+
}
376+
366377
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
367378
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
368-
$definition->setDecoratedService($value, $renameId, $priority);
379+
380+
$definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior);
369381
}
370382

371383
return $definition;

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class YamlFileLoader extends FileLoader
5858
'decorates' => 'decorates',
5959
'decoration_inner_name' => 'decoration_inner_name',
6060
'decoration_priority' => 'decoration_priority',
61+
'decoration_on_invalid' => 'decoration_on_invalid',
6162
'autowire' => 'autowire',
6263
'autoconfigure' => 'autoconfigure',
6364
'bind' => 'bind',
@@ -538,14 +539,28 @@ private function parseDefinition(string $id, $service, string $file, array $defa
538539
$definition->addTag($name, $tag);
539540
}
540541

541-
if (isset($service['decorates'])) {
542-
if ('' !== $service['decorates'] && '@' === $service['decorates'][0]) {
543-
throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($service['decorates'], 1)));
542+
if (null !== $decorates = $service['decorates'] ?? null) {
543+
if ('' !== $decorates && '@' === $decorates[0]) {
544+
throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($decorates, 1)));
545+
}
546+
547+
$decorationOnInvalid = \array_key_exists('decoration_on_invalid', $service) ? $service['decoration_on_invalid'] : 'exception';
548+
if ('exception' === $decorationOnInvalid) {
549+
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
550+
} elseif ('ignore' === $decorationOnInvalid) {
551+
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
552+
} elseif (null === $decorationOnInvalid) {
553+
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
554+
} elseif ('null' === $decorationOnInvalid) {
555+
throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean null (without quotes) in "%s"?', $decorationOnInvalid, $id, $file));
556+
} else {
557+
throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean "exception", "ignore" or null in "%s"?', $decorationOnInvalid, $id, $file));
544558
}
545559

546560
$renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null;
547561
$priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0;
548-
$definition->setDecoratedService($service['decorates'], $renameId, $priority);
562+
563+
$definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior);
549564
}
550565

551566
if (isset($service['autowire'])) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy