GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Component   F
last analyzed

Complexity

Total Complexity 101

Size/Duplication

Total Lines 677
Duplicated Lines 0 %

Test Coverage

Coverage 98.54%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 190
dl 0
loc 677
ccs 202
cts 205
cp 0.9854
rs 2
c 2
b 0
f 0
wmc 101

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __get() 0 21 5
A hasMethod() 0 14 5
A detachBehavior() 0 11 2
A __unset() 0 18 4
A hasProperty() 0 3 2
A attachBehaviors() 0 5 2
B canSetProperty() 0 14 7
B trigger() 0 36 9
A __call() 0 9 3
A ensureBehaviors() 0 6 3
A on() 0 17 6
A getBehaviors() 0 4 1
A __clone() 0 5 1
C __set() 0 43 12
A __isset() 0 16 4
A detachBehaviors() 0 5 2
A getBehavior() 0 4 2
A behaviors() 0 3 1
A hasEventHandlers() 0 15 5
B canGetProperty() 0 14 7
A attachBehaviorInternal() 0 17 4
A attachBehavior() 0 4 1
C off() 0 44 13

How to fix   Complexity   

Complex Class

Complex classes like Component 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.

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 Component, and based on these observations, apply Extract Interface, too.

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;
11
use yii\helpers\StringHelper;
12
13
/**
14
 * Component is the base class that implements the *property*, *event* and *behavior* features.
15
 *
16
 * Component provides the *event* and *behavior* features, in addition to the *property* feature which is implemented in
17
 * its parent class [[\yii\base\BaseObject|BaseObject]].
18
 *
19
 * Event is a way to "inject" custom code into existing code at certain places. For example, a comment object can trigger
20
 * an "add" event when the user adds a comment. We can write custom code and attach it to this event so that when the event
21
 * is triggered (i.e. comment will be added), our custom code will be executed.
22
 *
23
 * An event is identified by a name that should be unique within the class it is defined at. Event names are *case-sensitive*.
24
 *
25
 * One or multiple PHP callbacks, called *event handlers*, can be attached to an event. You can call [[trigger()]] to
26
 * raise an event. When an event is raised, the event handlers will be invoked automatically in the order they were
27
 * attached.
28
 *
29
 * To attach an event handler to an event, call [[on()]]:
30
 *
31
 * ```php
32
 * $post->on('update', function ($event) {
33
 *     // send email notification
34
 * });
35
 * ```
36
 *
37
 * In the above, an anonymous function is attached to the "update" event of the post. You may attach
38
 * the following types of event handlers:
39
 *
40
 * - anonymous function: `function ($event) { ... }`
41
 * - object method: `[$object, 'handleAdd']`
42
 * - static class method: `['Page', 'handleAdd']`
43
 * - global function: `'handleAdd'`
44
 *
45
 * The signature of an event handler should be like the following:
46
 *
47
 * ```php
48
 * function foo($event)
49
 * ```
50
 *
51
 * where `$event` is an [[Event]] object which includes parameters associated with the event.
52
 *
53
 * You can also attach a handler to an event when configuring a component with a configuration array.
54
 * The syntax is like the following:
55
 *
56
 * ```php
57
 * [
58
 *     'on add' => function ($event) { ... }
59
 * ]
60
 * ```
61
 *
62
 * where `on add` stands for attaching an event to the `add` event.
63
 *
64
 * Sometimes, you may want to associate extra data with an event handler when you attach it to an event
65
 * and then access it when the handler is invoked. You may do so by
66
 *
67
 * ```php
68
 * $post->on('update', function ($event) {
69
 *     // the data can be accessed via $event->data
70
 * }, $data);
71
 * ```
72
 *
73
 * A behavior is an instance of [[Behavior]] or its child class. A component can be attached with one or multiple
74
 * behaviors. When a behavior is attached to a component, its public properties and methods can be accessed via the
75
 * component directly, as if the component owns those properties and methods.
76
 *
77
 * To attach a behavior to a component, declare it in [[behaviors()]], or explicitly call [[attachBehavior]]. Behaviors
78
 * declared in [[behaviors()]] are automatically attached to the corresponding component.
79
 *
80
 * One can also attach a behavior to a component when configuring it with a configuration array. The syntax is like the
81
 * following:
82
 *
83
 * ```php
84
 * [
85
 *     'as tree' => [
86
 *         'class' => 'Tree',
87
 *     ],
88
 * ]
89
 * ```
90
 *
91
 * where `as tree` stands for attaching a behavior named `tree`, and the array will be passed to [[\Yii::createObject()]]
92
 * to create the behavior object.
93
 *
94
 * For more details and usage information on Component, see the [guide article on components](guide:concept-components).
95
 *
96
 * @property-read Behavior[] $behaviors List of behaviors attached to this component.
97
 *
98
 * @author Qiang Xue <[email protected]>
99
 * @since 2.0
100
 */
101
class Component extends BaseObject
102
{
103
    /**
104
     * @var array the attached event handlers (event name => handlers)
105
     */
106
    private $_events = [];
107
    /**
108
     * @var array the event handlers attached for wildcard patterns (event name wildcard => handlers)
109
     * @since 2.0.14
110
     */
111
    private $_eventWildcards = [];
112
    /**
113
     * @var Behavior[]|null the attached behaviors (behavior name => behavior). This is `null` when not initialized.
114
     */
115
    private $_behaviors;
116
117
118
    /**
119
     * Returns the value of a component property.
120
     *
121
     * This method will check in the following order and act accordingly:
122
     *
123
     *  - a property defined by a getter: return the getter result
124
     *  - a property of a behavior: return the behavior property value
125
     *
126
     * Do not call this method directly as it is a PHP magic method that
127
     * will be implicitly called when executing `$value = $component->property;`.
128
     * @param string $name the property name
129
     * @return mixed the property value or the value of a behavior's property
130
     * @throws UnknownPropertyException if the property is not defined
131
     * @throws InvalidCallException if the property is write-only.
132
     * @see __set()
133
     */
134 2104
    public function __get($name)
135
    {
136 2104
        $getter = 'get' . $name;
137 2104
        if (method_exists($this, $getter)) {
138
            // read property, e.g. getName()
139 2098
            return $this->$getter();
140
        }
141
142
        // behavior property
143 13
        $this->ensureBehaviors();
144 13
        foreach ($this->_behaviors as $behavior) {
145 4
            if ($behavior->canGetProperty($name)) {
146 3
                return $behavior->$name;
147
            }
148
        }
149
150 10
        if (method_exists($this, 'set' . $name)) {
151 1
            throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
152
        }
153
154 9
        throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
155
    }
156
157
    /**
158
     * Sets the value of a component property.
159
     *
160
     * This method will check in the following order and act accordingly:
161
     *
162
     *  - a property defined by a setter: set the property value
163
     *  - an event in the format of "on xyz": attach the handler to the event "xyz"
164
     *  - a behavior in the format of "as xyz": attach the behavior named as "xyz"
165
     *  - a property of a behavior: set the behavior property value
166
     *
167
     * Do not call this method directly as it is a PHP magic method that
168
     * will be implicitly called when executing `$component->property = $value;`.
169
     * @param string $name the property name or the event name
170
     * @param mixed $value the property value
171
     * @throws UnknownPropertyException if the property is not defined
172
     * @throws InvalidCallException if the property is read-only.
173
     * @see __get()
174
     */
175 4920
    public function __set($name, $value)
176
    {
177 4920
        $setter = 'set' . $name;
178 4920
        if (method_exists($this, $setter)) {
179
            // set property
180 4919
            $this->$setter($value);
181
182 4919
            return;
183 24
        } elseif (strncmp($name, 'on ', 3) === 0) {
184
            // on event: attach event handler
185 12
            $this->on(trim(substr($name, 3)), $value);
186
187 12
            return;
188 12
        } elseif (strncmp($name, 'as ', 3) === 0) {
189
            // as behavior: attach behavior
190 4
            $name = trim(substr($name, 3));
191 4
            if ($value instanceof Behavior) {
192
                $this->attachBehavior($name, $value);
193 4
            } elseif (isset($value['class']) && is_subclass_of($value['class'], Behavior::class, true)) {
194 3
                $this->attachBehavior($name, Yii::createObject($value));
195 1
            } elseif (is_string($value) && is_subclass_of($value, Behavior::class, true)) {
196 1
                $this->attachBehavior($name, Yii::createObject($value));
197
            } else {
198
                throw new InvalidConfigException('Class is not of type ' . Behavior::class . ' or its subclasses');
199
            }
200
201 4
            return;
202
        }
203
204
        // behavior property
205 8
        $this->ensureBehaviors();
206 8
        foreach ($this->_behaviors as $behavior) {
207 3
            if ($behavior->canSetProperty($name)) {
208 3
                $behavior->$name = $value;
209 3
                return;
210
            }
211
        }
212
213 5
        if (method_exists($this, 'get' . $name)) {
214 4
            throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
215
        }
216
217 1
        throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
218
    }
219
220
    /**
221
     * Checks if a property is set, i.e. defined and not null.
222
     *
223
     * This method will check in the following order and act accordingly:
224
     *
225
     *  - a property defined by a setter: return whether the property is set
226
     *  - a property of a behavior: return whether the property is set
227
     *  - return `false` for non existing properties
228
     *
229
     * Do not call this method directly as it is a PHP magic method that
230
     * will be implicitly called when executing `isset($component->property)`.
231
     * @param string $name the property name or the event name
232
     * @return bool whether the named property is set
233
     * @see https://www.php.net/manual/en/function.isset.php
234
     */
235 76
    public function __isset($name)
236
    {
237 76
        $getter = 'get' . $name;
238 76
        if (method_exists($this, $getter)) {
239 75
            return $this->$getter() !== null;
240
        }
241
242
        // behavior property
243 3
        $this->ensureBehaviors();
244 3
        foreach ($this->_behaviors as $behavior) {
245 1
            if ($behavior->canGetProperty($name)) {
246 1
                return $behavior->$name !== null;
247
            }
248
        }
249
250 3
        return false;
251
    }
252
253
    /**
254
     * Sets a component property to be null.
255
     *
256
     * This method will check in the following order and act accordingly:
257
     *
258
     *  - a property defined by a setter: set the property value to be null
259
     *  - a property of a behavior: set the property value to be null
260
     *
261
     * Do not call this method directly as it is a PHP magic method that
262
     * will be implicitly called when executing `unset($component->property)`.
263
     * @param string $name the property name
264
     * @throws InvalidCallException if the property is read only.
265
     * @see https://www.php.net/manual/en/function.unset.php
266
     */
267 2
    public function __unset($name)
268
    {
269 2
        $setter = 'set' . $name;
270 2
        if (method_exists($this, $setter)) {
271 1
            $this->$setter(null);
272 1
            return;
273
        }
274
275
        // behavior property
276 2
        $this->ensureBehaviors();
277 2
        foreach ($this->_behaviors as $behavior) {
278 1
            if ($behavior->canSetProperty($name)) {
279 1
                $behavior->$name = null;
280 1
                return;
281
            }
282
        }
283
284 1
        throw new InvalidCallException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name);
285
    }
286
287
    /**
288
     * Calls the named method which is not a class method.
289
     *
290
     * This method will check if any attached behavior has
291
     * the named method and will execute it if available.
292
     *
293
     * Do not call this method directly as it is a PHP magic method that
294
     * will be implicitly called when an unknown method is being invoked.
295
     * @param string $name the method name
296
     * @param array $params method parameters
297
     * @return mixed the method return value
298
     * @throws UnknownMethodException when calling unknown method
299
     */
300 20
    public function __call($name, $params)
301
    {
302 20
        $this->ensureBehaviors();
303 20
        foreach ($this->_behaviors as $object) {
304 13
            if ($object->hasMethod($name)) {
305 12
                return call_user_func_array([$object, $name], $params);
306
            }
307
        }
308 9
        throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
309
    }
310
311
    /**
312
     * This method is called after the object is created by cloning an existing one.
313
     * It removes all behaviors because they are attached to the old object.
314
     */
315 41
    public function __clone()
316
    {
317 41
        $this->_events = [];
318 41
        $this->_eventWildcards = [];
319 41
        $this->_behaviors = null;
320
    }
321
322
    /**
323
     * Returns a value indicating whether a property is defined for this component.
324
     *
325
     * A property is defined if:
326
     *
327
     * - the class has a getter or setter method associated with the specified name
328
     *   (in this case, property name is case-insensitive);
329
     * - the class has a member variable with the specified name (when `$checkVars` is true);
330
     * - an attached behavior has a property of the given name (when `$checkBehaviors` is true).
331
     *
332
     * @param string $name the property name
333
     * @param bool $checkVars whether to treat member variables as properties
334
     * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
335
     * @return bool whether the property is defined
336
     * @see canGetProperty()
337
     * @see canSetProperty()
338
     */
339 5
    public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
340
    {
341 5
        return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors);
342
    }
343
344
    /**
345
     * Returns a value indicating whether a property can be read.
346
     *
347
     * A property can be read if:
348
     *
349
     * - the class has a getter method associated with the specified name
350
     *   (in this case, property name is case-insensitive);
351
     * - the class has a member variable with the specified name (when `$checkVars` is true);
352
     * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true).
353
     *
354
     * @param string $name the property name
355
     * @param bool $checkVars whether to treat member variables as properties
356
     * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
357
     * @return bool whether the property can be read
358
     * @see canSetProperty()
359
     */
360 7
    public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
361
    {
362 7
        if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) {
363 5
            return true;
364 7
        } elseif ($checkBehaviors) {
365 7
            $this->ensureBehaviors();
366 7
            foreach ($this->_behaviors as $behavior) {
367 1
                if ($behavior->canGetProperty($name, $checkVars)) {
368 1
                    return true;
369
                }
370
            }
371
        }
372
373 7
        return false;
374
    }
375
376
    /**
377
     * Returns a value indicating whether a property can be set.
378
     *
379
     * A property can be written if:
380
     *
381
     * - the class has a setter method associated with the specified name
382
     *   (in this case, property name is case-insensitive);
383
     * - the class has a member variable with the specified name (when `$checkVars` is true);
384
     * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true).
385
     *
386
     * @param string $name the property name
387
     * @param bool $checkVars whether to treat member variables as properties
388
     * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
389
     * @return bool whether the property can be written
390
     * @see canGetProperty()
391
     */
392 18
    public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
393
    {
394 18
        if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) {
395 12
            return true;
396 7
        } elseif ($checkBehaviors) {
397 7
            $this->ensureBehaviors();
398 7
            foreach ($this->_behaviors as $behavior) {
399 1
                if ($behavior->canSetProperty($name, $checkVars)) {
400 1
                    return true;
401
                }
402
            }
403
        }
404
405 7
        return false;
406
    }
407
408
    /**
409
     * Returns a value indicating whether a method is defined.
410
     *
411
     * A method is defined if:
412
     *
413
     * - the class has a method with the specified name
414
     * - an attached behavior has a method with the given name (when `$checkBehaviors` is true).
415
     *
416
     * @param string $name the property name
417
     * @param bool $checkBehaviors whether to treat behaviors' methods as methods of this component
418
     * @return bool whether the method is defined
419
     */
420 57
    public function hasMethod($name, $checkBehaviors = true)
421
    {
422 57
        if (method_exists($this, $name)) {
423 45
            return true;
424 13
        } elseif ($checkBehaviors) {
425 13
            $this->ensureBehaviors();
426 13
            foreach ($this->_behaviors as $behavior) {
427 1
                if ($behavior->hasMethod($name)) {
428 1
                    return true;
429
                }
430
            }
431
        }
432
433 13
        return false;
434
    }
435
436
    /**
437
     * Returns a list of behaviors that this component should behave as.
438
     *
439
     * Child classes may override this method to specify the behaviors they want to behave as.
440
     *
441
     * The return value of this method should be an array of behavior objects or configurations
442
     * indexed by behavior names. A behavior configuration can be either a string specifying
443
     * the behavior class or an array of the following structure:
444
     *
445
     * ```php
446
     * 'behaviorName' => [
447
     *     'class' => 'BehaviorClass',
448
     *     'property1' => 'value1',
449
     *     'property2' => 'value2',
450
     * ]
451
     * ```
452
     *
453
     * Note that a behavior class must extend from [[Behavior]]. Behaviors can be attached using a name or anonymously.
454
     * When a name is used as the array key, using this name, the behavior can later be retrieved using [[getBehavior()]]
455
     * or be detached using [[detachBehavior()]]. Anonymous behaviors can not be retrieved or detached.
456
     *
457
     * Behaviors declared in this method will be attached to the component automatically (on demand).
458
     *
459
     * @return array the behavior configurations.
460
     */
461 2659
    public function behaviors()
462
    {
463 2659
        return [];
464
    }
465
466
    /**
467
     * Returns a value indicating whether there is any handler attached to the named event.
468
     * @param string $name the event name
469
     * @return bool whether there is any handler attached to the event.
470
     */
471 183
    public function hasEventHandlers($name)
472
    {
473 183
        $this->ensureBehaviors();
474
475 183
        if (!empty($this->_events[$name])) {
476 5
            return true;
477
        }
478
479 182
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
480 4
            if (!empty($handlers) && StringHelper::matchWildcard($wildcard, $name)) {
481 4
                return true;
482
            }
483
        }
484
485 182
        return Event::hasHandlers($this, $name);
486
    }
487
488
    /**
489
     * Attaches an event handler to an event.
490
     *
491
     * The event handler must be a valid PHP callback. The following are
492
     * some examples:
493
     *
494
     * ```
495
     * function ($event) { ... }         // anonymous function
496
     * [$object, 'handleClick']          // $object->handleClick()
497
     * ['Page', 'handleClick']           // Page::handleClick()
498
     * 'handleClick'                     // global function handleClick()
499
     * ```
500
     *
501
     * The event handler must be defined with the following signature,
502
     *
503
     * ```
504
     * function ($event)
505
     * ```
506
     *
507
     * where `$event` is an [[Event]] object which includes parameters associated with the event.
508
     *
509
     * Since 2.0.14 you can specify event name as a wildcard pattern:
510
     *
511
     * ```php
512
     * $component->on('event.group.*', function ($event) {
513
     *     Yii::trace($event->name . ' is triggered.');
514
     * });
515
     * ```
516
     *
517
     * @param string $name the event name
518
     * @param callable $handler the event handler
519
     * @param mixed $data the data to be passed to the event handler when the event is triggered.
520
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
521
     * @param bool $append whether to append new event handler to the end of the existing
522
     * handler list. If false, the new handler will be inserted at the beginning of the existing
523
     * handler list.
524
     * @see off()
525
     */
526 294
    public function on($name, $handler, $data = null, $append = true)
527
    {
528 294
        $this->ensureBehaviors();
529
530 294
        if (strpos($name, '*') !== false) {
531 5
            if ($append || empty($this->_eventWildcards[$name])) {
532 5
                $this->_eventWildcards[$name][] = [$handler, $data];
533
            } else {
534
                array_unshift($this->_eventWildcards[$name], [$handler, $data]);
535
            }
536 5
            return;
537
        }
538
539 291
        if ($append || empty($this->_events[$name])) {
540 291
            $this->_events[$name][] = [$handler, $data];
541
        } else {
542 6
            array_unshift($this->_events[$name], [$handler, $data]);
543
        }
544
    }
545
546
    /**
547
     * Detaches an existing event handler from this component.
548
     *
549
     * This method is the opposite of [[on()]].
550
     *
551
     * Note: in case wildcard pattern is passed for event name, only the handlers registered with this
552
     * wildcard will be removed, while handlers registered with plain names matching this wildcard will remain.
553
     *
554
     * @param string $name event name
555
     * @param callable|null $handler the event handler to be removed.
556
     * If it is null, all handlers attached to the named event will be removed.
557
     * @return bool if a handler is found and detached
558
     * @see on()
559
     */
560 91
    public function off($name, $handler = null)
561
    {
562 91
        $this->ensureBehaviors();
563 91
        if (empty($this->_events[$name]) && empty($this->_eventWildcards[$name])) {
564 6
            return false;
565
        }
566 90
        if ($handler === null) {
567 2
            unset($this->_events[$name], $this->_eventWildcards[$name]);
568 2
            return true;
569
        }
570
571 90
        $removed = false;
572
        // plain event names
573 90
        if (isset($this->_events[$name])) {
574 89
            foreach ($this->_events[$name] as $i => $event) {
575 89
                if ($event[0] === $handler) {
576 89
                    unset($this->_events[$name][$i]);
577 89
                    $removed = true;
578
                }
579
            }
580 89
            if ($removed) {
581 89
                $this->_events[$name] = array_values($this->_events[$name]);
582 89
                return true;
583
            }
584
        }
585
586
        // wildcard event names
587 2
        if (isset($this->_eventWildcards[$name])) {
588 1
            foreach ($this->_eventWildcards[$name] as $i => $event) {
589 1
                if ($event[0] === $handler) {
590 1
                    unset($this->_eventWildcards[$name][$i]);
591 1
                    $removed = true;
592
                }
593
            }
594 1
            if ($removed) {
595 1
                $this->_eventWildcards[$name] = array_values($this->_eventWildcards[$name]);
596
                // remove empty wildcards to save future redundant regex checks:
597 1
                if (empty($this->_eventWildcards[$name])) {
598 1
                    unset($this->_eventWildcards[$name]);
599
                }
600
            }
601
        }
602
603 2
        return $removed;
604
    }
605
606
    /**
607
     * Triggers an event.
608
     *
609
     * This method represents the happening of an event. It invokes all attached handlers for the event
610
     * including class-level handlers.
611
     *
612
     * @param string $name the event name
613
     * @param Event|null $event the event instance. If not set, a default [[Event]] object will be created.
614
     */
615 2606
    public function trigger($name, ?Event $event = null)
616
    {
617 2606
        $this->ensureBehaviors();
618
619 2606
        $eventHandlers = [];
620 2606
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
621 1
            if (StringHelper::matchWildcard($wildcard, $name)) {
622 1
                $eventHandlers[] = $handlers;
623
            }
624
        }
625 2606
        if (!empty($this->_events[$name])) {
626 276
            $eventHandlers[] = $this->_events[$name];
627
        }
628
629 2606
        if (!empty($eventHandlers)) {
630 277
            $eventHandlers = call_user_func_array('array_merge', $eventHandlers);
631 277
            if ($event === null) {
632 150
                $event = new Event();
633
            }
634 277
            if ($event->sender === null) {
635 277
                $event->sender = $this;
636
            }
637 277
            $event->handled = false;
638 277
            $event->name = $name;
639 277
            foreach ($eventHandlers as $handler) {
640 277
                $event->data = $handler[1];
641 277
                call_user_func($handler[0], $event);
642
                // stop further handling if the event is handled
643 271
                if ($event->handled) {
644 2
                    return;
645
                }
646
            }
647
        }
648
649
        // invoke class-level attached handlers
650 2605
        Event::trigger($this, $name, $event);
651
    }
652
653
    /**
654
     * Returns the named behavior object.
655
     * @param string $name the behavior name
656
     * @return Behavior|null the behavior object, or null if the behavior does not exist
657
     */
658 29
    public function getBehavior($name)
659
    {
660 29
        $this->ensureBehaviors();
661 29
        return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
662
    }
663
664
    /**
665
     * Returns all behaviors attached to this component.
666
     * @return Behavior[] list of behaviors attached to this component
667
     */
668 1
    public function getBehaviors()
669
    {
670 1
        $this->ensureBehaviors();
671 1
        return $this->_behaviors;
672
    }
673
674
    /**
675
     * Attaches a behavior to this component.
676
     * This method will create the behavior object based on the given
677
     * configuration. After that, the behavior object will be attached to
678
     * this component by calling the [[Behavior::attach()]] method.
679
     * @param string $name the name of the behavior.
680
     * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
681
     *
682
     *  - a [[Behavior]] object
683
     *  - a string specifying the behavior class
684
     *  - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
685
     *
686
     * @return Behavior the behavior object
687
     * @see detachBehavior()
688
     */
689 15
    public function attachBehavior($name, $behavior)
690
    {
691 15
        $this->ensureBehaviors();
692 15
        return $this->attachBehaviorInternal($name, $behavior);
693
    }
694
695
    /**
696
     * Attaches a list of behaviors to the component.
697
     * Each behavior is indexed by its name and should be a [[Behavior]] object,
698
     * a string specifying the behavior class, or an configuration array for creating the behavior.
699
     * @param array $behaviors list of behaviors to be attached to the component
700
     * @see attachBehavior()
701
     */
702 3
    public function attachBehaviors($behaviors)
703
    {
704 3
        $this->ensureBehaviors();
705 3
        foreach ($behaviors as $name => $behavior) {
706 3
            $this->attachBehaviorInternal($name, $behavior);
707
        }
708
    }
709
710
    /**
711
     * Detaches a behavior from the component.
712
     * The behavior's [[Behavior::detach()]] method will be invoked.
713
     * @param string $name the behavior's name.
714
     * @return Behavior|null the detached behavior. Null if the behavior does not exist.
715
     */
716 4
    public function detachBehavior($name)
717
    {
718 4
        $this->ensureBehaviors();
719 4
        if (isset($this->_behaviors[$name])) {
720 4
            $behavior = $this->_behaviors[$name];
721 4
            unset($this->_behaviors[$name]);
722 4
            $behavior->detach();
723 4
            return $behavior;
724
        }
725
726 1
        return null;
727
    }
728
729
    /**
730
     * Detaches all behaviors from the component.
731
     */
732 1
    public function detachBehaviors()
733
    {
734 1
        $this->ensureBehaviors();
735 1
        foreach ($this->_behaviors as $name => $behavior) {
736 1
            $this->detachBehavior($name);
737
        }
738
    }
739
740
    /**
741
     * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
742
     */
743 2660
    public function ensureBehaviors()
744
    {
745 2660
        if ($this->_behaviors === null) {
746 2660
            $this->_behaviors = [];
747 2660
            foreach ($this->behaviors() as $name => $behavior) {
748 259
                $this->attachBehaviorInternal($name, $behavior);
749
            }
750
        }
751
    }
752
753
    /**
754
     * Attaches a behavior to this component.
755
     * @param string|int $name the name of the behavior. If this is an integer, it means the behavior
756
     * is an anonymous one. Otherwise, the behavior is a named one and any existing behavior with the same name
757
     * will be detached first.
758
     * @param string|array|Behavior $behavior the behavior to be attached
759
     * @return Behavior the attached behavior.
760
     */
761 274
    private function attachBehaviorInternal($name, $behavior)
762
    {
763 274
        if (!($behavior instanceof Behavior)) {
764 259
            $behavior = Yii::createObject($behavior);
765
        }
766 274
        if (is_int($name)) {
767 9
            $behavior->attach($this);
768 9
            $this->_behaviors[] = $behavior;
769
        } else {
770 265
            if (isset($this->_behaviors[$name])) {
771 4
                $this->_behaviors[$name]->detach();
772
            }
773 265
            $behavior->attach($this);
774 265
            $this->_behaviors[$name] = $behavior;
775
        }
776
777 274
        return $behavior;
778
    }
779
}
780