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

Skip to content

Commit a82aeb1

Browse files
committed
feat(di): add AsAlias attribute
1 parent b9eef0f commit a82aeb1

12 files changed

+208
-2
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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\Attribute;
13+
14+
/**
15+
* An attribute to tell under which alias a service should be registered or to use the implemented interface if no parameter is given.
16+
*/
17+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
18+
final class AsAlias
19+
{
20+
public function __construct(
21+
public ?string $id = null,
22+
public bool $public = false,
23+
) {
24+
}
25+
}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* Deprecate undefined and numeric keys with `service_locator` config
1212
* Fail if Target attribute does not exist during compilation
1313
* Enable deprecating parameters with `ContainerBuilder::deprecateParameter()`
14+
* Add `#[AsAlias]` attribute to tell under which alias a service should be registered or to use the implemented interface if no parameter is given
1415

1516
6.2
1617
---

src/Symfony/Component/DependencyInjection/Loader/FileLoader.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;
1818
use Symfony\Component\Config\Loader\Loader;
1919
use Symfony\Component\Config\Resource\GlobResource;
20+
use Symfony\Component\DependencyInjection\Alias;
21+
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
2022
use Symfony\Component\DependencyInjection\Attribute\When;
2123
use Symfony\Component\DependencyInjection\ChildDefinition;
2224
use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass;
2325
use Symfony\Component\DependencyInjection\ContainerBuilder;
2426
use Symfony\Component\DependencyInjection\Definition;
2527
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
28+
use Symfony\Component\DependencyInjection\Exception\LogicException;
2629

2730
/**
2831
* FileLoader is the abstract class used by all built-in loaders that are file based.
@@ -38,6 +41,8 @@ abstract class FileLoader extends BaseFileLoader
3841
protected $instanceof = [];
3942
protected $interfaces = [];
4043
protected $singlyImplemented = [];
44+
/** @var array<string, Alias> */
45+
protected $aliases = [];
4146
protected $autoRegisterAliasesForSinglyImplementedInterfaces = true;
4247

4348
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null)
@@ -140,12 +145,37 @@ public function registerClasses(Definition $prototype, string $namespace, string
140145

141146
continue;
142147
}
148+
$interfaces = [];
143149
foreach (class_implements($class, false) as $interface) {
144150
$this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class;
151+
$interfaces[] = $interface;
152+
}
153+
154+
if (!$autoconfigureAttributes) {
155+
continue;
156+
}
157+
$r = $this->container->getReflectionClass($class);
158+
$defaultAlias = 1 === \count($interfaces) ? $interfaces[0] : null;
159+
foreach ($r->getAttributes(AsAlias::class) as $attr) {
160+
/** @var AsAlias $attribute */
161+
$attribute = $attr->newInstance();
162+
$alias = $attribute->id ?? $defaultAlias;
163+
$public = $attribute->public;
164+
if (null === $alias) {
165+
throw new LogicException(sprintf('Alias cannot be automatically determined for class "%s". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].', $class));
166+
}
167+
if (isset($this->aliases[$alias])) {
168+
throw new LogicException(sprintf('The "%s" alias has already been defined with the #[AsAlias] attribute in "%s".', $alias, $this->aliases[$alias]));
169+
}
170+
$this->aliases[$alias] = new Alias($class, $public);
145171
}
146172
}
147173
}
148174

175+
foreach ($this->aliases as $alias => $aliasDefinition) {
176+
$this->container->setAlias($alias, $aliasDefinition);
177+
}
178+
149179
if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) {
150180
$this->registerAliasesForSinglyImplementedInterfaces();
151181
}
@@ -157,12 +187,12 @@ public function registerClasses(Definition $prototype, string $namespace, string
157187
public function registerAliasesForSinglyImplementedInterfaces()
158188
{
159189
foreach ($this->interfaces as $interface) {
160-
if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) {
190+
if (!empty($this->singlyImplemented[$interface]) && !isset($this->aliases[$interface]) && !$this->container->has($interface)) {
161191
$this->container->setAlias($interface, $this->singlyImplemented[$interface]);
162192
}
163193
}
164194

165-
$this->interfaces = $this->singlyImplemented = [];
195+
$this->interfaces = $this->singlyImplemented = $this->aliases = [];
166196
}
167197

168198
/**
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias;
4+
5+
interface AliasBarInterface
6+
{
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias;
4+
5+
interface AliasFooInterface
6+
{
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias;
4+
5+
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
6+
7+
#[AsAlias(id: AliasFooInterface::class)]
8+
class WithAsAlias
9+
{
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias;
4+
5+
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
6+
7+
#[AsAlias(id: AliasFooInterface::class)]
8+
class WithAsAliasDuplicate
9+
{
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias;
4+
5+
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
6+
7+
#[AsAlias(id: AliasBarInterface::class)]
8+
class WithAsAliasIdMultipleInterface implements AliasFooInterface, AliasBarInterface
9+
{
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias;
4+
5+
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
6+
7+
#[AsAlias]
8+
class WithAsAliasInterface implements AliasFooInterface
9+
{
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias;
4+
5+
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
6+
7+
#[AsAlias(id: AliasFooInterface::class, public: true)]
8+
#[AsAlias(id: 'some-alias')]
9+
class WithAsAliasMultiple
10+
{
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias;
4+
5+
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
6+
7+
#[AsAlias]
8+
class WithAsAliasMultipleInterface implements AliasFooInterface, AliasBarInterface
9+
{
10+
}

src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Config\FileLocator;
1616
use Symfony\Component\Config\Loader\LoaderResolver;
17+
use Symfony\Component\DependencyInjection\Alias;
1718
use Symfony\Component\DependencyInjection\ChildDefinition;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
1920
use Symfony\Component\DependencyInjection\Definition;
2021
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
22+
use Symfony\Component\DependencyInjection\Exception\LogicException;
2123
use Symfony\Component\DependencyInjection\Loader\FileLoader;
2224
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
2325
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
@@ -32,6 +34,12 @@
3234
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Baz;
3335
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar;
3436
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\BarInterface;
37+
use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\AliasBarInterface;
38+
use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\AliasFooInterface;
39+
use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAlias;
40+
use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasIdMultipleInterface;
41+
use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasInterface;
42+
use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasMultiple;
3543

3644
class FileLoaderTest extends TestCase
3745
{
@@ -274,6 +282,73 @@ public function testRegisterClassesWithWhenEnv(?string $env, bool $expected)
274282

275283
$this->assertSame($expected, $container->has(Foo::class));
276284
}
285+
286+
/**
287+
* @dataProvider provideResourcesWithAsAliasAttributes
288+
*/
289+
public function testRegisterClassesWithAsAlias(string $resource, array $expectedAliases)
290+
{
291+
$container = new ContainerBuilder();
292+
$loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures'));
293+
$loader->registerClasses(
294+
(new Definition())->setAutoconfigured(true),
295+
'Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\\',
296+
$resource
297+
);
298+
299+
$this->assertEquals($expectedAliases, $container->getAliases());
300+
}
301+
302+
public static function provideResourcesWithAsAliasAttributes(): iterable
303+
{
304+
yield 'Private' => ['PrototypeAsAlias/{WithAsAlias,AliasFooInterface}.php', [AliasFooInterface::class => new Alias(WithAsAlias::class)]];
305+
yield 'Interface' => ['PrototypeAsAlias/{WithAsAliasInterface,AliasFooInterface}.php', [AliasFooInterface::class => new Alias(WithAsAliasInterface::class)]];
306+
yield 'Multiple' => ['PrototypeAsAlias/{WithAsAliasMultiple,AliasFooInterface}.php', [
307+
AliasFooInterface::class => new Alias(WithAsAliasMultiple::class, true),
308+
'some-alias' => new Alias(WithAsAliasMultiple::class),
309+
]];
310+
yield 'Multiple with id' => ['PrototypeAsAlias/{WithAsAliasIdMultipleInterface,AliasBarInterface,AliasFooInterface}.php', [
311+
AliasBarInterface::class => new Alias(WithAsAliasIdMultipleInterface::class),
312+
AliasFooInterface::class => new Alias(WithAsAliasIdMultipleInterface::class),
313+
]];
314+
}
315+
316+
/**
317+
* @dataProvider provideResourcesWithDuplicatedAsAliasAttributes
318+
*/
319+
public function testRegisterClassesWithDuplicatedAsAlias(string $resource, string $expectedExceptionMessage)
320+
{
321+
$this->expectException(LogicException::class);
322+
$this->expectExceptionMessage($expectedExceptionMessage);
323+
324+
$container = new ContainerBuilder();
325+
$loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures'));
326+
$loader->registerClasses(
327+
(new Definition())->setAutoconfigured(true),
328+
'Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\\',
329+
$resource
330+
);
331+
}
332+
333+
public static function provideResourcesWithDuplicatedAsAliasAttributes(): iterable
334+
{
335+
yield 'Duplicated' => ['PrototypeAsAlias/{WithAsAlias,WithAsAliasDuplicate,AliasFooInterface}.php', 'The "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\AliasFooInterface" alias has already been defined with the #[AsAlias] attribute in "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAlias".'];
336+
yield 'Interface duplicated' => ['PrototypeAsAlias/{WithAsAliasInterface,WithAsAlias,AliasFooInterface}.php', 'The "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\AliasFooInterface" alias has already been defined with the #[AsAlias] attribute in "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAlias".'];
337+
}
338+
339+
public function testRegisterClassesWithAsAliasAndImplementingMultipleInterfaces()
340+
{
341+
$this->expectException(LogicException::class);
342+
$this->expectExceptionMessage('Alias cannot be automatically determined for class "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasMultipleInterface". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].');
343+
344+
$container = new ContainerBuilder();
345+
$loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures'));
346+
$loader->registerClasses(
347+
(new Definition())->setAutoconfigured(true),
348+
'Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\\',
349+
'PrototypeAsAlias/{WithAsAliasMultipleInterface,AliasBarInterface,AliasFooInterface}.php'
350+
);
351+
}
277352
}
278353

279354
class TestFileLoader extends FileLoader

0 commit comments

Comments
 (0)