2a905cd603f73f44949d31bca30bc806b9fa8467
[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 /**
18 * A dispatcher which dispatches signals by calling its registered slot methods
19 * and passing them the method arguments which were originally passed to the
20 * signal method.
21 *
22 * @api
23 */
24 class Dispatcher implements \TYPO3\CMS\Core\SingletonInterface
25 {
26 /**
27 * @var bool
28 */
29 protected $isInitialized = false;
30
31 /**
32 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
33 */
34 protected $objectManager;
35
36 /**
37 * Information about all slots connected a certain signal.
38 * Indexed by [$signalClassName][$signalMethodName] and then numeric with an
39 * array of information about the slot
40 *
41 * @var array
42 */
43 protected $slots = [];
44
45 /**
46 * Initializes this object.
47 *
48 * This methods needs to be used as alternative to inject aspects.
49 * Since this dispatches is used very early when the ObjectManager
50 * is not fully initialized (especially concerning caching framework),
51 * this is the only way.
52 */
53 public function initializeObject()
54 {
55 if (!$this->isInitialized) {
56 $this->objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
57 $this->isInitialized = true;
58 }
59 }
60
61 /**
62 * Connects a signal with a slot.
63 * One slot can be connected with multiple signals by calling this method multiple times.
64 *
65 * @param string $signalClassName Name of the class containing the signal
66 * @param string $signalName Name of the signal
67 * @param mixed $slotClassNameOrObject Name of the class containing the slot or the instantiated class or a Closure object
68 * @param string $slotMethodName Name of the method to be used as a slot. If $slotClassNameOrObject is a Closure object, this parameter is ignored
69 * @param bool $passSignalInformation If set to TRUE, the last argument passed to the slot will be information about the signal (EmitterClassName::signalName)
70 * @throws \InvalidArgumentException
71 * @api
72 */
73 public function connect($signalClassName, $signalName, $slotClassNameOrObject, $slotMethodName = '', $passSignalInformation = true)
74 {
75 $class = null;
76 $object = null;
77 if (is_object($slotClassNameOrObject)) {
78 $object = $slotClassNameOrObject;
79 $method = $slotClassNameOrObject instanceof \Closure ? '__invoke' : $slotMethodName;
80 } else {
81 if ($slotMethodName === '') {
82 throw new \InvalidArgumentException('The slot method name must not be empty (except for closures).', 1229531659);
83 }
84 $class = $slotClassNameOrObject;
85 $method = $slotMethodName;
86 }
87 $slot = [
88 'class' => $class,
89 'method' => $method,
90 'object' => $object,
91 'passSignalInformation' => $passSignalInformation === true
92 ];
93 // The in_array() comparision needs to be strict to avoid potential issues
94 // with complex objects being registered as slot.
95 if (!is_array($this->slots[$signalClassName][$signalName] ?? false) || !in_array($slot, $this->slots[$signalClassName][$signalName], true)) {
96 $this->slots[$signalClassName][$signalName][] = $slot;
97 }
98 }
99
100 /**
101 * Dispatches a signal by calling the registered Slot methods
102 *
103 * @param string $signalClassName Name of the class containing the signal
104 * @param string $signalName Name of the signal
105 * @param array $signalArguments arguments passed to the signal method
106 * @return mixed
107 * @throws Exception\InvalidSlotException if the slot is not valid
108 * @throws Exception\InvalidSlotReturnException if a slot returns invalid arguments (too few or return value is not an array)
109 * @api
110 */
111 public function dispatch($signalClassName, $signalName, array $signalArguments = [])
112 {
113 $this->initializeObject();
114 if (!isset($this->slots[$signalClassName][$signalName])) {
115 return $signalArguments;
116 }
117 foreach ($this->slots[$signalClassName][$signalName] as $slotInformation) {
118 if (isset($slotInformation['object'])) {
119 $object = $slotInformation['object'];
120 } else {
121 if (!isset($this->objectManager)) {
122 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);
123 }
124 if (!$this->objectManager->isRegistered($slotInformation['class'])) {
125 throw new Exception\InvalidSlotException('The given class "' . $slotInformation['class'] . '" is not a registered object.', 1245673367);
126 }
127 $object = $this->objectManager->get($slotInformation['class']);
128 }
129
130 if (!method_exists($object, $slotInformation['method'])) {
131 throw new Exception\InvalidSlotException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() does not exist.', 1245673368);
132 }
133
134 $preparedSlotArguments = $signalArguments;
135 if ($slotInformation['passSignalInformation'] === true) {
136 $preparedSlotArguments[] = $signalClassName . '::' . $signalName;
137 }
138
139 $slotReturn = call_user_func_array([$object, $slotInformation['method']], $preparedSlotArguments);
140
141 if ($slotReturn) {
142 if (!is_array($slotReturn)) {
143 throw new Exception\InvalidSlotReturnException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '()\'s return value is of an not allowed type ('
144 . gettype($slotReturn) . ').', 1376683067);
145 }
146 if (count($slotReturn) !== count($signalArguments)) {
147 throw new Exception\InvalidSlotReturnException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() returned a different number ('
148 . count($slotReturn) . ') of arguments, than it received (' . count($signalArguments) . ').', 1376683066);
149 }
150 $signalArguments = $slotReturn;
151 }
152 }
153
154 return $signalArguments;
155 }
156
157 /**
158 * Returns all slots which are connected with the given signal
159 *
160 * @param string $signalClassName Name of the class containing the signal
161 * @param string $signalName Name of the signal
162 * @return array An array of arrays with slot information
163 * @api
164 */
165 public function getSlots($signalClassName, $signalName)
166 {
167 return isset($this->slots[$signalClassName][$signalName]) ? $this->slots[$signalClassName][$signalName] : [];
168 }
169 }