EventDispatcher::getListenerPriority()   B
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 12
cts 12
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 11
nc 4
nop 2
crap 5
1
<?php
2
/*
3
* The MIT License (MIT)
4
* Copyright © 2017 Marcos Lois Bermudez <[email protected]>
5
* *
6
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7
* documentation files (the “Software”), to deal in the Software without restriction, including without limitation
8
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
9
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
* *
11
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
12
* of the Software.
13
* *
14
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
15
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
17
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
* DEALINGS IN THE SOFTWARE.
19
*/
20
21
namespace TTIC\Laravel\Bridge\Event;
22
23
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
24
use Illuminate\Events\Dispatcher;
25
use Symfony\Component\EventDispatcher\Event;
26
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
27
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
28
use Symfony\Component\EventDispatcher\GenericEvent;
29
30
/**
31
 * A event dispatcher that extends Laravel dispatcher and implements
32
 * Symfony EventDispatcherInterdace.
33
 *
34
 * The Laravel events that reach a Synfony listener are wrapped in a
35
 * GenericEvent object.
36
 *
37
 * The Symfony events that reach a Laravel listener are delivered as
38
 * is with only one argument with the Event object.
39
 *
40
 * @package TTIC\Laravel\Bridge\Event
41
 * @author Marcos Lois Bermúdez <[email protected]>
42
 */
43
class EventDispatcher extends Dispatcher implements EventDispatcherInterface
44
{
45
    /**
46
     * {@inheritDoc}
47
     */
48 72
    public function fire($event, $payload = [], $halt = false)
49
    {
50
        // When the given "event" is actually an object we will assume it is an event
51
        // object and use the class as the event name and this event itself as the
52
        // payload to the handler, which makes object based events quite simple.
53 72
        if (is_object($event)) {
54 12
            list($payload, $event) = [[$event], get_class($event)];
55 4
        }
56
57 72
        $responses = [];
58
59
        // If an array is not given to us as the payload, we will turn it into one so
60
        // we can easily use call_user_func_array on the listeners, passing in the
61
        // payload to each of them so that they receive each of these arguments.
62 72
        if (!is_array($payload)) {
63 18
            $payload = [$payload];
64 6
        }
65
66 72
        $this->firing[] = $event;
67
68 72
        if (isset($payload[0]) && $payload[0] instanceof ShouldBroadcast) {
69 3
            $this->broadcastEvent($payload[0]);
70 1
        }
71
72 72
        foreach ($this->getListeners($event) as $listener) {
73
            // Check for a Symfony event listener
74 60
            if ($listener instanceof SymfonyWrappedListener) {
75
                // Wrap event if not Symfony one but only one time
76 21
                if (!isset($symfonyEvent)) {
77 21
                    $symfonyEvent = (isset($payload[0]) && ($payload[0] instanceof Event)) ?
78 21
                        $payload[0] : new GenericEvent('Laravel Wrapped Event', $payload);
79 7
                }
80
81 21
                $listener($symfonyEvent, $event, $this);
82
83 21
                if ($symfonyEvent->isPropagationStopped()) {
84 3
                    break;
85
                }
86
87 18
                $responses[] = null;
88 18
                continue;
89
            }
90
91
            // Laravel listener
92 45
            $response = call_user_func_array($listener, $payload);
93
94
            // If a response is returned from the listener and event halting is enabled
95
            // we will just return this response, and not call the rest of the event
96
            // listeners. Otherwise we will add the response on the response list.
97 45
            if (!is_null($response) && $halt) {
98 3
                array_pop($this->firing);
99
100 3
                return $response;
1 ignored issue
show
Bug Best Practice introduced by
The return type of return $response; (object|integer|double|string|array|boolean) is incompatible with the return type declared by the interface Illuminate\Contracts\Events\Dispatcher::fire of type array|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
101
            }
102
103
            // If a boolean false is returned from a listener, we will stop propagating
104
            // the event to any further listeners down in the chain, else we keep on
105
            // looping through the listeners and firing every one in our sequence.
106 45
            if ($response === false) {
107 3
                break;
108
            }
109
110 45
            $responses[] = $response;
111 24
        }
112
113 69
        array_pop($this->firing);
114
115 69
        return $halt ? null : $responses;
116
    }
117
118
    /**
119
     * {@inheritDoc}
120
     */
121 63
    public function addListener($eventName, $listener, $priority = 0)
122
    {
123
        // Wrap a Laravel event in a Symfony GenericEvent Object
124
//        $this->listen($eventName, function () use ($listener) {
125
//            $args = func_get_args();
126
//
127
//            // If not a Symfony Event wrap it
128
//            if (!isset($args[0]) || !($args[0] instanceof Event)) {
129
//                $event = new GenericEvent(null, $args);
130
//                $args = [$event];
131
//            }
132
//            $result = call_user_func_array($listener, $args);
133
//
134
//            // Check if Symfony listener has stopped the propagation
135
//            if (isset($event) && $event->isPropagationStopped()) {
136
//                $result = false;
137
//            }
138
//
139
//            return $result;
140
//        }, $priority);
141 63
        $this->listen($eventName, new SymfonyWrappedListener($listener), $priority);
142 63
    }
143
144
    /**
145
     * {@inheritDoc}
146
     */
147 18
    public function dispatch($eventName, Event $event = null)
148
    {
149 18
        if (null === $event) {
150 15
            $event = new Event();
151 5
        }
152
153 18
        $this->fire($eventName, $event);
0 ignored issues
show
Documentation introduced by
$event is of type object<Symfony\Component\EventDispatcher\Event>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
154
155 18
        return $event;
156
    }
157
158
    /**
159
     * {@inheritDoc}
160
     */
161 18
    public function addSubscriber(EventSubscriberInterface $subscriber)
162
    {
163 18
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
164 18
            if (is_string($params)) {
165 9
                $this->addListener($eventName, array($subscriber, $params));
166 14
            } elseif (is_string($params[0])) {
167 6
                $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
168 2
            } else {
169 6
                foreach ($params as $listener) {
170 14
                    $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
171 2
                }
172
            }
173 6
        }
174 18
    }
175
176
    /**
177
     * {@inheritDoc}
178
     */
179 9
    public function removeSubscriber(EventSubscriberInterface $subscriber)
180
    {
181 9
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
182 9
            if (is_array($params) && is_array($params[0])) {
183 3
                foreach ($params as $listener) {
184 3
                    $this->removeListener($eventName, array($subscriber, $listener[0]));
185 1
                }
186 1
            } else {
187 8
                $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
188
            }
189 3
        }
190 9
    }
191
192
    /**
193
     * {@inheritDoc}
194
     */
195 21
    public function removeListener($eventName, $listener)
196
    {
197 21
        if (!isset($this->listeners[$eventName])) {
198 3
            return;
199
        }
200
201 21
        foreach ($this->listeners[$eventName] as $priority => $listeners) {
202 21
            foreach ($listeners as $key => $val) {
203 21
                if ($listener == ($val instanceof SymfonyWrappedListener ? $val->getListener() : $val)) {
204 18
                    unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
205 18
                    if (!count($this->listeners[$eventName][$priority])) {
206 18
                        unset($this->listeners[$eventName][$priority]);
207 6
                    }
208 18
                    if (!count($this->listeners[$eventName])) {
209 18
                        unset($this->listeners[$eventName]);
210 6
                    }
211 20
                    return;
212
                }
213 1
            }
214 1
        }
215 3
    }
216
217
    /**
218
     * {@inheritDoc}
219
     */
220 3
    public function getListenerPriority($eventName, $listener)
221
    {
222 3
        if (!isset($this->listeners[$eventName])) {
223 3
            return null;
224
        }
225
226 3
        foreach ($this->listeners[$eventName] as $priority => $listeners) {
227 3
            $listeners = array_map(function ($listener) {
228 3
                return $listener instanceof SymfonyWrappedListener ?
229 3
                    $listener->getListener() : $listener;
230 3
            }, $listeners);
231 3
            if (false !== in_array($listener, $listeners, true)) {
232 3
                return $priority;
233
            }
234 1
        }
235
236 3
        return null;
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     */
242 96
    public function getListeners($eventName = null)
243
    {
244 96
        if (null !== $eventName) {
245 87
            return parent::getListeners($eventName);
246
        }
247
248 12
        foreach ($this->listeners as $eventName => $eventListeners) {
249 6
            if (!isset($this->sorted[$eventName])) {
250 5
                $this->sortListeners($eventName);
251 1
            }
252 4
        }
253
254 12
        return array_filter($this->sorted);
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 42
    public function hasListeners($eventName = null)
261
    {
262 42
        return parent::hasListeners($eventName);
263
    }
264
}
265