Completed
Push — master ( 7b578f...9f8fa7 )
by Ivannis Suárez
03:00
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\ArrayHashMap;
14
use Cubiche\Core\Collections\ArrayCollection\ArrayList;
15
use Cubiche\Core\Collections\ArrayCollection\SortedArrayHashMap;
16
use Cubiche\Core\Comparable\Comparator;
17
use Cubiche\Core\Comparable\ReverseComparator;
18
19
/**
20
 * EventDispatcher class.
21
 *
22
 * @author Ivannis Suárez Jerez <[email protected]>
23
 */
24
class EventDispatcher implements EventDispatcherInterface
25
{
26
    /**
27
     * @var ArrayHashMap
28
     */
29
    protected $listeners;
30
31
    /**
32
     * EventDispatcher constructor.
33
     */
34
    public function __construct()
35
    {
36
        $this->listeners = new ArrayHashMap();
37
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42
    public function dispatch($event)
43
    {
44
        $event = $this->ensureEvent($event);
45
46
        $eventName = $event->name();
47
        if ($listeners = $this->eventListeners($eventName)) {
48
            $this->doDispatch($listeners, $event);
49
        }
50
51
        return $event;
52
    }
53
54
    /**
55
     * Ensure event input is of type EventInterface or convert it.
56
     *
57
     * @param string|EventInterface $event
58
     *
59
     * @throws InvalidArgumentException
60
     *
61
     * @return EventInterface
62
     */
63
    public function ensureEvent($event)
64
    {
65
        if (is_string($event)) {
66
            return Event::named($event);
67
        }
68
69
        if (!$event instanceof EventInterface) {
70
            throw new \InvalidArgumentException(
71
                sprintf(
72
                    'Events should be provides as %s instances or string, received type: %s',
73
                    EventInterface::class,
74
                    gettype($event)
75
                )
76
            );
77
        }
78
79
        return $event;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function eventListeners($eventName)
86
    {
87
        if (!$this->listeners->containsKey($eventName)) {
88
            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...tion\SortedArrayHashMap.

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