Completed
Push — master ( d18c5a...59de81 )
by Garrett
01:37
created

Mediator::isSubscribed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
namespace Noair;
4
5
/**
6
 * Main event pipeline.
7
 *
8
 * @author  Garrett Whitehorn
9
 * @author  David Tkachuk
10
 *
11
 * @version 1.0
12
 */
13
class Mediator implements Observable
14
{
15
    /**
16
     * @api
17
     *
18
     * @var array Holds any published events to which no handler has yet subscribed
19
     *
20
     * @since   1.0
21
     */
22
    public $held = [];
23
24
    /**
25
     * @internal
26
     *
27
     * @var bool Whether we should put published events for which there are no subscribers onto the list.
28
     *
29
     * @since   1.0
30
     */
31
    protected $holdingUnheardEvents = false;
32
33
    /**
34
     * @internal
35
     *
36
     * @var Manager
37
     *
38
     * @since 1.0
39
     */
40
    protected $mgr;
41
42
    /**
43
     *
44
     */
45
    public function __construct(Manager $mgr)
46
    {
47
        $this->mgr = $mgr;
48
    }
49
50
    /**
51
     * Registers event handler(s) to event name(s).
52
     *
53
     * @api
54
     *
55
     * @throws BadMethodCallException if validation of any handler fails
56
     *
57
     * @param array $eventHandlers Associative array of event names & handlers
58
     *
59
     * @return array The results of firing any held events
60
     *
61
     * @since   1.0
62
     *
63
     * @version 1.0
64
     */
65
    public function subscribe(array $eventHandlers)
66
    {
67
        $results = [];
68
69
        foreach ($eventHandlers as $eventName => $handler) {
70
            if (!self::isValidHandler($handler)) {
71
                throw new \BadMethodCallException('Mediator::subscribe() - invalid handler passed for ' . $eventName);
72
            }
73
74
            // extract interval (in milliseconds) from $eventName
75
            $interval = 0;
76
            if (strpos($eventName, 'timer:') === 0) {
77
                $interval = (int) substr($eventName, 6);
78
                $eventName = 'timer';
79
            }
80
81
            $this->mgr->add($eventName, $interval, $handler);
82
83
            // there will never be held timer events, but otherwise fire matching held events
84
            if ($interval === 0) {
85
                $results[] = $this->fireHeldEvents($eventName);
86
            }
87
        }
88
89
        return $results;
90
    }
91
92
    /**
93
     * Let any relevant subscribers know an event needs to be handled.
94
     *
95
     * Note: The event object can be used to share information to other similar event handlers.
96
     *
97
     * @api
98
     *
99
     * @param Event $event An event object, usually freshly created
100
     *
101
     * @return mixed Result of the event
102
     *
103
     * @since   1.0
104
     *
105
     * @version 1.0
106
     */
107
    public function publish(Event $event)
108
    {
109
        $event->mediator = $this;
110
        $result = null;
111
112
        // Make sure event is fired to any subscribers that listen to all events
113
        // all is greedy, any is not - due to order
114
        foreach (['all', $event->name, 'any'] as $eventName) {
115
            if ($this->mgr->hasSubscribers($eventName)) {
116
                $result = $this->mgr->fire($eventName, $event, $result);
117
            }
118
        }
119
120
        if ($result !== null) {
121
            return $result;
122
        }
123
124
        // If no subscribers were listening to this event, try holding it
125
        $this->tryHolding($event);
126
    }
127
128
    /**
129
     * Detach a given handler (or all) from an event name.
130
     *
131
     * @api
132
     *
133
     * @param array $eventHandlers Associative array of event names & handlers
134
     *
135
     * @return self This object
136
     *
137
     * @since   1.0
138
     *
139
     * @version 1.0
140
     */
141
    public function unsubscribe(array $eventHandlers)
142
    {
143
        foreach ($eventHandlers as $eventName => $callback) {
144
            if ($callback == '*') {
145
                // we're unsubscribing all of $eventName
146
                $this->mgr->remove($eventName);
147
                continue;
148
            }
149
150
            $callback = $this->formatCallback($eventName, $callback);
151
152
            // if this is a timer subscriber
153
            if (strpos($eventName, 'timer:') === 0) {
154
                // then we'll need to match not only the callback but also the interval
155
                $callback = [
156
                    'interval' => (int) substr($eventName, 6),
157
                    'callback' => $callback,
158
                ];
159
                $eventName = 'timer';
160
            }
161
162
            $this->mgr->searchAndDestroy($eventName, $callback);
163
        }
164
165
        return $this;
166
    }
167
168
    /**
169
     * Get or set the value of the holdingUnheardEvents property.
170
     *
171
     * @api
172
     *
173
     * @param bool|null $val true or false to set the value, omit to retrieve
174
     *
175
     * @return bool the value of the property
176
     *
177
     * @since   1.0
178
     *
179
     * @version 1.0
180
     */
181
    public function holdUnheardEvents($val = null)
182
    {
183
        if ($val === null) {
184
            return $this->holdingUnheardEvents;
185
        }
186
187
        $val = (bool) $val;
188
        if ($val === false) {
189
            $this->held = []; // make sure the held list is wiped clean
190
        }
191
192
        return ($this->holdingUnheardEvents = $val);
193
    }
194
195
    /**
196
     * Determine if the event name has any subscribers.
197
     *
198
     * @api
199
     *
200
     * @param string $eventName The desired event's name
201
     *
202
     * @return bool Whether or not the event was published
203
     *
204
     * @since   1.0
205
     *
206
     * @version 1.0
207
     */
208
    public function hasSubscribers($eventName)
209
    {
210
        return $this->mgr->hasSubscribers($eventName);
211
    }
212
213
    /**
214
     * Determine if the described event has been subscribed to or not by the callback.
215
     *
216
     * @api
217
     *
218
     * @param string   $eventName The desired event's name
219
     * @param callable $callback  The specific callback we're looking for
220
     *
221
     * @return int|false Subscriber's array index if found, false otherwise; use ===
222
     *
223
     * @since   1.0
224
     *
225
     * @version 1.0
226
     */
227
    public function isSubscribed($eventName, callable $callback)
228
    {
229
        return $this->mgr->isSubscribed($eventName, $callback);
230
    }
231
232
    /**
233
     * If any events are held for $eventName, re-publish them now.
234
     *
235
     * @internal
236
     *
237
     * @param string $eventName The event name to check for
238
     *
239
     * @since   1.0
240
     *
241
     * @version 1.0
242
     */
243
    protected function fireHeldEvents($eventName)
244
    {
245
        $results = [];
246
        // loop through any held events
247
        foreach ($this->held as $i => $e) {
248
            // if this held event's name matches our new subscriber
249
            if ($e->getName() == $eventName) {
250
                // re-publish that matching held event
251
                $results[] = $this->publish(array_splice($this->held, $i, 1)[0]);
252
            }
253
        }
254
255
        return $results;
256
    }
257
258
    /**
259
     *
260
     */
261
    protected function formatCallback($eventName, $callback)
262
    {
263
        if (is_object($callback) && $callback instanceof Observer) {
264
            // assume we're unsubscribing a parsed method name
265
            $callback = [$callback, 'on' . str_replace(':', '', ucfirst($eventName))];
266
        }
267
268
        if (is_array($callback) && !is_callable($callback)) {
269
            // we've probably been given an Observer's handler array
270
            $callback = $callback[0];
271
        }
272
273
        if (!is_callable($callback)) {
274
            // callback is invalid, so halt
275
            throw new \InvalidArgumentException('Cannot unsubscribe a non-callable');
276
        }
277
278
        return $callback;
279
    }
280
281
    /**
282
     * Puts an event on the held list if enabled and not a timer.
283
     *
284
     * @internal
285
     *
286
     * @param Event $event The event object to be held
287
     *
288
     * @since   1.0
289
     *
290
     * @version 1.0
291
     */
292
    protected function tryHolding(Event $event)
293
    {
294
        if ($this->holdingUnheardEvents && $event->name != 'timer') {
295
            array_unshift($this->held, $event);
296
        }
297
    }
298
299
    /**
300
     *
301
     */
302
    protected static function isValidHandler($handler)
303
    {
304
        return (is_callable($handler[0])
305
                && (!isset($handler[1]) || is_int($handler[1]))
306
                && (!isset($handler[2]) || is_bool($handler[2]))
307
        );
308
    }
309
}
310