1<?php
2
3namespace Sabre\Event;
4
5/**
6 * Event Emitter Trait
7 *
8 * This trait contains all the basic functions to implement an
9 * EventEmitterInterface.
10 *
11 * Using the trait + interface allows you to add EventEmitter capabilities
12 * without having to change your base-class.
13 *
14 * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
15 * @author Evert Pot (http://evertpot.com/)
16 * @license http://sabre.io/license/ Modified BSD License
17 */
18trait EventEmitterTrait {
19
20    /**
21     * The list of listeners
22     *
23     * @var array
24     */
25    protected $listeners = [];
26
27    /**
28     * Subscribe to an event.
29     *
30     * @param string $eventName
31     * @param callable $callBack
32     * @param int $priority
33     * @return void
34     */
35    function on($eventName, callable $callBack, $priority = 100) {
36
37        if (!isset($this->listeners[$eventName])) {
38            $this->listeners[$eventName] = [
39                true,  // If there's only one item, it's sorted
40                [$priority],
41                [$callBack]
42            ];
43        } else {
44            $this->listeners[$eventName][0] = false; // marked as unsorted
45            $this->listeners[$eventName][1][] = $priority;
46            $this->listeners[$eventName][2][] = $callBack;
47        }
48
49    }
50
51    /**
52     * Subscribe to an event exactly once.
53     *
54     * @param string $eventName
55     * @param callable $callBack
56     * @param int $priority
57     * @return void
58     */
59    function once($eventName, callable $callBack, $priority = 100) {
60
61        $wrapper = null;
62        $wrapper = function() use ($eventName, $callBack, &$wrapper) {
63
64            $this->removeListener($eventName, $wrapper);
65            return call_user_func_array($callBack, func_get_args());
66
67        };
68
69        $this->on($eventName, $wrapper, $priority);
70
71    }
72
73    /**
74     * Emits an event.
75     *
76     * This method will return true if 0 or more listeners were succesfully
77     * handled. false is returned if one of the events broke the event chain.
78     *
79     * If the continueCallBack is specified, this callback will be called every
80     * time before the next event handler is called.
81     *
82     * If the continueCallback returns false, event propagation stops. This
83     * allows you to use the eventEmitter as a means for listeners to implement
84     * functionality in your application, and break the event loop as soon as
85     * some condition is fulfilled.
86     *
87     * Note that returning false from an event subscriber breaks propagation
88     * and returns false, but if the continue-callback stops propagation, this
89     * is still considered a 'successful' operation and returns true.
90     *
91     * Lastly, if there are 5 event handlers for an event. The continueCallback
92     * will be called at most 4 times.
93     *
94     * @param string $eventName
95     * @param array $arguments
96     * @param callback $continueCallBack
97     * @return bool
98     */
99    function emit($eventName, array $arguments = [], callable $continueCallBack = null) {
100
101        if (is_null($continueCallBack)) {
102
103            foreach ($this->listeners($eventName) as $listener) {
104
105                $result = call_user_func_array($listener, $arguments);
106                if ($result === false) {
107                    return false;
108                }
109            }
110
111        } else {
112
113            $listeners = $this->listeners($eventName);
114            $counter = count($listeners);
115
116            foreach ($listeners as $listener) {
117
118                $counter--;
119                $result = call_user_func_array($listener, $arguments);
120                if ($result === false) {
121                    return false;
122                }
123
124                if ($counter > 0) {
125                    if (!$continueCallBack()) break;
126                }
127
128            }
129
130        }
131
132        return true;
133
134    }
135
136    /**
137     * Returns the list of listeners for an event.
138     *
139     * The list is returned as an array, and the list of events are sorted by
140     * their priority.
141     *
142     * @param string $eventName
143     * @return callable[]
144     */
145    function listeners($eventName) {
146
147        if (!isset($this->listeners[$eventName])) {
148            return [];
149        }
150
151        // The list is not sorted
152        if (!$this->listeners[$eventName][0]) {
153
154            // Sorting
155            array_multisort($this->listeners[$eventName][1], SORT_NUMERIC, $this->listeners[$eventName][2]);
156
157            // Marking the listeners as sorted
158            $this->listeners[$eventName][0] = true;
159        }
160
161        return $this->listeners[$eventName][2];
162
163    }
164
165    /**
166     * Removes a specific listener from an event.
167     *
168     * If the listener could not be found, this method will return false. If it
169     * was removed it will return true.
170     *
171     * @param string $eventName
172     * @param callable $listener
173     * @return bool
174     */
175    function removeListener($eventName, callable $listener) {
176
177        if (!isset($this->listeners[$eventName])) {
178            return false;
179        }
180        foreach ($this->listeners[$eventName][2] as $index => $check) {
181            if ($check === $listener) {
182                unset($this->listeners[$eventName][1][$index]);
183                unset($this->listeners[$eventName][2][$index]);
184                return true;
185            }
186        }
187        return false;
188
189    }
190
191    /**
192     * Removes all listeners.
193     *
194     * If the eventName argument is specified, all listeners for that event are
195     * removed. If it is not specified, every listener for every event is
196     * removed.
197     *
198     * @param string $eventName
199     * @return void
200     */
201    function removeAllListeners($eventName = null) {
202
203        if (!is_null($eventName)) {
204            unset($this->listeners[$eventName]);
205        } else {
206            $this->listeners = [];
207        }
208
209    }
210
211}
212