Dispatcher::makeListener()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Magister\Services\Events;
4
5
use Magister\Services\Container\Container;
6
use Magister\Services\Contracts\Events\Dispatcher as DispatcherContract;
7
8
class Dispatcher implements DispatcherContract
9
{
10
    /**
11
     * The IoC container instance.
12
     *
13
     * @var \Magister\Services\Container\Container
14
     */
15
    protected $container;
16
17
    /**
18
     * The registered event listeners.
19
     *
20
     * @var array
21
     */
22
    protected $listeners = [];
23
24
    /**
25
     * The sorted event listeners.
26
     *
27
     * @var array
28
     */
29
    protected $sorted = [];
30
31
    /**
32
     * Create a new event dispatcher instance.
33
     *
34
     * @param \Magister\Services\Container\Container $container
35
     */
36
    public function __construct(Container $container = null)
37
    {
38
        $this->container = $container ?: new Container();
39
    }
40
41
    /**
42
     * Register an event listener with the dispatcher.
43
     *
44
     * @param string $event
45
     * @param mixed  $listener
46
     * @param int    $priority
47
     *
48
     * @return void
49
     */
50
    public function listen($event, $listener, $priority = 0)
51
    {
52
        $this->listeners[$event][$priority][] = $this->makeListener($listener);
53
54
        unset($this->sorted[$event]);
55
    }
56
57
    /**
58
     * Determine if a given event has listeners.
59
     *
60
     * @param string $eventName
61
     *
62
     * @return bool
63
     */
64
    public function hasListeners($eventName)
65
    {
66
        return isset($this->listeners[$eventName]);
67
    }
68
69
    /**
70
     * Fire an event and call the listeners.
71
     *
72
     * @param string $event
73
     * @param mixed  $payload
74
     * @param bool   $halt
75
     *
76
     * @return array|null
77
     */
78
    public function fire($event, $payload = [], $halt = false)
79
    {
80
        $responses = [];
81
82
        if (!is_array($payload)) {
83
            $payload = [$payload];
84
        }
85
86
        $payload[] = $event;
87
88
        foreach ($this->getListeners($event) as $listener) {
89
            $response = call_user_func_array($listener, $payload);
90
91
            if (!is_null($response) && $halt) {
92
                return $response;
0 ignored issues
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 Magister\Services\Contra...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...
93
            }
94
95
            if ($response === false) {
96
                break;
97
            }
98
99
            $responses[] = $response;
100
        }
101
102
        return $halt ? null : $responses;
103
    }
104
105
    /**
106
     * Get all of the listeners for a given event name.
107
     *
108
     * @param string $eventName
109
     *
110
     * @return array
111
     */
112
    public function getListeners($eventName)
113
    {
114
        if (!isset($this->sorted[$eventName])) {
115
            $this->sortListeners($eventName);
116
        }
117
118
        return $this->sorted[$eventName];
119
    }
120
121
    /**
122
     * Sort the listeners for a given event by priority.
123
     *
124
     * @param string $eventName
125
     *
126
     * @return array
127
     */
128
    protected function sortListeners($eventName)
129
    {
130
        $this->sorted[$eventName] = [];
131
132
        if (isset($this->listeners[$eventName])) {
133
            krsort($this->listeners[$eventName]);
134
135
            $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]);
136
        }
137
    }
138
139
    /**
140
     * Register an event listener with the dispatcher.
141
     *
142
     * @param mixed $listener
143
     *
144
     * @return mixed
145
     */
146
    public function makeListener($listener)
147
    {
148
        if (is_string($listener)) {
149
            $listener = $this->createClassListener($listener);
150
        }
151
152
        return $listener;
153
    }
154
155
    /**
156
     * Create a class based listener using the IoC container.
157
     *
158
     * @param mixed $listener
159
     *
160
     * @return \Closure
161
     */
162
    public function createClassListener($listener)
163
    {
164
        $container = $this->container;
165
166
        return function () use ($listener, $container) {
167
            $segments = explode('@', $listener);
168
169
            $method = count($segments) == 2 ? $segments[1] : 'handle';
170
171
            $callable = [new $segments[0](), $method];
172
173
            $data = func_get_args();
174
175
            return call_user_func_array($callable, $data);
176
        };
177
    }
178
179
    /**
180
     * Remove a set of listeners from the dispatcher.
181
     *
182
     * @param string $event
183
     *
184
     * @return void
185
     */
186
    public function forget($event)
187
    {
188
        unset($this->listeners[$event]);
189
190
        unset($this->sorted[$event]);
191
    }
192
}
193