Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -779,32 +779,13 @@ public function load(array $configs, ContainerBuilder $container): void

$container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
$tagAttributes = get_object_vars($attribute);

if (!$reflector instanceof \ReflectionMethod) {
$definition->addTag('kernel.event_listener', $tagAttributes);

return;
}

if (isset($tagAttributes['method'])) {
throw new LogicException(\sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
}

$tagAttributes['method'] = $reflector->getName();

if (!$eventArg = $reflector->getParameters()[0] ?? null) {
throw new LogicException(\sprintf('AsEventListener attribute requires the first argument of "%s::%s()" to be an event object.', $reflector->class, $reflector->name));
}

$types = ($type = $eventArg->getType() instanceof \ReflectionUnionType ? $eventArg->getType()->getTypes() : [$eventArg->getType()]) ?: [];

foreach ($types as $type) {
if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
$tagAttributes['event'] = $type->getName();

$definition->addTag('kernel.event_listener', $tagAttributes);
if ($reflector instanceof \ReflectionMethod) {
if (isset($tagAttributes['method'])) {
throw new LogicException(\sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
}
$tagAttributes['method'] = $reflector->getName();
}
$definition->addTag('kernel.event_listener', $tagAttributes);
Comment on lines +782 to +788
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just reverts the changes made in #61252.

});
$container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void {
$definition->addTag('controller.service_arguments');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,27 @@ public function process(ContainerBuilder $container): void
foreach ($container->findTaggedServiceIds('kernel.event_listener', true) as $id => $events) {
$noPreload = 0;

$resolvedEvents = [];
foreach ($events as $event) {
$priority = $event['priority'] ?? 0;

if (!isset($event['event'])) {
if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) {
continue;
}

$event['method'] ??= '__invoke';
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
$eventNames = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
} else {
$eventNames = [$event['event']];
}

foreach ($eventNames as $eventName) {
$event['event'] = $aliases[$eventName] ?? $eventName;
$resolvedEvents[] = $event;
}
}

$event['event'] = $aliases[$event['event']] ?? $event['event'];
foreach ($resolvedEvents as $event) {
$priority = $event['priority'] ?? 0;

if (!isset($event['method'])) {
$event['method'] = 'on'.preg_replace_callback([
Expand Down Expand Up @@ -167,21 +175,40 @@ public function process(ContainerBuilder $container): void
}
}

private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
/**
* @return string[]
*/
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): array
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual new implementation.

{
if (
null === ($class = $container->getDefinition($id)->getClass())
|| !($r = $container->getReflectionClass($class, false))
|| !$r->hasMethod($method)
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|| !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
|| !(($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType || $type instanceof \ReflectionUnionType)
) {
throw new InvalidArgumentException(\sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
}

return $name;
$types = $type instanceof \ReflectionUnionType ? $type->getTypes() : [$type];

$names = [];
foreach ($types as $type) {
if (!$type instanceof \ReflectionNamedType
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
) {
continue;
}

$names[] = $name;
}

if (!$names) {
throw new InvalidArgumentException(\sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
}

return $names;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;

use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
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\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
Expand Down Expand Up @@ -275,10 +274,7 @@ public function testItThrowsAnExceptionIfTagIsMissingMethodAndClassHasNoValidMet

public function testTaggedInvokableEventListener()
{
$container = new ContainerBuilder();
$container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute): void {
$definition->addTag('kernel.event_listener', get_object_vars($attribute));
});
$container = $this->createContainerBuilder();
$container->register('foo', TaggedInvokableListener::class)->setAutoconfigured(true);
$container->register('event_dispatcher', \stdClass::class);

Expand All @@ -297,21 +293,12 @@ public function testTaggedInvokableEventListener()
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
}

public function testTaggedMultiEventListener()
{
$container = new ContainerBuilder();
$container->registerAttributeForAutoconfiguration(AsEventListener::class,
static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector): void {
$tagAttributes = get_object_vars($attribute);
if ($reflector instanceof \ReflectionMethod) {
$tagAttributes['method'] = $reflector->getName();
}
$definition->addTag('kernel.event_listener', $tagAttributes);
}
);
Comment on lines -306 to -314
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the reason the tests passed when they shouldn't have.

$container = $this->createContainerBuilder();

$container->register('foo', TaggedMultiListener::class)->setAutoconfigured(true);
$container->register('event_dispatcher', \stdClass::class);
Expand Down Expand Up @@ -355,42 +342,12 @@ static function (ChildDefinition $definition, AsEventListener $attribute, \Refle
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
}

public function testTaggedMethodUnionTypeEventListener()
{
$container = new ContainerBuilder();
$container->registerAttributeForAutoconfiguration(AsEventListener::class,
static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
$tagAttributes = get_object_vars($attribute);

if (!$reflector instanceof \ReflectionMethod) {
$definition->addTag('kernel.event_listener', $tagAttributes);

return;
}

if (isset($tagAttributes['method'])) {
throw new \LogicException(\sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
}

$tagAttributes['method'] = $reflector->getName();

if (!$eventArg = $reflector->getParameters()[0] ?? null) {
throw new \LogicException(\sprintf('AsEventListener attribute requires the first argument of "%s::%s()" to be an event object.', $reflector->class, $reflector->name));
}

$types = ($type = $eventArg->getType() instanceof \ReflectionUnionType ? $eventArg->getType()->getTypes() : [$eventArg->getType()]) ?: [];

foreach ($types as $type) {
if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
$tagAttributes['event'] = $type->getName();

$definition->addTag('kernel.event_listener', $tagAttributes);
}
}
});
$container = $this->createContainerBuilder();

$container->register('foo', TaggedUnionTypeListener::class)->setAutoconfigured(true);
$container->register('event_dispatcher', \stdClass::class);
Expand Down Expand Up @@ -419,7 +376,7 @@ static function (ChildDefinition $definition, AsEventListener $attribute, \Refle
],
];

$this->assertEquals($expectedCalls, $definition->getMethodCalls());
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
}

public function testAliasedEventListener()
Expand Down Expand Up @@ -569,6 +526,17 @@ public function testOmitEventNameOnSubscriber()
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}

private function createContainerBuilder(): ContainerBuilder
{
$container = new ContainerBuilder();
$container->setParameter('kernel.debug', true);
$container->setParameter('kernel.project_dir', __DIR__);
$container->setParameter('kernel.container_class', 'testContainer');
(new FrameworkExtension())->load([], $container);

return $container;
}
}

class SubscriberService implements EventSubscriberInterface
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/EventDispatcher/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"symfony/expression-language": "^6.4|^7.0|^8.0",
"symfony/config": "^6.4|^7.0|^8.0",
"symfony/error-handler": "^6.4|^7.0|^8.0",
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do without this require? it feels reverse to have a component require a bundle

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I felt the same way, but the reason the previous PR didn't break the existing tests was that it didn't update the registerAttributeForAutoconfiguration() closure in the tests. I'm not sure how to ensure this doesn't happen again.

"symfony/http-foundation": "^6.4|^7.0|^8.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/stopwatch": "^6.4|^7.0|^8.0",
Expand Down
Loading