Skip to content

[DependencyInjection] Add #[Alias] to tell how service should be aliased #41476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/Symfony/Component/DependencyInjection/Attribute/Alias.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Attribute;

/**
* An attribute to tell how service should be aliased.
*
* @author Alexandre Daubois <alex.daubois@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class Alias
{
public function __construct(
public ?string $name = null,
) {
}
}
4 changes: 4 additions & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
CHANGELOG
=========

5.4
---
* Add `#[Alias]` to tell how service should be aliased

5.3
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function __construct()

$this->optimizationPasses = [[
new AutoAliasServicePass(),
new RegisterAliasAttributesPass(),
new ValidateEnvPlaceholdersPass(),
new ResolveDecoratorStackPass(),
new ResolveChildDefinitionsPass(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Attribute\Alias as AliasAttribute;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

/**
* Reads #[Alias] attributes on definitions which don't have the
* "container.ignore_attributes" tag.
*
* @author Alexandre Daubois <alex.daubois@gmail.com>
*/
final class RegisterAliasAttributesPass implements CompilerPassInterface
{
private static $registerForAliasing;

/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (80000 > \PHP_VERSION_ID) {
return;
}

foreach ($container->getDefinitions() as $id => $definition) {
if ($this->accept($definition) && $definition->isAutoconfigured() && null !== $class = $container->getReflectionClass($definition->getClass())) {
$this->processClass($container, $class);
}
}
}

public function accept(Definition $definition): bool
{
return 80000 <= \PHP_VERSION_ID && !$definition->hasTag('container.ignore_attributes');
}

public function processClass(ContainerBuilder $container, \ReflectionClass $class)
{
foreach ($class->getAttributes(AliasAttribute::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
self::registerForAliasing($container, $class, $attribute);
}
}

private static function registerForAliasing(ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute): void
{
if (self::$registerForAliasing) {
(self::$registerForAliasing)($container, $class, $attribute);

return;
}

self::$registerForAliasing = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) {
$attribute = (array) $attribute->newInstance();
$container->setAlias($attribute['name'], new Alias($class->name));
};

(self::$registerForAliasing)($container, $class, $attribute);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Tests\Compiler;

use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Compiler\RegisterAliasAttributesPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Tests\Fixtures\AliasAttributed;

/**
* @requires PHP 8
*/
class RegisterAliasAttributesPassTest extends TestCase
{
public function testProcess()
{
$container = new ContainerBuilder();
$container->register('foo', AliasAttributed::class)
->setAutoconfigured(true);

(new RegisterAliasAttributesPass())->process($container);

$this->assertEquals(AliasAttributed::class, $container->getAlias('my_alias_attribute'));
}

public function testIgnoreAttribute()
{
$container = new ContainerBuilder();
$container->register('foo', AliasAttributed::class)
->addTag('container.ignore_attributes')
->setAutoconfigured(true);

(new RegisterAliasAttributesPass())->process($container);

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The service alias "my_alias_attribute" does not exist.');
$container->getAlias('my_alias_attribute');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Symfony\Component\DependencyInjection\Tests\Fixtures;

use Symfony\Component\DependencyInjection\Attribute\Alias;

#[Alias('my_alias_attribute')]
class AliasAttributed
{
}