Completed
Push — 3.0 ( e28bf1...6f19d3 )
by Alexander
113:15 queued 105:35
created

Event   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 401
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 90.23%

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 2
dl 0
loc 401
ccs 120
cts 133
cp 0.9023
rs 3.6
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 7 2
A setName() 0 4 1
A defaultName() 0 4 1
A getTarget() 0 4 1
A setTarget() 0 4 1
A stopPropagation() 0 4 1
A isPropagationStopped() 0 4 1
A getParams() 0 4 1
A setParams() 0 4 1
A getParam() 0 7 2
B on() 0 19 7
C off() 0 48 13
A offAll() 0 5 1
C hasHandlers() 0 44 12
F trigger() 0 63 15

How to fix   Complexity   

Complex Class

Complex classes like Event often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Event, and based on these observations, apply Extract Interface, too.

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
 * @property string $name the event name.
27
 * @property object|string|null $target the target/context from which event was triggered.
28
 * @property array $params
29
 *
30
 * @author Qiang Xue <[email protected]>
31
 * @since 2.0
32
 */
33
class Event extends BaseObject
34
{
35
    /**
36
     * @var string the event name. Event handlers may use this property to check what event it is handling.
37
     */
38
    private $_name;
39
    /**
40
     * @var object|null the target/context from which event was triggered.
41
     */
42
    private $_target;
43
    /**
44
     * @var bool whether the propagation of this event is stopped.
45
     */
46
    private $_isPropagationStopped = false;
47
    /**
48
     * @var array the parameters that are passed to [[Component::on()]] when attaching an event handler.
49
     * Note that this varies according to which event handler is currently executing.
50
     */
51
    private $_params = [];
52
53
    /**
54
     * @var array contains all globally registered event handlers.
55
     */
56
    private static $_events = [];
57
    /**
58
     * @var array the globally registered event handlers attached for wildcard patterns (event name wildcard => handlers)
59
     * @since 2.0.14
60
     */
61
    private static $_eventWildcards = [];
62
63
64
    /**
65
     * Returns event name.
66
     * @return string event name.
67
     * @since 3.0.0
68
     */
69 482
    public function getName()
70
    {
71 482
        if ($this->_name === null) {
72 1
            $this->_name = $this->defaultName();
73
        }
74 482
        return $this->_name;
75
    }
76
77
    /**
78
     * Sets the event name.
79
     * @param string $name event name.
80
     * @since 3.0.0
81
     */
82 940
    public function setName($name)
83
    {
84 940
        $this->_name = $name;
85 940
    }
86
87
    /**
88
     * Generates default event name to be used in case it is not explicitly set.
89
     * By default this method generates event name from its class name, converting it to 'dot.separated.string' format.
90
     * Child classes may override this method providing their own implementation.
91
     * @return string default event name.
92
     * @since 3.0.0
93
     */
94 1
    protected function defaultName()
95
    {
96 1
        return str_replace('\\', '.', strtolower(get_class($this)));
97
    }
98
99
    /**
100
     * Returns target/context from which event was triggered.
101
     * Target usually is set as the object whose `trigger()` method is called.
102
     * This property may be a `null` when this event is a class-level event,
103
     * which is triggered in a static context.
104
     * @return object|null target/context from which event was triggered.
105
     * @since 3.0.0
106
     */
107 719
    public function getTarget()
108
    {
109 719
        return $this->_target;
110
    }
111
112
    /**
113
     * Sets target/context from which event was triggered.
114
     * @param object|null $target target/context from which event was triggered.
115
     * @since 3.0.0
116
     */
117 719
    public function setTarget($target)
118
    {
119 719
        $this->_target = $target;
120 719
    }
121
122
    /**
123
     * Indicate whether or not to stop propagating this event.
124
     * When a handler sets this to be `true`, the event processing will stop and
125
     * ignore the rest of the event handlers, which have not been invoked yet.
126
     * @param bool $flag whether or not to stop propagating this event. Default is `true`.
127
     * @since 3.0.0
128
     */
129 719
    public function stopPropagation($flag = true)
130
    {
131 719
        $this->_isPropagationStopped = $flag;
132 719
    }
133
134
    /**
135
     * Indicates whether or not the propagation of this event has been stopped.
136
     * @return bool whether or not the propagation of this event has been stopped.
137
     * @since 3.0.0
138
     */
139 117
    public function isPropagationStopped()
140
    {
141 117
        return $this->_isPropagationStopped;
142
    }
143
144
    /**
145
     * @return array
146
     * @since 3.0.0
147
     */
148
    public function getParams()
149
    {
150
        return $this->_params;
151
    }
152
153
    /**
154
     * @param array $params
155
     * @since 3.0.0
156
     */
157 118
    public function setParams(array $params)
158
    {
159 118
        $this->_params = $params;
160 118
    }
161
162
    /**
163
     * Get a single parameter by name
164
     * @param string $name parameter name.
165
     * @param mixed|null $default default value.
166
     * @return mixed parameter value.
167
     * @since 3.0.0
168
     */
169
    public function getParam($name, $default = null)
170
    {
171
        if (array_key_exists($name, $this->_params)) {
172
            return $this->_params[$name];
173
        }
174
        return $default;
175
    }
176
177
    /**
178
     * Attaches an event handler to a class-level event.
179
     *
180
     * When a class-level event is triggered, event handlers attached
181
     * to that class and all parent classes will be invoked.
182
     *
183
     * For example, the following code attaches an event handler to `ActiveRecord`'s
184
     * `afterInsert` event:
185
     *
186
     * ```php
187
     * Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
188
     *     Yii::debug(get_class($event->sender) . ' is inserted.');
189
     * });
190
     * ```
191
     *
192
     * The handler will be invoked for EVERY successful ActiveRecord insertion.
193
     *
194
     * Since 2.0.14 you can specify either class name or event name as a wildcard pattern:
195
     *
196
     * ```php
197
     * Event::on('app\models\db\*', '*Insert', function ($event) {
198
     *     Yii::debug(get_class($event->sender) . ' is inserted.');
199
     * });
200
     * ```
201
     *
202
     * For more details about how to declare an event handler, please refer to [[Component::on()]].
203
     *
204
     * @param string $class the fully qualified class name to which the event handler needs to attach.
205
     * @param string $name the event name.
206
     * @param callable $handler the event handler.
207
     * @param array $params the parameters to be passed to the event handler when the event is triggered.
208
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
209
     * @param bool $append whether to append new event handler to the end of the existing
210
     * handler list. If `false`, the new handler will be inserted at the beginning of the existing
211
     * handler list.
212
     * @see off()
213
     */
214 16
    public static function on($class, $name, $handler, array $params = [], $append = true)
215
    {
216 16
        $class = ltrim($class, '\\');
217
218 16
        if (strpos($class, '*') !== false || strpos($name, '*') !== false) {
219 2
            if ($append || empty(self::$_eventWildcards[$name][$class])) {
220 2
                self::$_eventWildcards[$name][$class][] = [$handler, $params];
221
            } else {
222
                array_unshift(self::$_eventWildcards[$name][$class], [$handler, $params]);
223
            }
224 2
            return;
225
        }
226
227 14
        if ($append || empty(self::$_events[$name][$class])) {
228 14
            self::$_events[$name][$class][] = [$handler, $params];
229
        } else {
230
            array_unshift(self::$_events[$name][$class], [$handler, $params]);
231
        }
232 14
    }
233
234
    /**
235
     * Detaches an event handler from a class-level event.
236
     *
237
     * This method is the opposite of [[on()]].
238
     *
239
     * Note: in case wildcard pattern is passed for class name or event name, only the handlers registered with this
240
     * wildcard will be removed, while handlers registered with plain names matching this wildcard will remain.
241
     *
242
     * @param string $class the fully qualified class name from which the event handler needs to be detached.
243
     * @param string $name the event name.
244
     * @param callable $handler the event handler to be removed.
245
     * If it is `null`, all handlers attached to the named event will be removed.
246
     * @return bool whether a handler is found and detached.
247
     * @see on()
248
     */
249 13
    public static function off($class, $name, $handler = null)
250
    {
251 13
        $class = ltrim($class, '\\');
252 13
        if (empty(self::$_events[$name][$class]) && empty(self::$_eventWildcards[$name][$class])) {
253
            return false;
254
        }
255 13
        if ($handler === null) {
256 8
            unset(self::$_events[$name][$class]);
257 8
            unset(self::$_eventWildcards[$name][$class]);
258 8
            return true;
259
        }
260
261
        // plain event names
262 5
        if (isset(self::$_events[$name][$class])) {
263 4
            $removed = false;
264 4
            foreach (self::$_events[$name][$class] as $i => $event) {
265 4
                if ($event[0] === $handler) {
266 4
                    unset(self::$_events[$name][$class][$i]);
267 4
                    $removed = true;
268
                }
269
            }
270 4
            if ($removed) {
271 4
                self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
272 4
                return $removed;
273
            }
274
        }
275
276
        // wildcard event names
277 1
        $removed = false;
278 1
        foreach (self::$_eventWildcards[$name][$class] as $i => $event) {
279 1
            if ($event[0] === $handler) {
280 1
                unset(self::$_eventWildcards[$name][$class][$i]);
281 1
                $removed = true;
282
            }
283
        }
284 1
        if ($removed) {
285 1
            self::$_eventWildcards[$name][$class] = array_values(self::$_eventWildcards[$name][$class]);
286
            // remove empty wildcards to save future redundant regex checks :
287 1
            if (empty(self::$_eventWildcards[$name][$class])) {
288 1
                unset(self::$_eventWildcards[$name][$class]);
289 1
                if (empty(self::$_eventWildcards[$name])) {
290 1
                    unset(self::$_eventWildcards[$name]);
291
                }
292
            }
293
        }
294
295 1
        return $removed;
296
    }
297
298
    /**
299
     * Detaches all registered class-level event handlers.
300
     * @see on()
301
     * @see off()
302
     * @since 2.0.10
303
     */
304 6
    public static function offAll()
305
    {
306 6
        self::$_events = [];
307 6
        self::$_eventWildcards = [];
308 6
    }
309
310
    /**
311
     * Returns a value indicating whether there is any handler attached to the specified class-level event.
312
     * Note that this method will also check all parent classes to see if there is any handler attached
313
     * to the named event.
314
     * @param string|object $class the object or the fully qualified class name specifying the class-level event.
315
     * @param string $name the event name.
316
     * @return bool whether there is any handler attached to the event.
317
     */
318 103
    public static function hasHandlers($class, $name)
319
    {
320 103
        if (empty(self::$_eventWildcards) && empty(self::$_events[$name])) {
321 102
            return false;
322
        }
323
324 6
        if (is_object($class)) {
325 2
            $class = get_class($class);
326
        } else {
327 4
            $class = ltrim($class, '\\');
328
        }
329
330 6
        $classes = array_merge(
331 6
            [$class],
332 6
            class_parents($class, true),
333 6
            class_implements($class, true)
334
        );
335
336
        // regular events
337 6
        foreach ($classes as $class) {
338 6
            if (!empty(self::$_events[$name][$class])) {
339 6
                return true;
340
            }
341
        }
342
343
        // wildcard events
344 4
        foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
345 2
            if (!StringHelper::matchWildcard($nameWildcard, $name)) {
346
                continue;
347
            }
348 2
            foreach ($classHandlers as $classWildcard => $handlers) {
349 2
                if (empty($handlers)) {
350
                    continue;
351
                }
352 2
                foreach ($classes as $class) {
353 2
                    if (!StringHelper::matchWildcard($classWildcard, $class)) {
354 2
                        return true;
355
                    }
356
                }
357
            }
358
        }
359
360 2
        return false;
361
    }
362
363
    /**
364
     * Triggers a class-level event.
365
     * This method will cause invocation of event handlers that are attached to the named event
366
     * for the specified class and all its parent classes.
367
     * @param string|object $class the object or the fully qualified class name specifying the class-level event.
368
     * @param Event|string $event the event instance or name. If string name passed, a default [[Event]] object will be created.
369
     */
370 1914
    public static function trigger($class, $event)
371
    {
372 1914
        if (is_object($event)) {
373 480
            $name = $event->getName();
374
        } else {
375 1804
            $name = $event;
376
        }
377
378 1914
        $wildcardEventHandlers = [];
379 1914
        foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
380 1
            if (!StringHelper::matchWildcard($nameWildcard, $name)) {
381
                continue;
382
            }
383 1
            $wildcardEventHandlers = array_merge($wildcardEventHandlers, $classHandlers);
384
        }
385
386 1914
        if (empty(self::$_events[$name]) && empty($wildcardEventHandlers)) {
387 1866
            return;
388
        }
389
390 623
        if (!is_object($event)) {
391 614
            $event = new static();
392 614
            $event->setName($name);
393
        }
394 623
        $event->stopPropagation(false);
395
396 623
        if (is_object($class)) {
397 623
            if ($event->getTarget() === null) {
398 616
                $event->setTarget($class);
399
            }
400 623
            $class = get_class($class);
401
        } else {
402
            $class = ltrim($class, '\\');
403
        }
404
405 623
        $classes = array_merge(
406 623
            [$class],
407 623
            class_parents($class, true),
408 623
            class_implements($class, true)
409
        );
410
411 623
        foreach ($classes as $class) {
412 623
            $eventHandlers = [];
413 623
            foreach ($wildcardEventHandlers as $classWildcard => $handlers) {
414 1
                if (StringHelper::matchWildcard($classWildcard, $class)) {
415 1
                    $eventHandlers = array_merge($eventHandlers, $handlers);
416 1
                    unset($wildcardEventHandlers[$classWildcard]);
417
                }
418
            }
419
420 623
            if (!empty(self::$_events[$name][$class])) {
421 12
                $eventHandlers = array_merge($eventHandlers, self::$_events[$name][$class]);
422
            }
423
424 623
            foreach ($eventHandlers as $handler) {
425 13
                $event->setParams($handler[1]);
426 13
                call_user_func($handler[0], $event);
427 13
                if ($event->isPropagationStopped()) {
428 623
                    return;
429
                }
430
            }
431
        }
432 623
    }
433
}
434