‪TYPO3CMS  ‪main
MessageHandlerPass.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
21 use Symfony\Component\DependencyInjection\ContainerBuilder;
22 use Symfony\Component\DependencyInjection\Definition;
23 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
27 
31 final class ‪MessageHandlerPass implements CompilerPassInterface
32 {
33  private readonly string ‪$tagName;
34 
35  private ContainerBuilder ‪$container;
36 
38 
39  public function ‪__construct(string ‪$tagName)
40  {
41  $this->tagName = ‪$tagName;
42  $this->orderingService = new ‪DependencyOrderingService();
43  }
44 
45  public function ‪process(ContainerBuilder ‪$container): void
46  {
47  $this->container = ‪$container;
48 
49  $handlersLocatorFactory = ‪$container->findDefinition(HandlersLocatorFactory::class);
50 
51  foreach ($this->‪collectHandlers($container) as $message => $handlers) {
52  foreach ($this->orderingService->orderByDependencies($handlers) as $handler) {
53  $handlersLocatorFactory->addMethodCall('addHandler', [
54  $message,
55  $handler['service'],
56  $handler['method'] ?? '__invoke',
57  ]);
58  }
59  }
60  }
61 
65  private function ‪collectHandlers(ContainerBuilder ‪$container): array
66  {
67  $unorderedHandlers = [];
68  foreach (‪$container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
69  $service = ‪$container->findDefinition($serviceName);
70  $service->setPublic(true);
71  foreach ($tags as $attributes) {
72  $messageHandler = $attributes['message'] ?? $this->‪getParameterType($serviceName, $service, $attributes['method'] ?? '__invoke');
73  if (!$messageHandler) {
74  throw new \InvalidArgumentException(
75  'Service tag "messenger.message_handler" requires a message attribute to be defined or the method must declare a parameter type. Missing in: ' . $serviceName,
76  1606732015
77  );
78  }
79 
80  $messageIdentifier = $attributes['identifier'] ?? $serviceName;
81  $unorderedHandlers[$messageHandler][$messageIdentifier] = [
82  'service' => $serviceName,
83  'method' => $attributes['method'] ?? null,
84  'before' => ‪GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
85  'after' => ‪GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
86  ];
87  }
88  }
89  return $unorderedHandlers;
90  }
91 
95  private function ‪getParameterType(string $serviceName, Definition $definition, string $method = '__invoke'): ?string
96  {
97  // A Reflection exception should never actually get thrown here, but linters want a try-catch just in case.
98  try {
99  if (!$definition->isAutowired()) {
100  throw new \InvalidArgumentException(
101  sprintf(
102  'Service "%s" has message handlers defined but does not declare a message to handle to and is not configured to autowire it from the handle method. Set autowire: true to enable auto-detection of the handled message.',
103  $serviceName
104  ),
105  1606732016,
106  );
107  }
108  $params = $this->‪getReflectionMethod($serviceName, $definition, $method)->getParameters();
109  $rType = count($params) ? $params[0]->getType() : null;
110  if (!$rType instanceof \ReflectionNamedType) {
111  throw new \InvalidArgumentException(
112  sprintf(
113  'Service "%s" registers method "%s" as a message handler, but does not specify a message type and the method does not type a parameter. Declare a class type for the method parameter or specify a message class explicitly',
114  $serviceName,
115  $method
116  ),
117  1606732017,
118  );
119  }
120  return $rType->getName();
121  } catch (\ReflectionException $e) {
122  // The collectHandlers() method will convert this to an exception.
123  return null;
124  }
125  }
126 
132  private function ‪getReflectionMethod(string $serviceName, Definition $definition, string $method): \ReflectionFunctionAbstract
133  {
134  if (!$class = $definition->getClass()) {
135  throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $serviceName), 1606732018);
136  }
137 
138  if (!$r = $this->container->getReflectionClass($class)) {
139  throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $serviceName, $class), 1606732019);
140  }
141 
142  if (!$r->hasMethod($method)) {
143  throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $serviceName, $class !== $serviceName ? $class . '::' . $method : $method), 1606732020);
144  }
145 
146  $r = $r->getMethod($method);
147  if (!$r->isPublic()) {
148  throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $serviceName, $class !== $serviceName ? $class . '::' . $method : $method), 1606732021);
149  }
150 
151  return $r;
152  }
153 }
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass\collectHandlers
‪collectHandlers(ContainerBuilder $container)
Definition: MessageHandlerPass.php:65
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass\$orderingService
‪DependencyOrderingService $orderingService
Definition: MessageHandlerPass.php:37
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass\process
‪process(ContainerBuilder $container)
Definition: MessageHandlerPass.php:45
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass\getReflectionMethod
‪getReflectionMethod(string $serviceName, Definition $definition, string $method)
Definition: MessageHandlerPass.php:132
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass\$container
‪ContainerBuilder $container
Definition: MessageHandlerPass.php:35
‪TYPO3\CMS\Core\DependencyInjection
Definition: AutowireInjectMethodsPass.php:18
‪TYPO3\CMS\Core\Service\DependencyOrderingService
Definition: DependencyOrderingService.php:32
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass\$tagName
‪readonly string $tagName
Definition: MessageHandlerPass.php:33
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass
Definition: MessageHandlerPass.php:32
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass\__construct
‪__construct(string $tagName)
Definition: MessageHandlerPass.php:39
‪TYPO3\CMS\Core\DependencyInjection\MessageHandlerPass\getParameterType
‪getParameterType(string $serviceName, Definition $definition, string $method='__invoke')
Definition: MessageHandlerPass.php:95
‪TYPO3\CMS\Core\Messenger\HandlersLocatorFactory
Definition: HandlersLocatorFactory.php:28
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822