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

Skip to content

Commit ee18b9c

Browse files
committed
[DependencyInjection] Add CheckAliasValidityPass to lint:container
Adding a pass to check that an aliased interface resolves to a service which implements that interface
1 parent f450a49 commit ee18b9c

File tree

6 files changed

+159
-0
lines changed

6 files changed

+159
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Console\Input\InputInterface;
2020
use Symfony\Component\Console\Output\OutputInterface;
2121
use Symfony\Component\Console\Style\SymfonyStyle;
22+
use Symfony\Component\DependencyInjection\Compiler\CheckAliasValidityPass;
2223
use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
2324
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
2425
use Symfony\Component\DependencyInjection\Compiler\ResolveFactoryClassPass;
@@ -107,6 +108,7 @@ private function getContainerBuilder(): ContainerBuilder
107108
$container->setParameter('container.build_hash', 'lint_container');
108109
$container->setParameter('container.build_id', 'lint_container');
109110

111+
$container->addCompilerPass(new CheckAliasValidityPass(), PassConfig::TYPE_BEFORE_REMOVING, -100);
110112
$container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100);
111113

112114
return $this->container = $container;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
17+
/**
18+
* This pass validates aliases, it provides the following checks:
19+
*
20+
* - 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.
21+
*/
22+
class CheckAliasValidityPass implements CompilerPassInterface
23+
{
24+
public function process(ContainerBuilder $container): void
25+
{
26+
foreach ($container->getAliases() as $id => $alias) {
27+
try {
28+
if (!$container->hasDefinition((string) $alias)) {
29+
continue;
30+
}
31+
32+
$target = $container->getDefinition((string) $alias);
33+
if ($target->getClass() === null || $target->getFactory() !== null) {
34+
continue;
35+
}
36+
37+
$reflection = $container->getReflectionClass($id);
38+
if ($reflection === null || !$reflection->isInterface()) {
39+
continue;
40+
}
41+
42+
$targetReflection = $container->getReflectionClass($target->getClass());
43+
if ($targetReflection !== null && !$targetReflection->implementsInterface($id)) {
44+
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));
45+
}
46+
} catch (\ReflectionException) {
47+
continue;
48+
}
49+
}
50+
}
51+
}
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+
}
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 FooNotImplementing
6+
{
7+
8+
}

0 commit comments

Comments
 (0)