Completed
Branch master (7b578f)
by Ivannis Suárez
02:27
created

EventDispatcher.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This file is part of the Cubiche package.
5
 *
6
 * Copyright (c) Cubiche
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
namespace Cubiche\Core\EventDispatcher;
12
13
use Cubiche\Core\Collections\ArrayCollection;
14
use Cubiche\Core\Collections\SortedArrayCollection;
15
use Cubiche\Core\Comparable\Comparator;
16
use Cubiche\Core\Comparable\ReverseComparator;
17
18
/**
19
 * EventDispatcher class.
20
 *
21
 * @author Ivannis Suárez Jerez <[email protected]>
22
 */
23
class EventDispatcher implements EventDispatcherInterface
24
{
25
    /**
26
     * @var ArrayCollection
27
     */
28
    protected $listeners;
29
30
    /**
31
     * EventDispatcher constructor.
32
     */
33
    public function __construct()
34
    {
35
        $this->listeners = new ArrayCollection();
36
    }
37
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function dispatch($event)
42
    {
43
        $event = $this->ensureEvent($event);
44
45
        $eventName = $event->name();
46
        if ($listeners = $this->eventListeners($eventName)) {
47
            $this->doDispatch($listeners, $event);
48
        }
49
50
        return $event;
51
    }
52
53
    /**
54
     * Ensure event input is of type EventInterface or convert it.
55
     *
56
     * @param string|EventInterface $event
57
     *
58
     * @throws InvalidArgumentException
59
     *
60
     * @return EventInterface
61
     */
62
    public function ensureEvent($event)
63
    {
64
        if (is_string($event)) {
65
            return Event::named($event);
66
        }
67
68
        if (!$event instanceof EventInterface) {
69
            throw new \InvalidArgumentException(
70
                sprintf(
71
                    'Events should be provides as %s instances or string, received type: %s',
72
                    EventInterface::class,
73
                    gettype($event)
74
                )
75
            );
76
        }
77
78
        return $event;
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function eventListeners($eventName)
85
    {
86
        if (!$this->listeners->containsKey($eventName)) {
87
            return array();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type declared by the interface Cubiche\Core\EventDispat...terface::eventListeners of type Cubiche\Core\Collections\SortedArrayCollection.

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...
88
        }
89
90
        return $this->listeners->get($eventName);
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function listeners()
97
    {
98
        return $this->listeners;
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function listenerPriority($eventName, callable $listener)
105
    {
106
        if (!$this->listeners->containsKey($eventName)) {
107
            return;
108
        }
109
110
        /** @var SortedArrayCollection $sortedListeners */
111
        $sortedListeners = $this->listeners->get($eventName);
112
113
        /** @var ArrayCollection $listeners */
114
        foreach ($sortedListeners as $priority => $listeners) {
115
            foreach ($listeners as $registered) {
116
                /** @var DelegateListener $registered */
117
                if ($registered->equals($listener)) {
118
                    return $priority;
119
                }
120
            }
121
        }
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function hasEventListeners($eventName)
128
    {
129
        return $this->listeners->containsKey($eventName);
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function hasListeners()
136
    {
137
        return $this->listeners->count() > 0;
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function addListener($eventName, callable $listener, $priority = 0)
144
    {
145
        if (!$this->listeners->containsKey($eventName)) {
146
            $this->listeners->set($eventName, new SortedArrayCollection([], new ReverseComparator(new Comparator())));
147
        }
148
149
        /** @var SortedArrayCollection $sortedListeners */
150
        $sortedListeners = $this->listeners->get($eventName);
151
        if (!$sortedListeners->containsKey($priority)) {
152
            $sortedListeners->set($priority, new ArrayCollection());
153
        }
154
155
        $sortedListeners->get($priority)->add(new DelegateListener($listener));
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function removeListener($eventName, callable $listener)
162
    {
163
        if (!$this->listeners->containsKey($eventName)) {
164
            return;
165
        }
166
167
        /** @var SortedArrayCollection $sortedListeners */
168
        $sortedListeners = $this->listeners->get($eventName);
169
170
        /** @var ArrayCollection $listeners */
171
        foreach ($sortedListeners as $priority => $listeners) {
172
            foreach ($listeners as $registered) {
173
                /** @var DelegateListener $registered */
174
                if ($registered->equals($listener)) {
175
                    $listeners->remove($registered);
176
                }
177
            }
178
179
            if ($listeners->count() == 0) {
180
                $sortedListeners->removeAt($priority);
181
            }
182
        }
183
184
        if ($sortedListeners->count() == 0) {
185
            $this->listeners->removeAt($eventName);
186
        }
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192
    public function addSubscriber(EventSubscriberInterface $subscriber)
193
    {
194
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
195
            if (is_string($params)) {
196
                $this->addListener($eventName, array($subscriber, $params));
197
            } elseif (is_string($params[0])) {
198
                $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
199
            } else {
200
                foreach ($params as $listener) {
201
                    $this->addListener(
202
                        $eventName,
203
                        array($subscriber, $listener[0]),
204
                        isset($listener[1]) ? $listener[1] : 0
205
                    );
206
                }
207
            }
208
        }
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function removeSubscriber(EventSubscriberInterface $subscriber)
215
    {
216
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
217
            if (is_array($params) && is_array($params[0])) {
218
                foreach ($params as $listener) {
219
                    $this->removeListener($eventName, array($subscriber, $listener[0]));
220
                }
221
            } else {
222
                $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
223
            }
224
        }
225
    }
226
227
    /**
228
     * Triggers the listeners of an event.
229
     *
230
     * @param SortedArrayCollection $sortedListeners
231
     * @param EventInterface        $event
232
     */
233
    protected function doDispatch(SortedArrayCollection $sortedListeners, EventInterface $event)
234
    {
235
        foreach ($sortedListeners as $priority => $listeners) {
236
            foreach ($listeners as $listener) {
237
                $listener($event);
238
                /** @var EventInterface $event */
239
                if ($event->isPropagationStopped()) {
240
                    break;
241
                }
242
            }
243
        }
244
    }
245
}
246