Completed
Pull Request — 2.1 (#15718)
by Alex
17:00
created

Event::hasHandlers()   C

Complexity

Conditions 12
Paths 19

Size

Total Lines 44
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 12.0948

Importance

Changes 0
Metric Value
dl 0
loc 44
ccs 21
cts 23
cp 0.913
rs 5.1612
c 0
b 0
f 0
cc 12
eloc 24
nc 19
nop 2
crap 12.0948

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\base;
9
10
use yii\helpers\StringHelper;
11
12
/**
13
 * Event is the base class for all event classes.
14
 *
15
 * It encapsulates the parameters associated with an event.
16
 * The [[sender]] property describes who raises the event.
17
 * And the [[handled]] property indicates if the event is handled.
18
 * If an event handler sets [[handled]] to be `true`, the rest of the
19
 * uninvoked handlers will no longer be called to handle the event.
20
 *
21
 * Additionally, when attaching an event handler, extra data may be passed
22
 * and be available via the [[data]] property when the event handler is invoked.
23
 *
24
 * For more details and usage information on Event, see the [guide article on events](guide:concept-events).
25
 *
26
 * @author Qiang Xue <[email protected]>
27
 * @since 2.0
28
 */
29
class Event extends BaseObject
30
{
31
    /**
32
     * @var string the event name. This property is set by [[Component::trigger()]] and [[trigger()]].
33
     * Event handlers may use this property to check what event it is handling.
34
     */
35
    public $name;
36
    /**
37
     * @var object the sender of this event. If not set, this property will be
38
     * set as the object whose `trigger()` method is called.
39
     * This property may also be a `null` when this event is a
40
     * class-level event which is triggered in a static context.
41
     */
42
    public $sender;
43
    /**
44
     * @var bool whether the event is handled. Defaults to `false`.
45
     * When a handler sets this to be `true`, the event processing will stop and
46
     * ignore the rest of the uninvoked event handlers.
47
     */
48
    public $handled = false;
49
    /**
50
     * @var mixed the data that is passed to [[Component::on()]] when attaching an event handler.
51
     * Note that this varies according to which event handler is currently executing.
52
     */
53
    public $data;
54
55
    /**
56
     * @var array contains all globally registered event handlers.
57
     */
58
    private static $_events = [];
59
    /**
60
     * @var array the globally registered event handlers attached for wildcard patterns (event name wildcard => handlers)
61
     * @since 2.0.14
62
     */
63
    private static $_eventWildcards = [];
64
65
66
    /**
67
     * Attaches an event handler to a class-level event.
68
     *
69
     * When a class-level event is triggered, event handlers attached
70
     * to that class and all parent classes will be invoked.
71
     *
72
     * For example, the following code attaches an event handler to `ActiveRecord`'s
73
     * `afterInsert` event:
74
     *
75
     * ```php
76
     * Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
77
     *     Yii::debug(get_class($event->sender) . ' is inserted.');
78
     * });
79
     * ```
80
     *
81
     * The handler will be invoked for EVERY successful ActiveRecord insertion.
82
     *
83
     * Since 2.0.14 you can specify either class name or event name as a wildcard pattern:
84
     *
85
     * ```php
86
     * Event::on('app\models\db\*', '*Insert', function ($event) {
87
     *     Yii::debug(get_class($event->sender) . ' is inserted.');
88
     * });
89
     * ```
90
     *
91
     * For more details about how to declare an event handler, please refer to [[Component::on()]].
92
     *
93
     * @param string $class the fully qualified class name to which the event handler needs to attach.
94
     * @param string $name the event name.
95
     * @param callable $handler the event handler.
96
     * @param mixed $data the data to be passed to the event handler when the event is triggered.
97
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
98
     * @param bool $append whether to append new event handler to the end of the existing
99
     * handler list. If `false`, the new handler will be inserted at the beginning of the existing
100
     * handler list.
101
     * @see off()
102
     */
103 16
    public static function on($class, $name, $handler, $data = null, $append = true)
104
    {
105 16
        $class = ltrim($class, '\\');
106
107 16
        if (strpos($class, '*') !== false || strpos($name, '*') !== false) {
108 2
            if ($append || empty(self::$_eventWildcards[$name][$class])) {
109 2
                self::$_eventWildcards[$name][$class][] = [$handler, $data];
110
            } else {
111
                array_unshift(self::$_eventWildcards[$name][$class], [$handler, $data]);
112
            }
113 2
            return;
114
        }
115
116 14
        if ($append || empty(self::$_events[$name][$class])) {
117 14
            self::$_events[$name][$class][] = [$handler, $data];
118
        } else {
119
            array_unshift(self::$_events[$name][$class], [$handler, $data]);
120
        }
121 14
    }
122
123
    /**
124
     * Detaches an event handler from a class-level event.
125
     *
126
     * This method is the opposite of [[on()]].
127
     *
128
     * Note: in case wildcard pattern is passed for class name or event name, only the handlers registered with this
129
     * wildcard will be removed, while handlers registered with plain names matching this wildcard will remain.
130
     *
131
     * @param string $class the fully qualified class name from which the event handler needs to be detached.
132
     * @param string $name the event name.
133
     * @param callable $handler the event handler to be removed.
134
     * If it is `null`, all handlers attached to the named event will be removed.
135
     * @return bool whether a handler is found and detached.
136
     * @see on()
137
     */
138 13
    public static function off($class, $name, $handler = null)
139
    {
140 13
        $class = ltrim($class, '\\');
141 13
        if (empty(self::$_events[$name][$class]) && empty(self::$_eventWildcards[$name][$class])) {
142
            return false;
143
        }
144 13
        if ($handler === null) {
145 8
            unset(self::$_events[$name][$class]);
146 8
            unset(self::$_eventWildcards[$name][$class]);
147 8
            return true;
148
        }
149
150
        // plain event names
151 5
        if (isset(self::$_events[$name][$class])) {
152 4
            $removed = false;
153 4
            foreach (self::$_events[$name][$class] as $i => $event) {
154 4
                if ($event[0] === $handler) {
155 4
                    unset(self::$_events[$name][$class][$i]);
156 4
                    $removed = true;
157
                }
158
            }
159 4
            if ($removed) {
160 4
                self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
161 4
                return $removed;
162
            }
163
        }
164
165
        // wildcard event names
166 1
        $removed = false;
167 1
        foreach (self::$_eventWildcards[$name][$class] as $i => $event) {
168 1
            if ($event[0] === $handler) {
169 1
                unset(self::$_eventWildcards[$name][$class][$i]);
170 1
                $removed = true;
171
            }
172
        }
173 1
        if ($removed) {
174 1
            self::$_eventWildcards[$name][$class] = array_values(self::$_eventWildcards[$name][$class]);
175
            // remove empty wildcards to save future redundant regex checks :
176 1
            if (empty(self::$_eventWildcards[$name][$class])) {
177 1
                unset(self::$_eventWildcards[$name][$class]);
178 1
                if (empty(self::$_eventWildcards[$name])) {
179 1
                    unset(self::$_eventWildcards[$name]);
180
                }
181
            }
182
        }
183
184 1
        return $removed;
185
    }
186
187
    /**
188
     * Detaches all registered class-level event handlers.
189
     * @see on()
190
     * @see off()
191
     * @since 2.0.10
192
     */
193 5
    public static function offAll()
194
    {
195 5
        self::$_events = [];
196 5
        self::$_eventWildcards = [];
197 5
    }
198
199
    /**
200
     * Returns a value indicating whether there is any handler attached to the specified class-level event.
201
     * Note that this method will also check all parent classes to see if there is any handler attached
202
     * to the named event.
203
     * @param string|object $class the object or the fully qualified class name specifying the class-level event.
204
     * @param string $name the event name.
205
     * @return bool whether there is any handler attached to the event.
206
     */
207 102
    public static function hasHandlers($class, $name)
208
    {
209 102
        if (empty(self::$_eventWildcards) && empty(self::$_events[$name])) {
210 101
            return false;
211
        }
212
213 6
        if (is_object($class)) {
214 2
            $class = get_class($class);
215
        } else {
216 4
            $class = ltrim($class, '\\');
217
        }
218
219 6
        $classes = array_merge(
220 6
            [$class],
221 6
            class_parents($class, true),
222 6
            class_implements($class, true)
223
        );
224
225
        // regular events
226 6
        foreach ($classes as $class) {
227 6
            if (!empty(self::$_events[$name][$class])) {
228 6
                return true;
229
            }
230
        }
231
232
        // wildcard events
233 4
        foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
234 2
            if (!StringHelper::matchWildcard($nameWildcard, $name)) {
235
                continue;
236
            }
237 2
            foreach ($classHandlers as $classWildcard => $handlers) {
238 2
                if (empty($handlers)) {
239
                    continue;
240
                }
241 2
                foreach ($classes as $class) {
242 2
                    if (!StringHelper::matchWildcard($classWildcard, $class)) {
243 2
                        return true;
244
                    }
245
                }
246
            }
247
        }
248
249 2
        return false;
250
    }
251
252
    /**
253
     * Triggers a class-level event.
254
     * This method will cause invocation of event handlers that are attached to the named event
255
     * for the specified class and all its parent classes.
256
     * @param string|object $class the object or the fully qualified class name specifying the class-level event.
257
     * @param string $name the event name.
258
     * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
259
     */
260 1835
    public static function trigger($class, $name, $event = null)
261
    {
262 1835
        $wildcardEventHandlers = [];
263 1835
        foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
264 1
            if (!StringHelper::matchWildcard($nameWildcard, $name)) {
265
                continue;
266
            }
267 1
            $wildcardEventHandlers = array_merge($wildcardEventHandlers, $classHandlers);
268
        }
269
270 1835
        if (empty(self::$_events[$name]) && empty($wildcardEventHandlers)) {
271 1786
            return;
272
        }
273
274 590
        if ($event === null) {
275 580
            $event = new static();
276
        }
277 590
        $event->handled = false;
278 590
        $event->name = $name;
279
280 590
        if (is_object($class)) {
281 590
            if ($event->sender === null) {
282 582
                $event->sender = $class;
283
            }
284 590
            $class = get_class($class);
285
        } else {
286
            $class = ltrim($class, '\\');
287
        }
288
289 590
        $classes = array_merge(
290 590
            [$class],
291 590
            class_parents($class, true),
292 590
            class_implements($class, true)
293
        );
294
295 590
        foreach ($classes as $class) {
296 590
            $eventHandlers = [];
297 590
            foreach ($wildcardEventHandlers as $classWildcard => $handlers) {
298 1
                if (StringHelper::matchWildcard($classWildcard, $class)) {
299 1
                    $eventHandlers = array_merge($eventHandlers, $handlers);
300
                }
301
            }
302
303 590
            if (!empty(self::$_events[$name][$class])) {
304 12
                $eventHandlers = array_merge($eventHandlers, self::$_events[$name][$class]);
305
            }
306
307 590
            foreach ($eventHandlers as $handler) {
308 13
                $event->data = $handler[1];
309 13
                call_user_func($handler[0], $event);
310 13
                if ($event->handled) {
311 590
                    return;
312
                }
313
            }
314
        }
315 590
    }
316
}
317