Skip to content

Commit 9e0106e

Browse files
authored
Add DefinitionDecoratesConstraint (#137)
1 parent d6b9416 commit 9e0106e

File tree

3 files changed

+232
-1
lines changed

3 files changed

+232
-1
lines changed

PhpUnit/AbstractContainerBuilderTestCase.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPUnit\Framework\TestCase;
77
use Symfony\Component\DependencyInjection\ContainerBuilder;
88
use Symfony\Component\DependencyInjection\Definition;
9-
use Symfony\Component\DependencyInjection\Reference;
109

1110
abstract class AbstractContainerBuilderTestCase extends TestCase
1211
{
@@ -233,4 +232,16 @@ final protected function assertContainerBuilderHasServiceLocator(
233232

234233
self::assertThat($definition, new DefinitionEqualsServiceLocatorConstraint($expectedServiceMap));
235234
}
235+
236+
final protected function assertContainerBuilderServiceDecoration(
237+
string $serviceId,
238+
string $decoratedServiceId,
239+
?string $renamedId = null,
240+
int $priority = 0,
241+
?int $invalidBehavior = null
242+
): void {
243+
$definition = $this->container->findDefinition($serviceId);
244+
245+
self::assertThat($definition, new DefinitionDecoratesConstraint($serviceId, $decoratedServiceId, $renamedId, $priority, $invalidBehavior));
246+
}
236247
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyDependencyInjectionTest\PhpUnit;
4+
5+
use PHPUnit\Framework\Constraint\Constraint;
6+
use Symfony\Component\DependencyInjection\ContainerBuilder;
7+
use Symfony\Component\DependencyInjection\ContainerInterface;
8+
9+
final class DefinitionDecoratesConstraint extends Constraint
10+
{
11+
private const INVALID_BEHAVIORS = [
12+
ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE => 'RUNTIME_EXCEPTION_ON_INVALID_REFERENCE',
13+
ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE => 'EXCEPTION_ON_INVALID_REFERENCE',
14+
ContainerInterface::NULL_ON_INVALID_REFERENCE => 'NULL_ON_INVALID_REFERENCE',
15+
ContainerInterface::IGNORE_ON_INVALID_REFERENCE => 'IGNORE_ON_INVALID_REFERENCE',
16+
ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE => 'IGNORE_ON_UNINITIALIZED_REFERENCE',
17+
];
18+
19+
private $serviceId;
20+
private $decoratedServiceId;
21+
private $renamedId;
22+
private $priority;
23+
private $invalidBehavior;
24+
25+
public function __construct(string $serviceId, string $decoratedServiceId, ?string $renamedId = null, int $priority = 0, ?int $invalidBehavior = null)
26+
{
27+
$this->serviceId = $serviceId;
28+
$this->decoratedServiceId = $decoratedServiceId;
29+
$this->renamedId = $renamedId;
30+
$this->priority = $priority;
31+
$this->invalidBehavior = $invalidBehavior;
32+
}
33+
34+
public function toString(): string
35+
{
36+
return sprintf(
37+
'"%s" decorates service "%s"%s with priority "%d" and "%s" behavior.',
38+
$this->serviceId,
39+
$this->decoratedServiceId,
40+
$this->renamedId !== null ? sprintf(' and renames it to "%s"', $this->renamedId) : '',
41+
$this->priority,
42+
self::INVALID_BEHAVIORS[$this->invalidBehavior ?? 0]
43+
);
44+
}
45+
46+
public function evaluate($other, string $description = '', bool $returnResult = false): bool
47+
{
48+
if (!($other instanceof ContainerBuilder)) {
49+
throw new \InvalidArgumentException(
50+
'Expected an instance of Symfony\Component\DependencyInjection\ContainerBuilder'
51+
);
52+
}
53+
54+
return $this->evaluateServiceDefinition($other, $returnResult);
55+
}
56+
57+
private function evaluateServiceDefinition(ContainerBuilder $containerBuilder, bool $returnResult): bool
58+
{
59+
if (!$containerBuilder->has($this->serviceId)) {
60+
if ($returnResult) {
61+
return false;
62+
}
63+
64+
$this->fail(
65+
$containerBuilder,
66+
sprintf(
67+
'The container builder has no service "%s"',
68+
$this->serviceId
69+
)
70+
);
71+
}
72+
73+
$definition = $containerBuilder->findDefinition($this->serviceId);
74+
75+
$decorated = $definition->getDecoratedService();
76+
77+
if ($decorated === null) {
78+
if ($returnResult) {
79+
return false;
80+
}
81+
82+
$this->fail(
83+
$containerBuilder,
84+
sprintf(
85+
'The container builder has a service "%s", but it does not decorate any service',
86+
$this->serviceId
87+
)
88+
);
89+
}
90+
91+
if ($decorated[0] !== $this->decoratedServiceId) {
92+
if ($returnResult) {
93+
return false;
94+
}
95+
96+
$this->fail(
97+
$containerBuilder,
98+
sprintf(
99+
'The container builder has a decorator service "%s", but it does decorate service "%s".',
100+
$this->serviceId,
101+
$decorated[0]
102+
)
103+
);
104+
}
105+
106+
if ($decorated[1] !== $this->renamedId) {
107+
if ($returnResult) {
108+
return false;
109+
}
110+
111+
$this->fail(
112+
$containerBuilder,
113+
sprintf(
114+
'The container builder has a decorator service "%s", but it does not rename decorated service to "%s".',
115+
$this->serviceId,
116+
$this->renamedId
117+
)
118+
);
119+
}
120+
121+
if ($decorated[2] !== $this->priority) {
122+
if ($returnResult) {
123+
return false;
124+
}
125+
126+
$this->fail(
127+
$containerBuilder,
128+
sprintf(
129+
'The container builder has a decorator service "%s", but it does not decorate at expected "%d" priority.',
130+
$this->serviceId,
131+
$this->priority
132+
)
133+
);
134+
}
135+
136+
if (($decorated[3] ?? null) !== $this->invalidBehavior) {
137+
if ($returnResult) {
138+
return false;
139+
}
140+
141+
$this->fail(
142+
$containerBuilder,
143+
sprintf(
144+
'The container builder has a decorator service "%s", but it does not decorate with expected "%s" behavior.',
145+
$this->serviceId,
146+
self::INVALID_BEHAVIORS[$this->invalidBehavior]
147+
)
148+
);
149+
}
150+
151+
return true;
152+
}
153+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyDependencyInjectionTest\Tests\PhpUnit;
4+
5+
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\DefinitionDecoratesConstraint;
6+
use PHPUnit\Framework\TestCase;
7+
use Symfony\Component\DependencyInjection\ContainerBuilder;
8+
use Symfony\Component\DependencyInjection\ContainerInterface;
9+
use Symfony\Component\DependencyInjection\Definition;
10+
11+
class DefinitionDecoratesConstraintTest extends TestCase
12+
{
13+
/**
14+
* @test
15+
* @dataProvider containerBuilderProvider
16+
*/
17+
public function match(ContainerBuilder $containerBuilder, bool $expectedToMatch, string $serviceId, string $decoratedServiceId, ?string $renamedId, int $priority, ?int $invalidBehavior): void
18+
{
19+
$constraint = new DefinitionDecoratesConstraint($serviceId, $decoratedServiceId, $renamedId, $priority, $invalidBehavior);
20+
21+
$this->assertSame($expectedToMatch, $constraint->evaluate($containerBuilder, '', true));
22+
}
23+
24+
public function containerBuilderProvider(): iterable
25+
{
26+
$containerBuilder = new ContainerBuilder();
27+
$containerBuilder->setDefinition('decorated1', new Definition('DecoratedClass1'));
28+
$containerBuilder->setDefinition('decorated2', new Definition('DecoratedClass2'));
29+
$containerBuilder->setDefinition('decorated3', new Definition('DecoratedClass3'));
30+
$containerBuilder->setDefinition('decorated4', new Definition('DecoratedClass4'));
31+
$containerBuilder->setDefinition('decorated5', new Definition('DecoratedClass5'));
32+
33+
$containerBuilder->setDefinition('decorator1', (new Definition('DecoratorClass1'))->setDecoratedService('decorated1'));
34+
$containerBuilder->setDefinition('decorator2', (new Definition('DecoratorClass2'))->setDecoratedService('decorated2', 'decorated2_0'));
35+
$containerBuilder->setDefinition('decorator3', (new Definition('DecoratorClass3'))->setDecoratedService('decorated3', null, 10));
36+
$containerBuilder->setDefinition('decorator4', (new Definition('DecoratorClass4'))->setDecoratedService('decorated4', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE));
37+
$containerBuilder->setDefinition('decorator5', (new Definition('DecoratorClass5'))->setDecoratedService('decorated5', 'decorated5_0', -3, ContainerInterface::NULL_ON_INVALID_REFERENCE));
38+
39+
yield [$containerBuilder, false, 'not_decorator', 'decorated', null, 0, null];
40+
yield [$containerBuilder, true, 'decorator1', 'decorated1', null, 0, null];
41+
yield [$containerBuilder, true, 'decorator2', 'decorated2', 'decorated2_0', 0, null];
42+
yield [$containerBuilder, true, 'decorator3', 'decorated3', null, 10, null];
43+
yield [$containerBuilder, true, 'decorator4', 'decorated4', null, 0, 3];
44+
yield [$containerBuilder, true, 'decorator5', 'decorated5', 'decorated5_0', -3, 2];
45+
}
46+
47+
/**
48+
* @test
49+
* @dataProvider stringRepresentationProvider
50+
*/
51+
public function it_has_a_string_representation(DefinitionDecoratesConstraint $constraint, string $expectedRepresentation): void
52+
{
53+
$this->assertSame($expectedRepresentation, $constraint->toString());
54+
}
55+
56+
public function stringRepresentationProvider(): iterable
57+
{
58+
yield [new DefinitionDecoratesConstraint('decorator', 'decorated'), '"decorator" decorates service "decorated" with priority "0" and "RUNTIME_EXCEPTION_ON_INVALID_REFERENCE" behavior.'];
59+
yield [new DefinitionDecoratesConstraint('decorator', 'decorated', 'decorated_0'), '"decorator" decorates service "decorated" and renames it to "decorated_0" with priority "0" and "RUNTIME_EXCEPTION_ON_INVALID_REFERENCE" behavior.'];
60+
yield [new DefinitionDecoratesConstraint('decorator', 'decorated', 'decorated_0', -3), '"decorator" decorates service "decorated" and renames it to "decorated_0" with priority "-3" and "RUNTIME_EXCEPTION_ON_INVALID_REFERENCE" behavior.'];
61+
yield [new DefinitionDecoratesConstraint('decorator', 'decorated', 'decorated_0', -3, 0), '"decorator" decorates service "decorated" and renames it to "decorated_0" with priority "-3" and "RUNTIME_EXCEPTION_ON_INVALID_REFERENCE" behavior.'];
62+
yield [new DefinitionDecoratesConstraint('decorator', 'decorated', 'decorated_0', -3, 1), '"decorator" decorates service "decorated" and renames it to "decorated_0" with priority "-3" and "EXCEPTION_ON_INVALID_REFERENCE" behavior.'];
63+
yield [new DefinitionDecoratesConstraint('decorator', 'decorated', 'decorated_0', -3, 2), '"decorator" decorates service "decorated" and renames it to "decorated_0" with priority "-3" and "NULL_ON_INVALID_REFERENCE" behavior.'];
64+
yield [new DefinitionDecoratesConstraint('decorator', 'decorated', 'decorated_0', -3, 3), '"decorator" decorates service "decorated" and renames it to "decorated_0" with priority "-3" and "IGNORE_ON_INVALID_REFERENCE" behavior.'];
65+
yield [new DefinitionDecoratesConstraint('decorator', 'decorated', 'decorated_0', -3, 4), '"decorator" decorates service "decorated" and renames it to "decorated_0" with priority "-3" and "IGNORE_ON_UNINITIALIZED_REFERENCE" behavior.'];
66+
}
67+
}

0 commit comments

Comments
 (0)