Completed
Push — master ( f53a3b...615a7f )
by Garrett
01:25
created

Mediator::formatCallback()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 8
nc 8
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
     * If any events are held for $eventName, re-publish them now.
197
     *
198
     * @internal
199
     *
200
     * @param string $eventName The event name to check for
201
     *
202
     * @since   1.0
203
     *
204
     * @version 1.0
205
     */
206
    protected function fireHeldEvents($eventName)
207
    {
208
        $results = [];
209
        // loop through any held events
210
        foreach ($this->held as $i => $e) {
211
            // if this held event's name matches our new subscriber
212
            if ($e->getName() == $eventName) {
213
                // re-publish that matching held event
214
                $results[] = $this->publish(array_splice($this->held, $i, 1)[0]);
215
            }
216
        }
217
218
        return $results;
219
    }
220
221
    /**
222
     *
223
     */
224
    protected function formatCallback($eventName, $callback)
225
    {
226
        if (is_object($callback) && $callback instanceof Observer) {
227
            // assume we're unsubscribing a parsed method name
228
            $callback = [$callback, 'on' . str_replace(':', '', ucfirst($eventName))];
229
        }
230
231
        if (is_array($callback) && !is_callable($callback)) {
232
            // we've probably been given an Observer's handler array
233
            $callback = $callback[0];
234
        }
235
236
        if (!is_callable($callback)) {
237
            // callback is invalid, so halt
238
            throw new \InvalidArgumentException('Cannot unsubscribe a non-callable');
239
        }
240
241
        return $callback;
242
    }
243
244
    /**
245
     * Puts an event on the held list if enabled and not a timer.
246
     *
247
     * @internal
248
     *
249
     * @param Event $event The event object to be held
250
     *
251
     * @since   1.0
252
     *
253
     * @version 1.0
254
     */
255
    protected function tryHolding(Event $event)
256
    {
257
        if ($this->holdingUnheardEvents && $event->name != 'timer') {
258
            array_unshift($this->held, $event);
259
        }
260
    }
261
262
    /**
263
     *
264
     */
265
    protected static function isValidHandler($handler)
266
    {
267
        return (is_callable($handler[0])
268
                && (!isset($handler[1]) || is_int($handler[1]))
269
                && (!isset($handler[2]) || is_bool($handler[2]))
270
        );
271
    }
272
}
273