Skip to content

Commit

Permalink
[BUGFIX] Support union types for event listeners
Browse files Browse the repository at this point in the history
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: core-ci <typo3@b13.com>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Oliver Bartsch <bo@cedev.de>
  • Loading branch information
o-ba committed Jul 6, 2023
1 parent 1b5041a commit 46cabd7
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 20 deletions.
2 changes: 0 additions & 2 deletions typo3/sysext/backend/Configuration/Services.yaml
Expand Up @@ -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:
Expand Down
Expand Up @@ -79,30 +79,37 @@ protected function collectListeners(ContainerBuilder $container): array
$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 {
Expand All @@ -114,13 +121,28 @@ protected function getParameterType(string $serviceName, Definition $definition,
}
$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;
Expand Down

0 comments on commit 46cabd7

Please sign in to comment.