‪TYPO3CMS  ‪main
ListenerProviderPass.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 ‪ListenerProviderPass implements CompilerPassInterface
32 {
33  private string ‪$tagName;
34 
35  private ContainerBuilder ‪$container;
36 
38 
39  public function ‪__construct(string ‪$tagName)
40  {
41  $this->tagName = ‪$tagName;
42  $this->orderer = new ‪DependencyOrderingService();
43  }
44 
48  public function ‪process(ContainerBuilder ‪$container): void
49  {
50  $this->container = ‪$container;
51 
52  if (!‪$container->hasDefinition(ListenerProvider::class)) {
53  // If there's no listener provider registered to begin with, don't bother registering listeners with it.
54  return;
55  }
56  $listenerProviderDefinition = ‪$container->findDefinition(ListenerProvider::class);
57 
58  $unorderedEventListeners = $this->‪collectListeners($container);
59 
60  foreach ($unorderedEventListeners as $eventName => $listeners) {
61  // Configure ListenerProvider factory to include these listeners
62  foreach ($this->orderer->orderByDependencies($listeners) as $listenerIdentifier => $listener) {
63  $listenerProviderDefinition->addMethodCall('addListener', [
64  $eventName,
65  $listener['service'],
66  $listener['method'],
67  $listenerIdentifier,
68  ]);
69  }
70  }
71  }
72 
76  protected function ‪collectListeners(ContainerBuilder ‪$container): array
77  {
78  $unorderedEventListeners = [];
79  foreach (‪$container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
80  $service = ‪$container->findDefinition($serviceName);
81  $service->setPublic(true);
82  foreach ($tags as $attributes) {
83  $eventIdentifiers = $attributes['event'] ?? $this->‪getParameterType($serviceName, $service, $attributes['method'] ?? '__invoke');
84  if (empty($eventIdentifiers)) {
85  throw new \InvalidArgumentException(
86  'Service tag "event.listener" requires an event attribute to be defined or the listener method must declare a parameter type. Missing in: ' . $serviceName,
87  1563217364
88  );
89  }
90  if (is_string($eventIdentifiers)) {
91  $eventIdentifiers = [$eventIdentifiers];
92  }
93  foreach ($eventIdentifiers as $eventIdentifier) {
94  $listenerIdentifier = $attributes['identifier'] ?? $serviceName;
95  $unorderedEventListeners[$eventIdentifier][$listenerIdentifier] = [
96  'service' => $serviceName,
97  'method' => $attributes['method'] ?? null,
98  'before' => GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
99  'after' => GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
100  ];
101  }
102  }
103  }
104  return $unorderedEventListeners;
105  }
106 
113  protected function ‪getParameterType(string $serviceName, Definition $definition, string $method = '__invoke'): ?array
114  {
115  // A Reflection exception should never actually get thrown here, but linters want a try-catch just in case.
116  try {
117  if (!$definition->isAutowired()) {
118  throw new \InvalidArgumentException(
119  sprintf('Service "%s" has event listeners defined but does not declare an event to listen to and is not configured to autowire it from the listener method. Set autowire: true to enable auto-detection of the listener event.', $serviceName),
120  1623881314,
121  );
122  }
123  $params = $this->‪getReflectionMethod($serviceName, $definition, $method)->getParameters();
124  $rType = count($params) ? $params[0]->getType() : null;
125  if ($rType instanceof \ReflectionNamedType) {
126  return [$rType->getName()];
127  }
128  if ($rType instanceof \ReflectionUnionType) {
129  $types = [];
130  foreach ($rType->getTypes() as $type) {
131  if ($type instanceof \ReflectionNamedType) {
132  $types[] = $type->getName();
133  }
134  }
135  if ($types === []) {
136  throw new \InvalidArgumentException(
137  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),
138  1688646662,
139  );
140  }
141  return $types;
142  }
143  throw new \InvalidArgumentException(
144  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),
145  1623881315,
146  );
147  } catch (\ReflectionException $e) {
148  // The collectListeners() method will convert this to an exception.
149  return null;
150  }
151  }
152 
158  protected function ‪getReflectionMethod(string $serviceName, Definition $definition, string $method): \ReflectionFunctionAbstract
159  {
160  if (!$class = $definition->getClass()) {
161  throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $serviceName), 1623881310);
162  }
163 
164  if (!$r = $this->container->getReflectionClass($class)) {
165  throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $serviceName, $class), 1623881311);
166  }
167 
168  if (!$r->hasMethod($method)) {
169  throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $serviceName, $class !== $serviceName ? $class . '::' . $method : $method), 1623881312);
170  }
171 
172  $r = $r->getMethod($method);
173  if (!$r->isPublic()) {
174  throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $serviceName, $class !== $serviceName ? $class . '::' . $method : $method), 1623881313);
175  }
176 
177  return $r;
178  }
179 }
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass\$tagName
‪string $tagName
Definition: ListenerProviderPass.php:33
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass\$container
‪ContainerBuilder $container
Definition: ListenerProviderPass.php:35
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass\collectListeners
‪collectListeners(ContainerBuilder $container)
Definition: ListenerProviderPass.php:76
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass\$orderer
‪DependencyOrderingService $orderer
Definition: ListenerProviderPass.php:37
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass\getParameterType
‪string[] null getParameterType(string $serviceName, Definition $definition, string $method='__invoke')
Definition: ListenerProviderPass.php:113
‪TYPO3\CMS\Core\DependencyInjection
Definition: AutowireInjectMethodsPass.php:18
‪TYPO3\CMS\Core\Service\DependencyOrderingService
Definition: DependencyOrderingService.php:32
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass\process
‪process(ContainerBuilder $container)
Definition: ListenerProviderPass.php:48
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass
Definition: ListenerProviderPass.php:32
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass\__construct
‪__construct(string $tagName)
Definition: ListenerProviderPass.php:39
‪TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass\getReflectionMethod
‪getReflectionMethod(string $serviceName, Definition $definition, string $method)
Definition: ListenerProviderPass.php:158
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\EventDispatcher\ListenerProvider
Definition: ListenerProvider.php:30