Issues (915)

framework/base/Event.php (1 issue)

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