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 | https://github.com/symfony/symfony-docs/pull/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
-------
f167c77eaf
Handle non existent decorated services
This commit is contained in:
commit
71873fc770
@ -15,6 +15,7 @@ CHANGELOG
|
||||
* added support for improved syntax to define method calls in Yaml
|
||||
* added `LazyString` for lazy computation of string values injected into services
|
||||
* made the `%env(base64:...)%` processor able to decode base64url
|
||||
* added ability to choose behavior of decorations on non existent decorated services
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
@ -13,6 +13,9 @@ namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Overwrites a service but keeps the overridden one.
|
||||
@ -37,7 +40,9 @@ class DecoratorServicePass implements CompilerPassInterface
|
||||
$decoratingDefinitions = [];
|
||||
|
||||
foreach ($definitions as list($id, $definition)) {
|
||||
list($inner, $renamedId) = $definition->getDecoratedService();
|
||||
$decoratedService = $definition->getDecoratedService();
|
||||
list($inner, $renamedId) = $decoratedService;
|
||||
$invalidBehavior = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
|
||||
$definition->setDecoratedService(null);
|
||||
|
||||
@ -45,6 +50,7 @@ class DecoratorServicePass implements CompilerPassInterface
|
||||
$renamedId = $id.'.inner';
|
||||
}
|
||||
$definition->innerServiceId = $renamedId;
|
||||
$definition->decorationOnInvalid = $invalidBehavior;
|
||||
|
||||
// we create a new alias/service for the service we are replacing
|
||||
// to be able to reference it in the new one
|
||||
@ -53,13 +59,21 @@ class DecoratorServicePass implements CompilerPassInterface
|
||||
$public = $alias->isPublic();
|
||||
$private = $alias->isPrivate();
|
||||
$container->setAlias($renamedId, new Alias((string) $alias, false));
|
||||
} else {
|
||||
} elseif ($container->hasDefinition($inner)) {
|
||||
$decoratedDefinition = $container->getDefinition($inner);
|
||||
$public = $decoratedDefinition->isPublic();
|
||||
$private = $decoratedDefinition->isPrivate();
|
||||
$decoratedDefinition->setPublic(false);
|
||||
$container->setDefinition($renamedId, $decoratedDefinition);
|
||||
$decoratingDefinitions[$inner] = $decoratedDefinition;
|
||||
} elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) {
|
||||
$container->removeDefinition($id);
|
||||
continue;
|
||||
} elseif (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
|
||||
$public = $definition->isPublic();
|
||||
$private = $definition->isPrivate();
|
||||
} else {
|
||||
throw new ServiceNotFoundException($inner, $id);
|
||||
}
|
||||
|
||||
if (isset($decoratingDefinitions[$inner])) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\ExceptionInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
@ -149,7 +150,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass
|
||||
if (null === $decoratedService) {
|
||||
$def->setDecoratedService($decoratedService);
|
||||
} else {
|
||||
$def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2]);
|
||||
$def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2], $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,9 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
|
||||
$this->signalingException = new RuntimeException('Invalid reference.');
|
||||
|
||||
try {
|
||||
$this->processValue($container->getDefinitions(), 1);
|
||||
foreach ($container->getDefinitions() as $this->currentId => $definition) {
|
||||
$this->processValue($definition);
|
||||
}
|
||||
} finally {
|
||||
$this->container = $this->signalingException = null;
|
||||
}
|
||||
@ -72,9 +74,6 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
|
||||
$i = 0;
|
||||
|
||||
foreach ($value as $k => $v) {
|
||||
if (!$rootLevel) {
|
||||
$this->currentId = $k;
|
||||
}
|
||||
try {
|
||||
if (false !== $i && $k !== $i++) {
|
||||
$i = false;
|
||||
@ -101,6 +100,14 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
|
||||
if ($this->container->has($id = (string) $value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$currentDefinition = $this->container->getDefinition($this->currentId);
|
||||
|
||||
// resolve decorated service behavior depending on decorator service
|
||||
if ($currentDefinition->innerServiceId === $id && ContainerInterface::NULL_ON_INVALID_REFERENCE === $currentDefinition->decorationOnInvalid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$invalidBehavior = $value->getInvalidBehavior();
|
||||
|
||||
if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior && $value instanceof TypedReference && !$this->container->has($id)) {
|
||||
|
@ -56,6 +56,13 @@ class Definition
|
||||
*/
|
||||
public $innerServiceId;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Used to store the behavior to follow when using service decoration and the decorated service is invalid
|
||||
*/
|
||||
public $decorationOnInvalid;
|
||||
|
||||
/**
|
||||
* @param string|null $class The service class
|
||||
* @param array $arguments An array of arguments to pass to the service constructor
|
||||
@ -130,23 +137,30 @@ class Definition
|
||||
* @param string|null $id The decorated service id, use null to remove decoration
|
||||
* @param string|null $renamedId The new decorated service id
|
||||
* @param int $priority The priority of decoration
|
||||
* @param int $invalidBehavior The behavior to adopt when decorated is invalid
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
|
||||
*/
|
||||
public function setDecoratedService($id, $renamedId = null, $priority = 0)
|
||||
public function setDecoratedService($id, $renamedId = null, $priority = 0/*, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE*/)
|
||||
{
|
||||
if ($renamedId && $id === $renamedId) {
|
||||
throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id));
|
||||
}
|
||||
|
||||
$invalidBehavior = 3 < \func_num_args() ? (int) func_get_arg(3) : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
|
||||
$this->changes['decorated_service'] = true;
|
||||
|
||||
if (null === $id) {
|
||||
$this->decoratedService = null;
|
||||
} else {
|
||||
$this->decoratedService = [$id, $renamedId, (int) $priority];
|
||||
|
||||
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
|
||||
$this->decoratedService[] = $invalidBehavior;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -116,9 +116,15 @@ class XmlDumper extends Dumper
|
||||
if ($definition->isLazy()) {
|
||||
$service->setAttribute('lazy', 'true');
|
||||
}
|
||||
if (null !== $decorated = $definition->getDecoratedService()) {
|
||||
list($decorated, $renamedId, $priority) = $decorated;
|
||||
if (null !== $decoratedService = $definition->getDecoratedService()) {
|
||||
list($decorated, $renamedId, $priority) = $decoratedService;
|
||||
$service->setAttribute('decorates', $decorated);
|
||||
|
||||
$decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE], true)) {
|
||||
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore';
|
||||
$service->setAttribute('decoration-on-invalid', $invalidBehavior);
|
||||
}
|
||||
if (null !== $renamedId) {
|
||||
$service->setAttribute('decoration-inner-name', $renamedId);
|
||||
}
|
||||
|
@ -131,8 +131,8 @@ class YamlDumper extends Dumper
|
||||
$code .= " shared: false\n";
|
||||
}
|
||||
|
||||
if (null !== $decorated = $definition->getDecoratedService()) {
|
||||
list($decorated, $renamedId, $priority) = $decorated;
|
||||
if (null !== $decoratedService = $definition->getDecoratedService()) {
|
||||
list($decorated, $renamedId, $priority) = $decoratedService;
|
||||
$code .= sprintf(" decorates: %s\n", $decorated);
|
||||
if (null !== $renamedId) {
|
||||
$code .= sprintf(" decoration_inner_name: %s\n", $renamedId);
|
||||
@ -140,6 +140,12 @@ class YamlDumper extends Dumper
|
||||
if (0 !== $priority) {
|
||||
$code .= sprintf(" decoration_priority: %s\n", $priority);
|
||||
}
|
||||
|
||||
$decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE])) {
|
||||
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore';
|
||||
$code .= sprintf(" decoration_on_invalid: %s\n", $invalidBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
if ($callable = $definition->getFactory()) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
|
||||
trait DecorateTrait
|
||||
@ -19,14 +20,17 @@ trait DecorateTrait
|
||||
* Sets the service that this service is decorating.
|
||||
*
|
||||
* @param string|null $id The decorated service id, use null to remove decoration
|
||||
* @param string|null $renamedId The new decorated service id
|
||||
* @param int $priority The priority of decoration
|
||||
* @param int $invalidBehavior The behavior to adopt when decorated is invalid
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
|
||||
*/
|
||||
final public function decorate(?string $id, string $renamedId = null, int $priority = 0): self
|
||||
final public function decorate(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): self
|
||||
{
|
||||
$this->definition->setDecoratedService($id, $renamedId, $priority);
|
||||
$this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -362,10 +362,22 @@ class XmlFileLoader extends FileLoader
|
||||
$definition->setBindings($bindings);
|
||||
}
|
||||
|
||||
if ($value = $service->getAttribute('decorates')) {
|
||||
if ($decorates = $service->getAttribute('decorates')) {
|
||||
$decorationOnInvalid = $service->getAttribute('decoration-on-invalid') ?: 'exception';
|
||||
if ('exception' === $decorationOnInvalid) {
|
||||
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
} elseif ('ignore' === $decorationOnInvalid) {
|
||||
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
|
||||
} elseif ('null' === $decorationOnInvalid) {
|
||||
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
|
||||
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
|
||||
$definition->setDecoratedService($value, $renameId, $priority);
|
||||
|
||||
$definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior);
|
||||
}
|
||||
|
||||
return $definition;
|
||||
|
@ -58,6 +58,7 @@ class YamlFileLoader extends FileLoader
|
||||
'decorates' => 'decorates',
|
||||
'decoration_inner_name' => 'decoration_inner_name',
|
||||
'decoration_priority' => 'decoration_priority',
|
||||
'decoration_on_invalid' => 'decoration_on_invalid',
|
||||
'autowire' => 'autowire',
|
||||
'autoconfigure' => 'autoconfigure',
|
||||
'bind' => 'bind',
|
||||
@ -538,14 +539,28 @@ class YamlFileLoader extends FileLoader
|
||||
$definition->addTag($name, $tag);
|
||||
}
|
||||
|
||||
if (isset($service['decorates'])) {
|
||||
if ('' !== $service['decorates'] && '@' === $service['decorates'][0]) {
|
||||
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)));
|
||||
if (null !== $decorates = $service['decorates'] ?? null) {
|
||||
if ('' !== $decorates && '@' === $decorates[0]) {
|
||||
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)));
|
||||
}
|
||||
|
||||
$decorationOnInvalid = \array_key_exists('decoration_on_invalid', $service) ? $service['decoration_on_invalid'] : 'exception';
|
||||
if ('exception' === $decorationOnInvalid) {
|
||||
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
} elseif ('ignore' === $decorationOnInvalid) {
|
||||
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
|
||||
} elseif (null === $decorationOnInvalid) {
|
||||
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
|
||||
} elseif ('null' === $decorationOnInvalid) {
|
||||
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));
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
$renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null;
|
||||
$priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0;
|
||||
$definition->setDecoratedService($service['decorates'], $renameId, $priority);
|
||||
|
||||
$definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior);
|
||||
}
|
||||
|
||||
if (isset($service['autowire'])) {
|
||||
|
@ -129,6 +129,7 @@
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="parent" type="xsd:string" />
|
||||
<xsd:attribute name="decorates" type="xsd:string" />
|
||||
<xsd:attribute name="decoration-on-invalid" type="invalid_decorated_service_sequence" />
|
||||
<xsd:attribute name="decoration-inner-name" type="xsd:string" />
|
||||
<xsd:attribute name="decoration-priority" type="xsd:integer" />
|
||||
<xsd:attribute name="autowire" type="boolean" />
|
||||
@ -281,6 +282,14 @@
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="invalid_decorated_service_sequence">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="null" />
|
||||
<xsd:enumeration value="ignore" />
|
||||
<xsd:enumeration value="exception" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="boolean">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="(%.+%|true|false)" />
|
||||
|
@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
class DecoratorServicePassTest extends TestCase
|
||||
{
|
||||
@ -125,6 +126,61 @@ class DecoratorServicePassTest extends TestCase
|
||||
$this->assertNull($quxDefinition->getDecoratedService());
|
||||
}
|
||||
|
||||
public function testProcessWithInvalidDecorated()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$decoratorDefinition = $container
|
||||
->register('decorator')
|
||||
->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
$this->assertFalse($container->has('decorator'));
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$decoratorDefinition = $container
|
||||
->register('decorator')
|
||||
->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::NULL_ON_INVALID_REFERENCE)
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
$this->assertTrue($container->has('decorator'));
|
||||
$this->assertSame(ContainerInterface::NULL_ON_INVALID_REFERENCE, $decoratorDefinition->decorationOnInvalid);
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$decoratorDefinition = $container
|
||||
->register('decorator')
|
||||
->setDecoratedService('unknown_service')
|
||||
;
|
||||
|
||||
$this->expectException('Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException');
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
public function testProcessNoInnerAliasWithInvalidDecorated()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$decoratorDefinition = $container
|
||||
->register('decorator')
|
||||
->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::NULL_ON_INVALID_REFERENCE)
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
$this->assertFalse($container->hasAlias('decorator.inner'));
|
||||
}
|
||||
|
||||
public function testProcessWithInvalidDecoratedAndWrongBehavior()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$decoratorDefinition = $container
|
||||
->register('decorator')
|
||||
->setDecoratedService('unknown_decorated', null, 0, 12)
|
||||
;
|
||||
|
||||
$this->expectException('Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException');
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinition()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ResolveInvalidReferencesPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
@ -127,6 +128,43 @@ class ResolveInvalidReferencesPassTest extends TestCase
|
||||
$this->assertSame([[[]]], $def->getArguments());
|
||||
}
|
||||
|
||||
public function testProcessSetDecoratedAsNullOnInvalid()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$decoratorDefinition = $container
|
||||
->register('decorator')
|
||||
->setArguments([
|
||||
new Reference('decorator.inner'),
|
||||
])
|
||||
->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::NULL_ON_INVALID_REFERENCE)
|
||||
;
|
||||
|
||||
(new DecoratorServicePass())->process($container);
|
||||
(new ResolveInvalidReferencesPass())->process($container);
|
||||
|
||||
$this->assertSame([null], $decoratorDefinition->getArguments());
|
||||
}
|
||||
|
||||
public function testProcessSetOnlyDecoratedAsNullOnInvalid()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$unknownArgument = new Reference('unknown_argument');
|
||||
$decoratorDefinition = $container
|
||||
->register('decorator')
|
||||
->setArguments([
|
||||
new Reference('decorator.inner'),
|
||||
$unknownArgument,
|
||||
])
|
||||
->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::NULL_ON_INVALID_REFERENCE)
|
||||
;
|
||||
|
||||
(new DecoratorServicePass())->process($container);
|
||||
(new ResolveInvalidReferencesPass())->process($container);
|
||||
|
||||
$this->assertSame(null, $decoratorDefinition->getArguments()[0]);
|
||||
$this->assertEquals($unknownArgument, $decoratorDefinition->getArguments()[1]);
|
||||
}
|
||||
|
||||
protected function process(ContainerBuilder $container)
|
||||
{
|
||||
$pass = new ResolveInvalidReferencesPass();
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\DependencyInjection\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
@ -86,6 +87,13 @@ class DefinitionTest extends TestCase
|
||||
|
||||
public function testSetGetDecoratedService()
|
||||
{
|
||||
$def = new Definition('stdClass');
|
||||
$this->assertNull($def->getDecoratedService());
|
||||
$def->setDecoratedService('foo', 'foo.renamed', 5, ContainerInterface::NULL_ON_INVALID_REFERENCE);
|
||||
$this->assertEquals(['foo', 'foo.renamed', 5, ContainerInterface::NULL_ON_INVALID_REFERENCE], $def->getDecoratedService());
|
||||
$def->setDecoratedService(null);
|
||||
$this->assertNull($def->getDecoratedService());
|
||||
|
||||
$def = new Definition('stdClass');
|
||||
$this->assertNull($def->getDecoratedService());
|
||||
$def->setDecoratedService('foo', 'foo.renamed', 5);
|
||||
|
@ -146,6 +146,16 @@ class XmlDumperTest extends TestCase
|
||||
</services>
|
||||
</container>
|
||||
", include $fixturesPath.'/containers/container16.php'],
|
||||
["<?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>
|
||||
<service id=\"service_container\" class=\"Symfony\Component\DependencyInjection\ContainerInterface\" public=\"true\" synthetic=\"true\"/>
|
||||
<service id=\"decorator\" decorates=\"decorated\" decoration-on-invalid=\"null\" decoration-inner-name=\"decorated.inner\" decoration-priority=\"1\"/>
|
||||
<service id=\"Psr\Container\ContainerInterface\" alias=\"service_container\" public=\"false\"/>
|
||||
<service id=\"Symfony\Component\DependencyInjection\ContainerInterface\" alias=\"service_container\" public=\"false\"/>
|
||||
</services>
|
||||
</container>
|
||||
", include $fixturesPath.'/containers/container34.php'],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,13 @@ class YamlDumperTest extends TestCase
|
||||
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services24.yml', $dumper->dump());
|
||||
}
|
||||
|
||||
public function testDumpDecoratedServices()
|
||||
{
|
||||
$container = include self::$fixturesPath.'/containers/container34.php';
|
||||
$dumper = new YamlDumper($container);
|
||||
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services34.yml', $dumper->dump());
|
||||
}
|
||||
|
||||
public function testDumpLoad()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container
|
||||
->register('decorator')
|
||||
->setDecoratedService('decorated', 'decorated.inner', 1, ContainerInterface::NULL_ON_INVALID_REFERENCE)
|
||||
;
|
||||
|
||||
return $container;
|
@ -47,7 +47,7 @@ class Symfony_DI_PhpDumper_Test_Rot13Parameters extends Container
|
||||
public function getRemovedIds(): array
|
||||
{
|
||||
return [
|
||||
'.service_locator.GU08LT9' => true,
|
||||
'.service_locator.ZZqL6HL' => true,
|
||||
'Psr\\Container\\ContainerInterface' => true,
|
||||
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
|
||||
];
|
||||
|
@ -48,7 +48,7 @@ class Symfony_DI_PhpDumper_Service_Locator_Argument extends Container
|
||||
public function getRemovedIds(): array
|
||||
{
|
||||
return [
|
||||
'.service_locator.38dy3OH' => true,
|
||||
'.service_locator.iSxuxv5' => true,
|
||||
'Psr\\Container\\ContainerInterface' => true,
|
||||
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
|
||||
'foo2' => true,
|
||||
|
@ -45,8 +45,8 @@ class ProjectServiceContainer extends Container
|
||||
public function getRemovedIds(): array
|
||||
{
|
||||
return [
|
||||
'.service_locator.nZQiwdg' => true,
|
||||
'.service_locator.nZQiwdg.foo_service' => true,
|
||||
'.service_locator.bPEFRiK' => true,
|
||||
'.service_locator.bPEFRiK.foo_service' => true,
|
||||
'Psr\\Container\\ContainerInterface' => true,
|
||||
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
|
||||
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true,
|
||||
|
@ -47,6 +47,7 @@
|
||||
<service id="decorator_service" decorates="decorated" />
|
||||
<service id="decorator_service_with_name" decorates="decorated" decoration-inner-name="decorated.pif-pouf"/>
|
||||
<service id="decorator_service_with_name_and_priority" decorates="decorated" decoration-inner-name="decorated.pif-pouf" decoration-priority="5"/>
|
||||
<service id="decorator_service_with_name_and_priority_and_on_invalid" decorates="decorated" decoration-inner-name="decorated.pif-pouf" decoration-priority="5" decoration-on-invalid="ignore"/>
|
||||
<service id="new_factory1" class="FooBarClass">
|
||||
<factory function="factory" />
|
||||
</service>
|
||||
|
@ -0,0 +1,7 @@
|
||||
services:
|
||||
foo:
|
||||
class: stdClass
|
||||
bar:
|
||||
class: stdClass
|
||||
decorates: "foo"
|
||||
decoration_on_invalid: 'null'
|
@ -0,0 +1,17 @@
|
||||
|
||||
services:
|
||||
service_container:
|
||||
class: Symfony\Component\DependencyInjection\ContainerInterface
|
||||
public: true
|
||||
synthetic: true
|
||||
decorator:
|
||||
decorates: decorated
|
||||
decoration_inner_name: decorated.inner
|
||||
decoration_priority: 1
|
||||
decoration_on_invalid: null
|
||||
Psr\Container\ContainerInterface:
|
||||
alias: service_container
|
||||
public: false
|
||||
Symfony\Component\DependencyInjection\ContainerInterface:
|
||||
alias: service_container
|
||||
public: false
|
@ -30,6 +30,11 @@ services:
|
||||
decorates: decorated
|
||||
decoration_inner_name: decorated.pif-pouf
|
||||
decoration_priority: 5
|
||||
decorator_service_with_name_and_priority_and_on_invalid:
|
||||
decorates: decorated
|
||||
decoration_inner_name: decorated.pif-pouf
|
||||
decoration_priority: 5
|
||||
decoration_on_invalid: ignore
|
||||
new_factory1: { class: FooBarClass, factory: factory}
|
||||
new_factory2: { class: FooBarClass, factory: ['@baz', getClass]}
|
||||
new_factory3: { class: FooBarClass, factory: [BazClass, getInstance]}
|
||||
|
@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
|
||||
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
@ -281,6 +282,7 @@ class XmlFileLoaderTest extends TestCase
|
||||
$this->assertEquals(['decorated', null, 0], $services['decorator_service']->getDecoratedService());
|
||||
$this->assertEquals(['decorated', 'decorated.pif-pouf', 0], $services['decorator_service_with_name']->getDecoratedService());
|
||||
$this->assertEquals(['decorated', 'decorated.pif-pouf', 5], $services['decorator_service_with_name_and_priority']->getDecoratedService());
|
||||
$this->assertEquals(['decorated', 'decorated.pif-pouf', 5, ContainerInterface::IGNORE_ON_INVALID_REFERENCE], $services['decorator_service_with_name_and_priority_and_on_invalid']->getDecoratedService());
|
||||
}
|
||||
|
||||
public function testParsesIteratorArgument()
|
||||
|
@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
@ -174,6 +175,7 @@ class YamlFileLoaderTest extends TestCase
|
||||
$this->assertEquals(['decorated', null, 0], $services['decorator_service']->getDecoratedService());
|
||||
$this->assertEquals(['decorated', 'decorated.pif-pouf', 0], $services['decorator_service_with_name']->getDecoratedService());
|
||||
$this->assertEquals(['decorated', 'decorated.pif-pouf', 5], $services['decorator_service_with_name_and_priority']->getDecoratedService());
|
||||
$this->assertEquals(['decorated', 'decorated.pif-pouf', 5, ContainerInterface::IGNORE_ON_INVALID_REFERENCE], $services['decorator_service_with_name_and_priority_and_on_invalid']->getDecoratedService());
|
||||
}
|
||||
|
||||
public function testDeprecatedAliases()
|
||||
@ -578,6 +580,14 @@ class YamlFileLoaderTest extends TestCase
|
||||
$loader->load('bad_decorates.yml');
|
||||
}
|
||||
|
||||
public function testDecoratedServicesWithWrongOnInvalidSyntaxThrowsException()
|
||||
{
|
||||
$this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException');
|
||||
$this->expectExceptionMessage('Did you mean null (without quotes)');
|
||||
$loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/yaml'));
|
||||
$loader->load('bad_decoration_on_invalid_null.yml');
|
||||
}
|
||||
|
||||
public function testInvalidTagsWithDefaults()
|
||||
{
|
||||
$this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException');
|
||||
|
Reference in New Issue
Block a user