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