Completed
Push — master ( 3a9e02...ed265b )
by Pierre
03:47
created

Dispatcher::dispatchResourcedHandlers()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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