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

Skip to content

Commit 810f7e9

Browse files
committed
[DependencyInjection] Add CheckAliasValidityPass to check interface compatibility
Adding a pass to check that an aliased interface resolves to a service which implements the interface
1 parent d9eccd1 commit 810f7e9

File tree

6 files changed

+159
-0
lines changed

6 files changed

+159
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
16+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
17+
18+
/**
19+
* This pass validates aliases, it provides the following checks:
20+
*
21+
* - An alias which happens to be an interface must resolve to a service implementing this interface. This ensures injecting the aliased interface won't cause a type error at runtime.
22+
*/
23+
class CheckAliasValidityPass implements CompilerPassInterface
24+
{
25+
public function process(ContainerBuilder $container): void
26+
{
27+
foreach ($container->getAliases() as $id => $alias) {
28+
try {
29+
if (!$container->hasDefinition((string) $alias)) {
30+
continue;
31+
}
32+
33+
$target = $container->getDefinition((string) $alias);
34+
if ($target->getClass() === null || $target->getFactory() !== null) {
35+
continue;
36+
}
37+
38+
$reflection = $container->getReflectionClass($id);
39+
if ($reflection === null || !$reflection->isInterface()) {
40+
continue;
41+
}
42+
43+
$targetReflection = $container->getReflectionClass($target->getClass());
44+
if ($targetReflection !== null && !$targetReflection->implementsInterface($id)) {
45+
throw new RuntimeException(sprintf('Invalid alias definition: alias "%s" is referencing class "%s" but this class does not implement "%s". Because this alias is an interface, "%s" must implement "%s".', $id, $target->getClass(), $id, $target->getClass(), $id));
46+
}
47+
} catch (\ReflectionException) {
48+
continue;
49+
}
50+
}
51+
}
52+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public function __construct()
7171
new ResolveTaggedIteratorArgumentPass(),
7272
new ResolveServiceSubscribersPass(),
7373
new ResolveReferencesToAliasesPass(),
74+
new CheckAliasValidityPass(),
7475
new ResolveInvalidReferencesPass(),
7576
new AnalyzeServiceReferencesPass(true),
7677
new CheckCircularReferencesPass(),
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Compiler\CheckAliasValidityPass;
16+
use Symfony\Component\DependencyInjection\Compiler\CheckDefinitionValidityPass;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
19+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
20+
use Symfony\Component\DependencyInjection\Reference;
21+
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckAliasValidityPass\FooImplementing;
22+
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckAliasValidityPass\FooInterface;
23+
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckAliasValidityPass\FooNotImplementing;
24+
25+
class CheckAliasValidityPassTest extends TestCase
26+
{
27+
public function testProcessDetectsClassNotImplementingAliasedInterface(): void
28+
{
29+
$this->expectException(RuntimeException::class);
30+
$container = new ContainerBuilder();
31+
$container->register('a')->setClass(FooNotImplementing::class);
32+
$container->setAlias(FooInterface::class, 'a');
33+
34+
$this->process($container);
35+
}
36+
37+
public function testProcessAcceptsClassImplementingAliasedInterface(): void
38+
{
39+
$container = new ContainerBuilder();
40+
$container->register('a')->setClass(FooImplementing::class);
41+
$container->setAlias(FooInterface::class, 'a');
42+
43+
$this->process($container);
44+
$this->addToAssertionCount(1);
45+
}
46+
47+
public function testProcessIgnoresArbitraryAlias(): void
48+
{
49+
$container = new ContainerBuilder();
50+
$container->register('a')->setClass(FooImplementing::class);
51+
$container->setAlias('not_an_interface', 'a');
52+
53+
$this->process($container);
54+
$this->addToAssertionCount(1);
55+
}
56+
57+
public function testProcessIgnoresTargetWithFactory(): void
58+
{
59+
$container = new ContainerBuilder();
60+
$container->register('a')->setFactory(new Reference('foo'));
61+
$container->setAlias(FooInterface::class, 'a');
62+
63+
$this->process($container);
64+
$this->addToAssertionCount(1);
65+
}
66+
67+
public function testProcessIgnoresTargetWithoutClass(): void
68+
{
69+
$container = new ContainerBuilder();
70+
$container->register('a');
71+
$container->setAlias(FooInterface::class, 'a');
72+
73+
$this->process($container);
74+
$this->addToAssertionCount(1);
75+
}
76+
77+
protected function process(ContainerBuilder $container): void
78+
{
79+
$pass = new CheckAliasValidityPass();
80+
$pass->process($container);
81+
}
82+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckAliasValidityPass;
4+
5+
class FooImplementing implements FooInterface
6+
{
7+
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckAliasValidityPass;
4+
5+
interface FooInterface
6+
{
7+
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckAliasValidityPass;
4+
5+
class FooNotImplementing
6+
{
7+
8+
}

0 commit comments

Comments
 (0)