Passed
Push — master ( 778d70...e2773e )
by Alexander
09:59
created

framework/base/Event.php (1 issue)

Severity
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://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|null 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::trace(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::trace(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 24
    public static function on($class, $name, $handler, $data = null, $append = true)
104
    {
105 24
        $class = ltrim($class, '\\');
106
107 24
        if (strpos($class, '*') !== false || strpos($name, '*') !== false) {
108 4
            if ($append || empty(self::$_eventWildcards[$name][$class])) {
109 4
                self::$_eventWildcards[$name][$class][] = [$handler, $data];
110
            } else {
111
                array_unshift(self::$_eventWildcards[$name][$class], [$handler, $data]);
112
            }
113 4
            return;
114
        }
115
116 20
        if ($append || empty(self::$_events[$name][$class])) {
117 20
            self::$_events[$name][$class][] = [$handler, $data];
118
        } else {
119
            array_unshift(self::$_events[$name][$class], [$handler, $data]);
120
        }
121 20
    }
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|null $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 19
    public static function off($class, $name, $handler = null)
139
    {
140 19
        $class = ltrim($class, '\\');
141 19
        if (empty(self::$_events[$name][$class]) && empty(self::$_eventWildcards[$name][$class])) {
142
            return false;
143
        }
144 19
        if ($handler === null) {
145 13
            unset(self::$_events[$name][$class]);
146 13
            unset(self::$_eventWildcards[$name][$class]);
147 13
            return true;
148
        }
149
150
        // plain event names
151 6
        if (isset(self::$_events[$name][$class])) {
152 5
            $removed = false;
153 5
            foreach (self::$_events[$name][$class] as $i => $event) {
154 5
                if ($event[0] === $handler) {
155 5
                    unset(self::$_events[$name][$class][$i]);
156 5
                    $removed = true;
157
                }
158
            }
159 5
            if ($removed) {
160 5
                self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
161 5
                return true;
162
            }
163
        }
164
165
        // wildcard event names
166 2
        $removed = false;
167 2
        if (isset(self::$_eventWildcards[$name][$class])) {
168 1
            foreach (self::$_eventWildcards[$name][$class] as $i => $event) {
169 1
                if ($event[0] === $handler) {
170 1
                    unset(self::$_eventWildcards[$name][$class][$i]);
171 1
                    $removed = true;
172
                }
173
            }
174 1
            if ($removed) {
175 1
                self::$_eventWildcards[$name][$class] = array_values(self::$_eventWildcards[$name][$class]);
176
                // remove empty wildcards to save future redundant regex checks :
177 1
                if (empty(self::$_eventWildcards[$name][$class])) {
178 1
                    unset(self::$_eventWildcards[$name][$class]);
179 1
                    if (empty(self::$_eventWildcards[$name])) {
180 1
                        unset(self::$_eventWildcards[$name]);
181
                    }
182
                }
183
            }
184
        }
185
186 2
        return $removed;
187
    }
188
189
    /**
190
     * Detaches all registered class-level event handlers.
191
     * @see on()
192
     * @see off()
193
     * @since 2.0.10
194
     */
195 9
    public static function offAll()
196
    {
197 9
        self::$_events = [];
198 9
        self::$_eventWildcards = [];
199 9
    }
200
201
    /**
202
     * Returns a value indicating whether there is any handler attached to the specified class-level event.
203
     * Note that this method will also check all parent classes to see if there is any handler attached
204
     * to the named event.
205
     * @param string|object $class the object or the fully qualified class name specifying the class-level event.
206
     * @param string $name the event name.
207
     * @return bool whether there is any handler attached to the event.
208
     */
209 188
    public static function hasHandlers($class, $name)
210
    {
211 188
        if (empty(self::$_eventWildcards) && empty(self::$_events[$name])) {
212 186
            return false;
213
        }
214
215 7
        if (is_object($class)) {
216 2
            $class = get_class($class);
217
        } else {
218 5
            $class = ltrim($class, '\\');
219
        }
220
221 7
        $classes = array_merge(
222 7
            [$class],
223 7
            class_parents($class, true),
224 7
            class_implements($class, true)
225
        );
226
227
        // regular events
228 7
        foreach ($classes as $className) {
229 7
            if (!empty(self::$_events[$name][$className])) {
230 4
                return true;
231
            }
232
        }
233
234
        // wildcard events
235 5
        foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
236 3
            if (!StringHelper::matchWildcard($nameWildcard, $name, ['escape' => false])) {
237
                continue;
238
            }
239 3
            foreach ($classHandlers as $classWildcard => $handlers) {
240 3
                if (empty($handlers)) {
241
                    continue;
242
                }
243 3
                foreach ($classes as $className) {
244 3
                    if (StringHelper::matchWildcard($classWildcard, $className, ['escape' => false])) {
245 3
                        return true;
246
                    }
247
                }
248
            }
249
        }
250
251 2
        return false;
252
    }
253
254
    /**
255
     * Triggers a class-level event.
256
     * This method will cause invocation of event handlers that are attached to the named event
257
     * for the specified class and all its parent classes.
258
     * @param string|object $class the object or the fully qualified class name specifying the class-level event.
259
     * @param string $name the event name.
260
     * @param Event|null $event the event parameter. If not set, a default [[Event]] object will be created.
261
     */
262 2577
    public static function trigger($class, $name, $event = null)
263
    {
264 2577
        $wildcardEventHandlers = [];
265 2577
        foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
266 2
            if (!StringHelper::matchWildcard($nameWildcard, $name)) {
267
                continue;
268
            }
269 2
            $wildcardEventHandlers = array_merge($wildcardEventHandlers, $classHandlers);
270
        }
271
272 2577
        if (empty(self::$_events[$name]) && empty($wildcardEventHandlers)) {
273 2528
            return;
274
        }
275
276 803
        if ($event === null) {
277 789
            $event = new static();
278
        }
279 803
        $event->handled = false;
280 803
        $event->name = $name;
281
282 803
        if (is_object($class)) {
283 803
            if ($event->sender === null) {
284 794
                $event->sender = $class;
285
            }
286 803
            $class = get_class($class);
287
        } else {
288 1
            $class = ltrim($class, '\\');
289
        }
290
291 803
        $classes = array_merge(
292 803
            [$class],
293 803
            class_parents($class, true),
294 803
            class_implements($class, true)
295
        );
296
297 803
        foreach ($classes as $class) {
0 ignored issues
show
$class is overwriting one of the parameters of this function.
Loading history...
298 803
            $eventHandlers = [];
299 803
            foreach ($wildcardEventHandlers as $classWildcard => $handlers) {
300 2
                if (StringHelper::matchWildcard($classWildcard, $class, ['escape' => false])) {
301 2
                    $eventHandlers = array_merge($eventHandlers, $handlers);
302 2
                    unset($wildcardEventHandlers[$classWildcard]);
303
                }
304
            }
305
306 803
            if (!empty(self::$_events[$name][$class])) {
307 15
                $eventHandlers = array_merge($eventHandlers, self::$_events[$name][$class]);
308
            }
309
310 803
            foreach ($eventHandlers as $handler) {
311 17
                $event->data = $handler[1];
312 17
                call_user_func($handler[0], $event);
313 17
                if ($event->handled) {
314
                    return;
315
                }
316
            }
317
        }
318 803
    }
319
}
320