[FEATURE] Allow signalSlots to modify arguments
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / SignalSlot / Dispatcher.php
1 <?php
2 namespace TYPO3\CMS\Extbase\SignalSlot;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2013 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /**
31 * A dispatcher which dispatches signals by calling its registered slot methods
32 * and passing them the method arguments which were originally passed to the
33 * signal method.
34 *
35 * @api
36 */
37 class Dispatcher implements \TYPO3\CMS\Core\SingletonInterface {
38
39 /**
40 * @var boolean
41 */
42 protected $isInitialized = FALSE;
43
44 /**
45 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
46 */
47 protected $objectManager;
48
49 /**
50 * Information about all slots connected a certain signal.
51 * Indexed by [$signalClassName][$signalMethodName] and then numeric with an
52 * array of information about the slot
53 *
54 * @var array
55 */
56 protected $slots = array();
57
58 /**
59 * Initializes this object.
60 *
61 * This methods needs to be used as alternative to inject aspects.
62 * Since this dispatches is used very early when the ObjectManager
63 * is not fully initialized (especially concerning caching framework),
64 * this is the only way.
65 *
66 * @return void
67 */
68 public function initializeObject() {
69 if (!$this->isInitialized) {
70 $this->objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
71 $this->isInitialized = TRUE;
72 }
73 }
74
75 /**
76 * Connects a signal with a slot.
77 * One slot can be connected with multiple signals by calling this method multiple times.
78 *
79 * @param string $signalClassName Name of the class containing the signal
80 * @param string $signalName Name of the signal
81 * @param mixed $slotClassNameOrObject Name of the class containing the slot or the instantiated class or a Closure object
82 * @param string $slotMethodName Name of the method to be used as a slot. If $slotClassNameOrObject is a Closure object, this parameter is ignored
83 * @param boolean $passSignalInformation If set to TRUE, the last argument passed to the slot will be information about the signal (EmitterClassName::signalName)
84 * @throws \InvalidArgumentException
85 * @return void
86 * @api
87 */
88 public function connect($signalClassName, $signalName, $slotClassNameOrObject, $slotMethodName = '', $passSignalInformation = TRUE) {
89 $class = NULL;
90 $object = NULL;
91 if (is_object($slotClassNameOrObject)) {
92 $object = $slotClassNameOrObject;
93 $method = $slotClassNameOrObject instanceof \Closure ? '__invoke' : $slotMethodName;
94 } else {
95 if ($slotMethodName === '') {
96 throw new \InvalidArgumentException('The slot method name must not be empty (except for closures).', 1229531659);
97 }
98 $class = $slotClassNameOrObject;
99 $method = $slotMethodName;
100 }
101 $slot = array(
102 'class' => $class,
103 'method' => $method,
104 'object' => $object,
105 'passSignalInformation' => $passSignalInformation === TRUE
106 );
107 if (!is_array($this->slots[$signalClassName][$signalName]) || !in_array($slot, $this->slots[$signalClassName][$signalName])) {
108 $this->slots[$signalClassName][$signalName][] = $slot;
109 }
110 }
111
112 /**
113 * Dispatches a signal by calling the registered Slot methods
114 *
115 * @param string $signalClassName Name of the class containing the signal
116 * @param string $signalName Name of the signal
117 * @param array $signalArguments arguments passed to the signal method
118 * @return mixed
119 * @throws Exception\InvalidSlotException if the slot is not valid
120 * @throws Exception\InvalidSlotReturnException if a slot returns invalid arguments (too few or return value is not an array)
121 * @api
122 */
123 public function dispatch($signalClassName, $signalName, array $signalArguments = array()) {
124 $this->initializeObject();
125 if (!isset($this->slots[$signalClassName][$signalName])) {
126 return NULL;
127 }
128 foreach ($this->slots[$signalClassName][$signalName] as $slotInformation) {
129 if (isset($slotInformation['object'])) {
130 $object = $slotInformation['object'];
131 } else {
132 if (!isset($this->objectManager)) {
133 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);
134 }
135 if (!$this->objectManager->isRegistered($slotInformation['class'])) {
136 throw new Exception\InvalidSlotException('The given class "' . $slotInformation['class'] . '" is not a registered object.', 1245673367);
137 }
138 $object = $this->objectManager->get($slotInformation['class']);
139 }
140
141 if (!method_exists($object, $slotInformation['method'])) {
142 throw new Exception\InvalidSlotException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() does not exist.', 1245673368);
143 }
144
145 $preparedSlotArguments = $signalArguments;
146 if ($slotInformation['passSignalInformation'] === TRUE) {
147 $preparedSlotArguments[] = $signalClassName . '::' . $signalName;
148 }
149
150 $slotReturn = call_user_func_array(array($object, $slotInformation['method']), $preparedSlotArguments);
151
152 if ($slotReturn) {
153 if (!is_array($slotReturn)) {
154 throw new Exception\InvalidSlotReturnException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '()\'s return value is of an not allowed type ('
155 . gettype($slotReturn) . ').', 1376683067);
156 } elseif (count($slotReturn) !== count($signalArguments)) {
157 throw new Exception\InvalidSlotReturnException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() returned a different number ('
158 . count($slotReturn) . ') of arguments, than it recieved (' . count($signalArguments) . ').', 1376683066);
159 } else {
160 $signalArguments = $slotReturn;
161 }
162 }
163 }
164
165 return $signalArguments;
166 }
167
168 /**
169 * Returns all slots which are connected with the given signal
170 *
171 * @param string $signalClassName Name of the class containing the signal
172 * @param string $signalName Name of the signal
173 * @return array An array of arrays with slot information
174 * @api
175 */
176 public function getSlots($signalClassName, $signalName) {
177 return isset($this->slots[$signalClassName][$signalName]) ? $this->slots[$signalClassName][$signalName] : array();
178 }
179 }
180
181 ?>