[TASK] Streamline phpdoc annotations in EXT:extbase
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / SignalSlot / Dispatcher.php
1 <?php
2 namespace TYPO3\CMS\Extbase\SignalSlot;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Log\LoggerInterface;
18 use TYPO3\CMS\Core\Log\LogManager;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Extbase\Object\ObjectManager;
21
22 /**
23 * A dispatcher which dispatches signals by calling its registered slot methods
24 * and passing them the method arguments which were originally passed to the
25 * signal method.
26 */
27 class Dispatcher implements \TYPO3\CMS\Core\SingletonInterface
28 {
29 /**
30 * @var bool
31 */
32 protected $isInitialized = false;
33
34 /**
35 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
36 */
37 protected $objectManager;
38
39 /**
40 * Information about all slots connected a certain signal.
41 * Indexed by [$signalClassName][$signalMethodName] and then numeric with an
42 * array of information about the slot
43 *
44 * @var array
45 */
46 protected $slots = [];
47
48 /**
49 * @var LoggerInterface
50 */
51 protected $logger;
52
53 /**
54 * Initializes this object.
55 *
56 * This methods needs to be used as alternative to inject aspects.
57 * Since this dispatches is used very early when the ObjectManager
58 * is not fully initialized (especially concerning caching framework),
59 * this is the only way.
60 */
61 public function initializeObject()
62 {
63 if (!$this->isInitialized) {
64 $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
65 $logManager = GeneralUtility::makeInstance(LogManager::class);
66 $this->logger = $logManager->getLogger(self::class);
67 $this->isInitialized = true;
68 }
69 }
70
71 /**
72 * Connects a signal with a slot.
73 * One slot can be connected with multiple signals by calling this method multiple times.
74 *
75 * @param string $signalClassName Name of the class containing the signal
76 * @param string $signalName Name of the signal
77 * @param mixed $slotClassNameOrObject Name of the class containing the slot or the instantiated class or a Closure object
78 * @param string $slotMethodName Name of the method to be used as a slot. If $slotClassNameOrObject is a Closure object, this parameter is ignored
79 * @param bool $passSignalInformation If set to TRUE, the last argument passed to the slot will be information about the signal (EmitterClassName::signalName)
80 * @throws \InvalidArgumentException
81 */
82 public function connect($signalClassName, $signalName, $slotClassNameOrObject, $slotMethodName = '', $passSignalInformation = true)
83 {
84 $class = null;
85 $object = null;
86 if (is_object($slotClassNameOrObject)) {
87 $object = $slotClassNameOrObject;
88 $method = $slotClassNameOrObject instanceof \Closure ? '__invoke' : $slotMethodName;
89 } else {
90 if ($slotMethodName === '') {
91 throw new \InvalidArgumentException('The slot method name must not be empty (except for closures).', 1229531659);
92 }
93 $class = $slotClassNameOrObject;
94 $method = $slotMethodName;
95 }
96 $slot = [
97 'class' => $class,
98 'method' => $method,
99 'object' => $object,
100 'passSignalInformation' => $passSignalInformation === true,
101 ];
102 // The in_array() comparision needs to be strict to avoid potential issues
103 // with complex objects being registered as slot.
104 if (!is_array($this->slots[$signalClassName][$signalName] ?? false) || !in_array($slot, $this->slots[$signalClassName][$signalName], true)) {
105 $this->slots[$signalClassName][$signalName][] = $slot;
106 }
107 }
108
109 /**
110 * Dispatches a signal by calling the registered Slot methods
111 *
112 * @param string $signalClassName Name of the class containing the signal
113 * @param string $signalName Name of the signal
114 * @param array $signalArguments arguments passed to the signal method
115 * @return mixed
116 * @throws Exception\InvalidSlotException if the slot is not valid
117 * @throws Exception\InvalidSlotReturnException if a slot returns invalid arguments (too few or return value is not an array)
118 */
119 public function dispatch($signalClassName, $signalName, array $signalArguments = [])
120 {
121 $this->initializeObject();
122 $this->logger->debug(
123 'Triggered signal ' . $signalClassName . ' ' . $signalName,
124 [
125 'signalClassName' => $signalClassName,
126 'signalName' => $signalName,
127 'signalArguments' => $signalArguments,
128 ]
129 );
130 if (!isset($this->slots[$signalClassName][$signalName])) {
131 return $signalArguments;
132 }
133 foreach ($this->slots[$signalClassName][$signalName] as $slotInformation) {
134 if (isset($slotInformation['object'])) {
135 $object = $slotInformation['object'];
136 } else {
137 if (!isset($this->objectManager)) {
138 throw new Exception\InvalidSlotException(sprintf('Cannot dispatch %s::%s to class %s. The object manager is not yet available in the Signal Slot Dispatcher and therefore it cannot dispatch classes.', $signalClassName, $signalName, $slotInformation['class'] ?? ''), 1298113624);
139 }
140 if (!$this->objectManager->isRegistered($slotInformation['class'])) {
141 throw new Exception\InvalidSlotException('The given class "' . $slotInformation['class'] . '" is not a registered object.', 1245673367);
142 }
143 $object = $this->objectManager->get($slotInformation['class']);
144 }
145
146 if (!method_exists($object, $slotInformation['method'])) {
147 throw new Exception\InvalidSlotException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() does not exist.', 1245673368);
148 }
149
150 $preparedSlotArguments = $signalArguments;
151 if ($slotInformation['passSignalInformation'] === true) {
152 $preparedSlotArguments[] = $signalClassName . '::' . $signalName;
153 }
154
155 $slotReturn = call_user_func_array([$object, $slotInformation['method']], $preparedSlotArguments);
156
157 if ($slotReturn) {
158 if (!is_array($slotReturn)) {
159 throw new Exception\InvalidSlotReturnException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '()\'s return value is of an not allowed type ('
160 . gettype($slotReturn) . ').', 1376683067);
161 }
162 if (count($slotReturn) !== count($signalArguments)) {
163 throw new Exception\InvalidSlotReturnException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() returned a different number ('
164 . count($slotReturn) . ') of arguments, than it received (' . count($signalArguments) . ').', 1376683066);
165 }
166 $signalArguments = $slotReturn;
167 }
168 }
169
170 return $signalArguments;
171 }
172
173 /**
174 * Returns all slots which are connected with the given signal
175 *
176 * @param string $signalClassName Name of the class containing the signal
177 * @param string $signalName Name of the signal
178 * @return array An array of arrays with slot information
179 */
180 public function getSlots($signalClassName, $signalName)
181 {
182 return $this->slots[$signalClassName][$signalName] ?? [];
183 }
184 }