Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f167c77

Browse files
committed
Handle non existent decorated services
1 parent 0472dbf commit f167c77

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'])) {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
<xsd:attribute name="alias" type="xsd:string" />
130130
<xsd:attribute name="parent" type="xsd:string" />
131131
<xsd:attribute name="decorates" type="xsd:string" />
132+
<xsd:attribute name="decoration-on-invalid" type="invalid_decorated_service_sequence" />
132133
<xsd:attribute name="decoration-inner-name" type="xsd:string" />
133134
<xsd:attribute name="decoration-priority" type="xsd:integer" />
134135
<xsd:attribute name="autowire" type="boolean" />
@@ -281,6 +282,14 @@
281282
</xsd:restriction>
282283
</xsd:simpleType>
283284

285+
<xsd:simpleType name="invalid_decorated_service_sequence">
286+
<xsd:restriction base="xsd:string">
287+
<xsd:enumeration value="null" />
288+
<xsd:enumeration value="ignore" />
289+
<xsd:enumeration value="exception" />
290+
</xsd:restriction>
291+
</xsd:simpleType>
292+
284293
<xsd:simpleType name="boolean">
285294
<xsd:restriction base="xsd:string">
286295
<xsd:pattern value="(%.+%|true|false)" />

src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\Alias;
1616
use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\ContainerInterface;
1819

1920
class DecoratorServicePassTest extends TestCase
2021
{
@@ -125,6 +126,61 @@ public function testProcessWithPriority()
125126
$this->assertNull($quxDefinition->getDecoratedService());
126127
}
127128

129+
public function testProcessWithInvalidDecorated()
130+
{
131+
$container = new ContainerBuilder();
132+
$decoratorDefinition = $container
133+
->register('decorator')
134+
->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
135+
;
136+
137+
$this->process($container);
138+
$this->assertFalse($container->has('decorator'));
139+
140+
$container = new ContainerBuilder();
141+
$decoratorDefinition = $container
142+
->register('decorator')
143+
->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::NULL_ON_INVALID_REFERENCE)
144+
;
145+
146+
$this->process($container);
147+
$this->assertTrue($container->has('decorator'));
148+
$this->assertSame(ContainerInterface::NULL_ON_INVALID_REFERENCE, $decoratorDefinition->decorationOnInvalid);
149+
150+
$container = new ContainerBuilder();
151+
$decoratorDefinition = $container
152+
->register('decorator')
153+
->setDecoratedService('unknown_service')
154+
;
155+
156+
$this->expectException('Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException');
157+
$this->process($container);
158+
}
159+
160+
public function testProcessNoInnerAliasWithInvalidDecorated()
161+
{
162+
$container = new ContainerBuilder();
163+
$decoratorDefinition = $container
164+
->register('decorator')
165+
->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::NULL_ON_INVALID_REFERENCE)
166+
;
167+
168+
$this->process($container);
169+
$this->assertFalse($container->hasAlias('decorator.inner'));
170+
}
171+
172+
public function testProcessWithInvalidDecoratedAndWrongBehavior()
173+
{
174+
$container = new ContainerBuilder();
175+
$decoratorDefinition = $container
176+
->register('decorator')
177+
->setDecoratedService('unknown_decorated', null, 0, 12)
178+
;
179+
180+
$this->expectException('Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException');
181+
$this->process($container);
182+
}
183+
128184
public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinition()
129185
{
130186
$container = new ContainerBuilder();

0 commit comments

Comments
 (0)