Completed
Push — master ( 0742f0...3a9e02 )
by Pierre
03:46
created

Dispatcher::subscribeClosure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 1
rs 10
c 1
b 0
f 0
eloc 4
nc 1
nop 3
1
<?php
2
3
namespace App\Component\Pubsub;
4
5
use Closure;
6
use Exception;
7
use ReflectionParameter;
8
use App\Component\Pubsub\EventInterface;
9
10
class Dispatcher implements DispatcherInterface
11
{
12
13
    /**
14
     * listener stack.
15
     * Struct : [resName][event][hash]
16
     *
17
     * @var array
18
     */
19
    protected $stack = array();
20
21
    /**
22
     * Subscribes the listener to the resource's events.
23
     * If $resName is *,
24
     * then the listener will be dispatched when the specified event
25
     * is fired.
26
     * If $event is *,
27
     * then the listener will be dispatched
28
     * for any dispatched event of the specified resource.
29
     * If $resName and $event is *,
30
     * the listener will be dispatched
31
     * for any dispatched event for any resource.
32
     *
33
     * @param ListenerInterface $listener
34
     * @param String $resName
35
     * @param Mixed $event
36
     * @return Dispatcher
37
     */
38 1
    public function subscribe(
39
        ListenerInterface $listener,
40
        $resName = self::ALL,
41
        $event = self::ALL
42
    ): DispatcherInterface {
43 1
        $hash = $this->hash($listener);
44 1
        $this->stack[$resName][$event][$hash] = $listener;
45 1
        return $this;
46
    }
47
48
    /**
49
     * Subscribes the listener to the resource's events.
50
     * If $resName is *,
51
     * then the listener will be dispatched when the specified event
52
     * is fired.
53
     * If $event is *,
54
     * then the listener will be dispatched
55
     * for any dispatched event of the specified resource.
56
     * If $resName and $event is *,
57
     * the listener will be dispatched
58
     * for any dispatched event for any resource.
59
     *
60
     * @param Closure $closure
61
     * @param String $resName
62
     * @param Mixed $event
63
     * @return Dispatcher
64
     */
65 1
    public function subscribeClosure(
66
        Closure $closure,
67
        $resName = self::ALL,
68
        $event = self::ALL
69
    ): DispatcherInterface {
70 1
        $listener = $this->closureToListener($closure);
71 1
        $hash = $this->hash($listener);
72 1
        $this->stack[$resName][$event][$hash] = $listener;
73 1
        return $this;
74
    }
75
76
    /**
77
     * Unsubscribes the listener from the resource's events
78
     *
79
     * @param ListenerInterface $listener
80
     * @param String $resName
81
     * @param Mixed $event
82
     * @return Dispatcher
83
     */
84
    public function unsubscribe(
85
        ListenerInterface $listener,
86
        $resName = self::ALL,
87
        $event = self::ALL
88
    ): DispatcherInterface {
89
        $hash = $this->hash($listener);
90
        unset($this->stack[$resName][$event][$hash]);
91
        return $this;
92
    }
93
94
    /**
95
     * Publishes an event to all the listeners
96
     * listening to the specified event
97
     * for the specified resource
98
     *
99
     * @param EventInterface $event
100
     * @return Dispatcher
101
     */
102
    public function publish(EventInterface $event): DispatcherInterface
103
    {
104
        $resName = $event->getResourceName();
105
        $eventName = $event->getEventName();
106
        $this
107
            ->dispatchAllHandlers($event)
108
            ->dispatchResourcedHandlers($resName, $event)
109
            ->dispatchResourcedEventedHandlers($resName, $eventName, $event);
110
        return $this;
111
    }
112
113
    /**
114
     * dispatch to all handlers the wildcard handlers
115
     *
116
     * @param EventInterface $event
117
     * @return void
118
     */
119
    protected function dispatchAllHandlers(
120
        EventInterface $event
121
    ): DispatcherInterface {
122
        if (isset($this->stack[self::ALL][self::ALL])) {
123
            foreach ($this->stack[self::ALL][self::ALL] as $listener) {
124
                $listener->publish($event);
125
            }
126
        }
127
        return $this;
128
    }
129
130
    /**
131
     * dispatch to handlers identified by resource name
132
     * despite the event
133
     *
134
     * @param string $resName
135
     * @param EventInterface $event
136
     * @return void
137
     */
138
    protected function dispatchResourcedHandlers(
139
        string $resName,
140
        EventInterface $event
141
    ): DispatcherInterface {
142
        if (isset($this->stack[$resName][self::ALL])) {
143
            foreach ($this->stack[$resName][self::ALL] as $listener) {
144
                $listener->publish($event);
145
            }
146
        }
147
        return $this;
148
    }
149
150
    /**
151
     * dispatch to handlers identified by resource name and event name
152
     *
153
     * @param string $resName
154
     * @param EventInterface $event
155
     * @return void
156
     */
157
    protected function dispatchResourcedEventedHandlers(
158
        string $resName,
159
        string $eventName,
160
        EventInterface $event
161
    ): DispatcherInterface {
162
        if (isset($this->stack[$resName][$eventName])) {
163
            foreach ($this->stack[$resName][$eventName] as $listener) {
164
                $listener->publish($event);
165
            }
166
        }
167
        return $this;
168
    }
169
170
    /**
171
     * return hash for a listener instance
172
     *
173
     * @param ListenerInterface $listener
174
     * @return string
175
     */
176
    protected function hash(ListenerInterface $listener): string
177
    {
178
        return spl_object_hash($listener);
179
    }
180
181
    /**
182
     * transform closure to listener
183
     *
184
     * @param Closure $closure
185
     * @return ListenerInterface
186
     */
187
    protected function closureToListener(Closure $closure): ListenerInterface
188
    {
189
        $listener = new class ($closure) implements ListenerInterface
190
        {
191
            const ERR_CLOSURE_ARG_MISSING = 'Listener closure required at least one Event argument';
192
            const ERR_CLOSURE_ARG_INVALID ='Listener closure arg type should comply EventInterface';
193
        
194
            /**
195
             * listener as closure
196
             *
197
             * @var Closure
198
             */
199
            protected $closure;
200
201
            /**
202
             * instanciate
203
             *
204
             * @param Closure $closure
205
             */
206
            public function __construct(Closure $closure)
207
            {
208
                $params = $this->getClosureParameters($closure);
209
                $nbargs = count($params);
210
                if ($nbargs === 0) {
211
                    throw new Exception(
212
                        self::ERR_CLOSURE_ARG_MISSING
213
                    );
214
                }
215
                $argTypeName = $params[0]->getType()->getName();
216
                if ($argTypeName !== EventInterface::class) {
217
                    throw new Exception(
218
                        self::ERR_CLOSURE_ARG_INVALID
219
                    );
220
                }
221
                $this->closure = $closure;
222
            }
223
224
            /**
225
             * publish
226
             *
227
             * @param EventInterface $event
228
             * @return void
229
             */
230
            public function publish(EventInterface $event)
231
            {
232
                call_user_func($this->closure, $event);
233
            }
234
235
            /**
236
             * return an array of reflexion parameter
237
             *
238
             * @see https://www.php.net/manual/fr/class.reflectionparameter.php
239
             *
240
             * @param Closure $closure
241
             * @return ReflectionParameter[]
242
             */
243
            protected function getClosureParameters(Closure $closure): array
244
            {
245
                $reflection = new \ReflectionFunction($closure);
246
                return $reflection->getParameters();
247
            }
248
        };
249
        return $listener;
250
    }
251
}
252