[DependencyInjection] Autoconfigurable attributes
This commit is contained in:
parent
f50e6afd7d
commit
2ab3caf080
@ -58,6 +58,7 @@ use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
|||||||
use Symfony\Component\DependencyInjection\Parameter;
|
use Symfony\Component\DependencyInjection\Parameter;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\EventListener;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
@ -549,6 +550,10 @@ class FrameworkExtension extends Extension
|
|||||||
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
|
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
|
||||||
->addMethodCall('setLogger', [new Reference('logger')]);
|
->addMethodCall('setLogger', [new Reference('logger')]);
|
||||||
|
|
||||||
|
$container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void {
|
||||||
|
$definition->addTag('kernel.event_listener', get_object_vars($attribute));
|
||||||
|
});
|
||||||
|
|
||||||
if (!$container->getParameter('kernel.debug')) {
|
if (!$container->getParameter('kernel.debug')) {
|
||||||
// remove tagged iterator argument for resource checkers
|
// remove tagged iterator argument for resource checkers
|
||||||
$container->getDefinition('config_cache_factory')->setArguments([]);
|
$container->getDefinition('config_cache_factory')->setArguments([]);
|
||||||
|
@ -7,6 +7,7 @@ CHANGELOG
|
|||||||
* Add `ServicesConfigurator::remove()` in the PHP-DSL
|
* Add `ServicesConfigurator::remove()` in the PHP-DSL
|
||||||
* Add `%env(not:...)%` processor to negate boolean values
|
* Add `%env(not:...)%` processor to negate boolean values
|
||||||
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
|
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
|
||||||
|
* Add autoconfigurable attributes
|
||||||
|
|
||||||
5.2.0
|
5.2.0
|
||||||
-----
|
-----
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
<?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\ChildDefinition;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Alexander M. Turek <me@derrabus.de>
|
||||||
|
*/
|
||||||
|
final class AttributeAutoconfigurationPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
private $ignoreAttributesTag;
|
||||||
|
|
||||||
|
public function __construct(string $ignoreAttributesTag = 'container.ignore_attributes')
|
||||||
|
{
|
||||||
|
$this->ignoreAttributesTag = $ignoreAttributesTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(ContainerBuilder $container): void
|
||||||
|
{
|
||||||
|
if (80000 > \PHP_VERSION_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$autoconfiguredAttributes = $container->getAutoconfiguredAttributes();
|
||||||
|
|
||||||
|
foreach ($container->getDefinitions() as $id => $definition) {
|
||||||
|
if (!$definition->isAutoconfigured()
|
||||||
|
|| $definition->isAbstract()
|
||||||
|
|| $definition->hasTag($this->ignoreAttributesTag)
|
||||||
|
|| !($reflector = $container->getReflectionClass($definition->getClass(), false))
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$instanceof = $definition->getInstanceofConditionals();
|
||||||
|
$conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition('');
|
||||||
|
foreach ($reflector->getAttributes() as $attribute) {
|
||||||
|
if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) {
|
||||||
|
$configurator($conditionals, $attribute->newInstance(), $reflector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$instanceof[$reflector->getName()] = $conditionals;
|
||||||
|
$definition->setInstanceofConditionals($instanceof);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,7 @@ class PassConfig
|
|||||||
100 => [
|
100 => [
|
||||||
new ResolveClassPass(),
|
new ResolveClassPass(),
|
||||||
new RegisterAutoconfigureAttributesPass(),
|
new RegisterAutoconfigureAttributesPass(),
|
||||||
|
new AttributeAutoconfigurationPass(),
|
||||||
new ResolveInstanceofConditionalsPass(),
|
new ResolveInstanceofConditionalsPass(),
|
||||||
new RegisterEnvVarProcessorsPass(),
|
new RegisterEnvVarProcessorsPass(),
|
||||||
],
|
],
|
||||||
|
@ -123,6 +123,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
|||||||
|
|
||||||
private $autoconfiguredInstanceof = [];
|
private $autoconfiguredInstanceof = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var callable[]
|
||||||
|
*/
|
||||||
|
private $autoconfiguredAttributes = [];
|
||||||
|
|
||||||
private $removedIds = [];
|
private $removedIds = [];
|
||||||
|
|
||||||
private $removedBindingIds = [];
|
private $removedBindingIds = [];
|
||||||
@ -671,6 +676,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
|||||||
|
|
||||||
$this->autoconfiguredInstanceof[$interface] = $childDefinition;
|
$this->autoconfiguredInstanceof[$interface] = $childDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($container->getAutoconfiguredAttributes() as $attribute => $configurator) {
|
||||||
|
if (isset($this->autoconfiguredAttributes[$attribute])) {
|
||||||
|
throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same attribute.', $attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->autoconfiguredAttributes[$attribute] = $configurator;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1309,6 +1322,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
|||||||
return $this->autoconfiguredInstanceof[$interface];
|
return $this->autoconfiguredInstanceof[$interface];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an attribute that will be used for autoconfiguring annotated classes.
|
||||||
|
*
|
||||||
|
* The configurator will receive a Definition instance and an instance of the attribute, in that order.
|
||||||
|
*/
|
||||||
|
public function registerAttributeForAutoconfiguration(string $attributeClass, callable $configurator): void
|
||||||
|
{
|
||||||
|
$this->autoconfiguredAttributes[$attributeClass] = $configurator;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers an autowiring alias that only binds to a specific argument name.
|
* Registers an autowiring alias that only binds to a specific argument name.
|
||||||
*
|
*
|
||||||
@ -1338,6 +1361,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
|||||||
return $this->autoconfiguredInstanceof;
|
return $this->autoconfiguredInstanceof;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return callable[]
|
||||||
|
*/
|
||||||
|
public function getAutoconfiguredAttributes(): array
|
||||||
|
{
|
||||||
|
return $this->autoconfiguredAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves env parameter placeholders in a string or an array.
|
* Resolves env parameter placeholders in a string or an array.
|
||||||
*
|
*
|
||||||
|
@ -16,14 +16,22 @@ use Symfony\Component\Config\FileLocator;
|
|||||||
use Symfony\Component\DependencyInjection\Alias;
|
use Symfony\Component\DependencyInjection\Alias;
|
||||||
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3Configurator;
|
||||||
use Symfony\Contracts\Service\ServiceProviderInterface;
|
use Symfony\Contracts\Service\ServiceProviderInterface;
|
||||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||||
|
|
||||||
@ -506,6 +514,109 @@ class IntegrationTest extends TestCase
|
|||||||
];
|
];
|
||||||
$this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]);
|
$this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires PHP 8
|
||||||
|
*/
|
||||||
|
public function testTagsViaAttribute()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->registerAttributeForAutoconfiguration(
|
||||||
|
CustomAutoconfiguration::class,
|
||||||
|
static function (ChildDefinition $definition, CustomAutoconfiguration $attribute, \ReflectionClass $reflector) {
|
||||||
|
$definition->addTag('app.custom_tag', get_object_vars($attribute) + ['class' => $reflector->getName()]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$container->register('one', TaggedService1::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->setAutoconfigured(true);
|
||||||
|
$container->register('two', TaggedService2::class)
|
||||||
|
->addTag('app.custom_tag', ['info' => 'This tag is not autoconfigured'])
|
||||||
|
->setPublic(true)
|
||||||
|
->setAutoconfigured(true);
|
||||||
|
|
||||||
|
$collector = new TagCollector();
|
||||||
|
$container->addCompilerPass($collector);
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
self::assertSame([
|
||||||
|
'one' => [
|
||||||
|
['someAttribute' => 'one', 'priority' => 0, 'class' => TaggedService1::class],
|
||||||
|
['someAttribute' => 'two', 'priority' => 0, 'class' => TaggedService1::class],
|
||||||
|
],
|
||||||
|
'two' => [
|
||||||
|
['info' => 'This tag is not autoconfigured'],
|
||||||
|
['someAttribute' => 'prio 100', 'priority' => 100, 'class' => TaggedService2::class],
|
||||||
|
],
|
||||||
|
], $collector->collectedTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires PHP 8
|
||||||
|
*/
|
||||||
|
public function testAttributesAreIgnored()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->registerAttributeForAutoconfiguration(
|
||||||
|
CustomAutoconfiguration::class,
|
||||||
|
static function (Definition $definition, CustomAutoconfiguration $attribute) {
|
||||||
|
$definition->addTag('app.custom_tag', get_object_vars($attribute));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$container->register('one', TaggedService1::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('container.ignore_attributes')
|
||||||
|
->setAutoconfigured(true);
|
||||||
|
$container->register('two', TaggedService2::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->setAutoconfigured(true);
|
||||||
|
|
||||||
|
$collector = new TagCollector();
|
||||||
|
$container->addCompilerPass($collector);
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
self::assertSame([
|
||||||
|
'two' => [
|
||||||
|
['someAttribute' => 'prio 100', 'priority' => 100],
|
||||||
|
],
|
||||||
|
], $collector->collectedTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires PHP 8
|
||||||
|
*/
|
||||||
|
public function testAutoconfigureViaAttribute()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->registerAttributeForAutoconfiguration(
|
||||||
|
CustomAutoconfiguration::class,
|
||||||
|
static function (ChildDefinition $definition) {
|
||||||
|
$definition
|
||||||
|
->addMethodCall('doSomething', [1, 2, 3])
|
||||||
|
->setBindings(['string $foo' => 'bar'])
|
||||||
|
->setConfigurator(new Reference('my_configurator'))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$container->register('my_configurator', TaggedService3Configurator::class);
|
||||||
|
$container->register('three', TaggedService3::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->setAutoconfigured(true);
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
/** @var TaggedService3 $service */
|
||||||
|
$service = $container->get('three');
|
||||||
|
|
||||||
|
self::assertSame('bar', $service->foo);
|
||||||
|
self::assertSame(6, $service->sum);
|
||||||
|
self::assertTrue($service->hasBeenConfigured);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServiceSubscriberStub implements ServiceSubscriberInterface
|
class ServiceSubscriberStub implements ServiceSubscriberInterface
|
||||||
@ -566,3 +677,13 @@ class IntegrationTestStubParent
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class TagCollector implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
public $collectedTags;
|
||||||
|
|
||||||
|
public function process(ContainerBuilder $container): void
|
||||||
|
{
|
||||||
|
$this->collectedTags = $container->findTaggedServiceIds('app.custom_tag');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
<?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\Fixtures\Attribute;
|
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
|
||||||
|
final class CustomAutoconfiguration
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $someAttribute,
|
||||||
|
public int $priority = 0,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<?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\Fixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration;
|
||||||
|
|
||||||
|
#[CustomAutoconfiguration(someAttribute: 'one')]
|
||||||
|
#[CustomAutoconfiguration(someAttribute: 'two')]
|
||||||
|
final class TaggedService1
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?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\Fixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration;
|
||||||
|
|
||||||
|
#[CustomAutoconfiguration(someAttribute: 'prio 100', priority: 100)]
|
||||||
|
final class TaggedService2
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<?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\Fixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration;
|
||||||
|
|
||||||
|
#[CustomAutoconfiguration(someAttribute: 'three')]
|
||||||
|
final class TaggedService3
|
||||||
|
{
|
||||||
|
public int $sum = 0;
|
||||||
|
public bool $hasBeenConfigured = false;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public string $foo,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doSomething(int $a, int $b, int $c): void
|
||||||
|
{
|
||||||
|
$this->sum = $a + $b + $c;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<?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\Fixtures;
|
||||||
|
|
||||||
|
final class TaggedService3Configurator
|
||||||
|
{
|
||||||
|
public function __invoke(TaggedService3 $service)
|
||||||
|
{
|
||||||
|
$service->hasBeenConfigured = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<?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\EventDispatcher\Attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service tag to autoconfigure event listeners.
|
||||||
|
*
|
||||||
|
* @author Alexander M. Turek <me@derrabus.de>
|
||||||
|
*/
|
||||||
|
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
|
||||||
|
class EventListener
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public ?string $event = null,
|
||||||
|
public ?string $method = null,
|
||||||
|
public int $priority = 0
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
5.3
|
||||||
|
---
|
||||||
|
|
||||||
|
* Add `EventListener` attribute for declaring listeners on PHP 8.
|
||||||
|
|
||||||
5.1.0
|
5.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -13,12 +13,19 @@ namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\EventListener;
|
||||||
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
|
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
|
||||||
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\Tests\Fixtures\CustomEvent;
|
||||||
|
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedInvokableListener;
|
||||||
|
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedMultiListener;
|
||||||
|
|
||||||
class RegisterListenersPassTest extends TestCase
|
class RegisterListenersPassTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -231,6 +238,90 @@ class RegisterListenersPassTest extends TestCase
|
|||||||
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
|
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires PHP 8
|
||||||
|
*/
|
||||||
|
public function testTaggedInvokableEventListener()
|
||||||
|
{
|
||||||
|
if (!class_exists(AttributeAutoconfigurationPass::class)) {
|
||||||
|
self::markTestSkipped('This test requires Symfony DependencyInjection >= 5.3');
|
||||||
|
}
|
||||||
|
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void {
|
||||||
|
$definition->addTag('kernel.event_listener', get_object_vars($attribute));
|
||||||
|
});
|
||||||
|
$container->register('foo', TaggedInvokableListener::class)->setAutoconfigured(true);
|
||||||
|
$container->register('event_dispatcher', \stdClass::class);
|
||||||
|
|
||||||
|
(new AttributeAutoconfigurationPass())->process($container);
|
||||||
|
(new ResolveInstanceofConditionalsPass())->process($container);
|
||||||
|
(new RegisterListenersPass())->process($container);
|
||||||
|
|
||||||
|
$definition = $container->getDefinition('event_dispatcher');
|
||||||
|
$expectedCalls = [
|
||||||
|
[
|
||||||
|
'addListener',
|
||||||
|
[
|
||||||
|
CustomEvent::class,
|
||||||
|
[new ServiceClosureArgument(new Reference('foo')), '__invoke'],
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires PHP 8
|
||||||
|
*/
|
||||||
|
public function testTaggedMultiEventListener()
|
||||||
|
{
|
||||||
|
if (!class_exists(AttributeAutoconfigurationPass::class)) {
|
||||||
|
self::markTestSkipped('This test requires Symfony DependencyInjection >= 5.3');
|
||||||
|
}
|
||||||
|
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->registerAttributeForAutoconfiguration(EventListener::class, static function (ChildDefinition $definition, EventListener $attribute): void {
|
||||||
|
$definition->addTag('kernel.event_listener', get_object_vars($attribute));
|
||||||
|
});
|
||||||
|
$container->register('foo', TaggedMultiListener::class)->setAutoconfigured(true);
|
||||||
|
$container->register('event_dispatcher', \stdClass::class);
|
||||||
|
|
||||||
|
(new AttributeAutoconfigurationPass())->process($container);
|
||||||
|
(new ResolveInstanceofConditionalsPass())->process($container);
|
||||||
|
(new RegisterListenersPass())->process($container);
|
||||||
|
|
||||||
|
$definition = $container->getDefinition('event_dispatcher');
|
||||||
|
$expectedCalls = [
|
||||||
|
[
|
||||||
|
'addListener',
|
||||||
|
[
|
||||||
|
CustomEvent::class,
|
||||||
|
[new ServiceClosureArgument(new Reference('foo')), 'onCustomEvent'],
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'addListener',
|
||||||
|
[
|
||||||
|
'foo',
|
||||||
|
[new ServiceClosureArgument(new Reference('foo')), 'onFoo'],
|
||||||
|
42,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'addListener',
|
||||||
|
[
|
||||||
|
'bar',
|
||||||
|
[new ServiceClosureArgument(new Reference('foo')), 'onBarEvent'],
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
|
||||||
|
}
|
||||||
|
|
||||||
public function testAliasedEventListener()
|
public function testAliasedEventListener()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
@ -416,10 +507,6 @@ final class AliasedEvent
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
final class CustomEvent
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
final class TypedListener
|
final class TypedListener
|
||||||
{
|
{
|
||||||
public function __invoke(AliasedEvent $event): void
|
public function __invoke(AliasedEvent $event): void
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
<?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\EventDispatcher\Tests\Fixtures;
|
||||||
|
|
||||||
|
final class CustomEvent
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
<?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\EventDispatcher\Tests\Fixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\EventListener;
|
||||||
|
|
||||||
|
#[EventListener]
|
||||||
|
final class TaggedInvokableListener
|
||||||
|
{
|
||||||
|
public function __invoke(CustomEvent $event): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
<?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\EventDispatcher\Tests\Fixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\EventListener;
|
||||||
|
|
||||||
|
#[EventListener(event: CustomEvent::class, method: 'onCustomEvent')]
|
||||||
|
#[EventListener(event: 'foo', priority: 42)]
|
||||||
|
#[EventListener(event: 'bar', method: 'onBarEvent')]
|
||||||
|
final class TaggedMultiListener
|
||||||
|
{
|
||||||
|
public function onCustomEvent(CustomEvent $event): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onFoo(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onBarEvent(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user