Skip to content
Snippets Groups Projects
Commit 46cabd7b authored by Oliver Bartsch's avatar Oliver Bartsch
Browse files

[BUGFIX] Support union types for event listeners

Since #94345 it's possible to omit the "event"
identifier when configuring an event listener,
since the service can be resolved using reflection.
This however did until now not work when using
union types. This patch therefore adjusts the
corresponding compiler pass, making it possible
to use the same method for listing on multiple
events.

Resolves: #101264
Related: #94345
Releases: main, 12.4
Change-Id: I5668269104515811d3c916c832942ef532bdfa27
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79821


Tested-by: default avatarcore-ci <typo3@b13.com>
Reviewed-by: default avatarOliver Bartsch <bo@cedev.de>
Tested-by: default avatarOliver Bartsch <bo@cedev.de>
parent 1b5041a7
Branches
Tags
No related merge requests found
......@@ -188,10 +188,8 @@ services:
tags:
- name: event.listener
identifier: 'typo3/cms-backend/failed-login-attempt-notification'
event: TYPO3\CMS\Core\Authentication\Event\LoginAttemptFailedEvent
- name: event.listener
identifier: 'typo3/cms-backend/failed-mfa-verification-notification'
event: TYPO3\CMS\Core\Authentication\Event\MfaVerificationFailedEvent
TYPO3\CMS\Backend\Security\EmailLoginNotification:
tags:
......
......@@ -79,30 +79,37 @@ final class ListenerProviderPass implements CompilerPassInterface
$service = $container->findDefinition($serviceName);
$service->setPublic(true);
foreach ($tags as $attributes) {
$eventIdentifier = $attributes['event'] ?? $this->getParameterType($serviceName, $service, $attributes['method'] ?? '__invoke');
if (!$eventIdentifier) {
$eventIdentifiers = $attributes['event'] ?? $this->getParameterType($serviceName, $service, $attributes['method'] ?? '__invoke');
if (empty($eventIdentifiers)) {
throw new \InvalidArgumentException(
'Service tag "event.listener" requires an event attribute to be defined or the listener method must declare a parameter type. Missing in: ' . $serviceName,
1563217364
);
}
$listenerIdentifier = $attributes['identifier'] ?? $serviceName;
$unorderedEventListeners[$eventIdentifier][$listenerIdentifier] = [
'service' => $serviceName,
'method' => $attributes['method'] ?? null,
'before' => GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
'after' => GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
];
if (is_string($eventIdentifiers)) {
$eventIdentifiers = [$eventIdentifiers];
}
foreach ($eventIdentifiers as $eventIdentifier) {
$listenerIdentifier = $attributes['identifier'] ?? $serviceName;
$unorderedEventListeners[$eventIdentifier][$listenerIdentifier] = [
'service' => $serviceName,
'method' => $attributes['method'] ?? null,
'before' => GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
'after' => GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
];
}
}
}
return $unorderedEventListeners;
}
/**
* Derives the class type of the first argument of a given method.
* Derives the class type(s) of the first argument of a given method.
* Supporting union types, this method returns the class type(s) as list.
*
* @return string[]|null A list of class types or NULL on failure
*/
protected function getParameterType(string $serviceName, Definition $definition, string $method = '__invoke'): ?string
protected function getParameterType(string $serviceName, Definition $definition, string $method = '__invoke'): ?array
{
// A Reflection exception should never actually get thrown here, but linters want a try-catch just in case.
try {
......@@ -114,13 +121,28 @@ final class ListenerProviderPass implements CompilerPassInterface
}
$params = $this->getReflectionMethod($serviceName, $definition, $method)->getParameters();
$rType = count($params) ? $params[0]->getType() : null;
if (!$rType instanceof \ReflectionNamedType) {
throw new \InvalidArgumentException(
sprintf('Service "%s" registers method "%s" as an event listener, but does not specify an event type and the method does not type a parameter. Declare a class type for the method parameter or specify an event class explicitly', $serviceName, $method),
1623881315,
);
if ($rType instanceof \ReflectionNamedType) {
return [$rType->getName()];
}
if ($rType instanceof \ReflectionUnionType) {
$types = [];
foreach ($rType->getTypes() as $type) {
if ($type instanceof \ReflectionNamedType) {
$types[] = $type->getName();
}
}
if ($types === []) {
throw new \InvalidArgumentException(
sprintf('Service "%s" registers method "%s" as an event listener, but does not specify an event type and the method\'s first parameter does not contain a valid class type. Declare valid class types for the method parameter or specify the event classes explicitly', $serviceName, $method),
1688646662,
);
}
return $types;
}
return $rType->getName();
throw new \InvalidArgumentException(
sprintf('Service "%s" registers method "%s" as an event listener, but does not specify an event type and the method does not type a parameter. Declare a class type for the method parameter or specify an event class explicitly', $serviceName, $method),
1623881315,
);
} catch (\ReflectionException $e) {
// The collectListeners() method will convert this to an exception.
return null;
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment