1 <?php
2 /**
3 * @package FrameworkOnFramework
4 * @subpackage utils
5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
6 * @license GNU General Public License version 2 or later; see LICENSE.txt
7 */
8
9 defined('FOF_INCLUDED') or die;
10
11 /**
12 * Class to handle dispatching of events.
13 *
14 * This is the Observable part of the Observer design pattern
15 * for the event architecture.
16 *
17 * This class is based on JEventDispatcher as found in Joomla! 3.2.0
18 */
19 class FOFUtilsObservableDispatcher extends FOFUtilsObject
20 {
21 /**
22 * An array of Observer objects to notify
23 *
24 * @var array
25 */
26 protected $_observers = array();
27
28 /**
29 * The state of the observable object
30 *
31 * @var mixed
32 */
33 protected $_state = null;
34
35 /**
36 * A multi dimensional array of [function][] = key for observers
37 *
38 * @var array
39 */
40 protected $_methods = array();
41
42 /**
43 * Stores the singleton instance of the dispatcher.
44 *
45 * @var FOFUtilsObservableDispatcher
46 */
47 protected static $instance = null;
48
49 /**
50 * Returns the global Event Dispatcher object, only creating it
51 * if it doesn't already exist.
52 *
53 * @return FOFUtilsObservableDispatcher The EventDispatcher object.
54 */
55 public static function getInstance()
56 {
57 if (self::$instance === null)
58 {
59 self::$instance = new static;
60 }
61
62 return self::$instance;
63 }
64
65 /**
66 * Get the state of the FOFUtilsObservableDispatcher object
67 *
68 * @return mixed The state of the object.
69 */
70 public function getState()
71 {
72 return $this->_state;
73 }
74
75 /**
76 * Registers an event handler to the event dispatcher
77 *
78 * @param string $event Name of the event to register handler for
79 * @param string $handler Name of the event handler
80 *
81 * @return void
82 *
83 * @throws InvalidArgumentException
84 */
85 public function register($event, $handler)
86 {
87 // Are we dealing with a class or callback type handler?
88 if (is_callable($handler))
89 {
90 // Ok, function type event handler... let's attach it.
91 $method = array('event' => $event, 'handler' => $handler);
92 $this->attach($method);
93 }
94 elseif (class_exists($handler))
95 {
96 // Ok, class type event handler... let's instantiate and attach it.
97 $this->attach(new $handler($this));
98 }
99 else
100 {
101 throw new InvalidArgumentException('Invalid event handler.');
102 }
103 }
104
105 /**
106 * Triggers an event by dispatching arguments to all observers that handle
107 * the event and returning their return values.
108 *
109 * @param string $event The event to trigger.
110 * @param array $args An array of arguments.
111 *
112 * @return array An array of results from each function call.
113 */
114 public function trigger($event, $args = array())
115 {
116 $result = array();
117
118 /*
119 * If no arguments were passed, we still need to pass an empty array to
120 * the call_user_func_array function.
121 */
122 $args = (array) $args;
123
124 $event = strtolower($event);
125
126 // Check if any plugins are attached to the event.
127 if (!isset($this->_methods[$event]) || empty($this->_methods[$event]))
128 {
129 // No Plugins Associated To Event!
130 return $result;
131 }
132
133 // Loop through all plugins having a method matching our event
134 foreach ($this->_methods[$event] as $key)
135 {
136 // Check if the plugin is present.
137 if (!isset($this->_observers[$key]))
138 {
139 continue;
140 }
141
142 // Fire the event for an object based observer.
143 if (is_object($this->_observers[$key]))
144 {
145 $args['event'] = $event;
146 $value = $this->_observers[$key]->update($args);
147 }
148 // Fire the event for a function based observer.
149 elseif (is_array($this->_observers[$key]))
150 {
151 $value = call_user_func_array($this->_observers[$key]['handler'], $args);
152 }
153
154 if (isset($value))
155 {
156 $result[] = $value;
157 }
158 }
159
160 return $result;
161 }
162
163 /**
164 * Attach an observer object
165 *
166 * @param object $observer An observer object to attach
167 *
168 * @return void
169 */
170 public function attach($observer)
171 {
172 if (is_array($observer))
173 {
174 if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
175 {
176 return;
177 }
178
179 // Make sure we haven't already attached this array as an observer
180 foreach ($this->_observers as $check)
181 {
182 if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
183 {
184 return;
185 }
186 }
187
188 $this->_observers[] = $observer;
189 end($this->_observers);
190 $methods = array($observer['event']);
191 }
192 else
193 {
194 if (!($observer instanceof FOFUtilsObservableEvent))
195 {
196 return;
197 }
198
199 // Make sure we haven't already attached this object as an observer
200 $class = get_class($observer);
201
202 foreach ($this->_observers as $check)
203 {
204 if ($check instanceof $class)
205 {
206 return;
207 }
208 }
209
210 $this->_observers[] = $observer;
211
212 // Required in PHP 7 since foreach() doesn't advance the internal array counter, see http://php.net/manual/en/migration70.incompatible.php
213 end($this->_observers);
214
215 $methods = array();
216
217 foreach(get_class_methods($observer) as $obs_method)
218 {
219 // Magic methods are not allowed
220 if(strpos('__', $obs_method) === 0)
221 {
222 continue;
223 }
224
225 $methods[] = $obs_method;
226 }
227
228 //$methods = get_class_methods($observer);
229 }
230
231 $key = key($this->_observers);
232
233 foreach ($methods as $method)
234 {
235 $method = strtolower($method);
236
237 if (!isset($this->_methods[$method]))
238 {
239 $this->_methods[$method] = array();
240 }
241
242 $this->_methods[$method][] = $key;
243 }
244 }
245
246 /**
247 * Detach an observer object
248 *
249 * @param object $observer An observer object to detach.
250 *
251 * @return boolean True if the observer object was detached.
252 */
253 public function detach($observer)
254 {
255 $retval = false;
256
257 $key = array_search($observer, $this->_observers);
258
259 if ($key !== false)
260 {
261 unset($this->_observers[$key]);
262 $retval = true;
263
264 foreach ($this->_methods as &$method)
265 {
266 $k = array_search($key, $method);
267
268 if ($k !== false)
269 {
270 unset($method[$k]);
271 }
272 }
273 }
274
275 return $retval;
276 }
277 }
278