Passed
Pull Request — master (#20248)
by Timothy
24:16
created

Component::__set()   C

Complexity

Conditions 15
Paths 12

Size

Total Lines 45
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 15.2764

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 28
c 1
b 0
f 0
nc 12
nop 2
dl 0
loc 45
ccs 25
cts 28
cp 0.8929
crap 15.2764
rs 5.9166

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 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 2098
    public function __get($name)
135
    {
136 2098
        $getter = 'get' . $name;
137 2098
        if (method_exists($this, $getter)) {
138
            // read property, e.g. getName()
139 2092
            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 4911
    public function __set($name, $value)
176
    {
177 4911
        $setter = 'set' . $name;
178 4911
        if (method_exists($this, $setter)) {
179
            // set property
180 4910
            $this->$setter($value);
181
182 4910
            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 ($value instanceof \Closure) {
194
                $this->attachBehavior($name, call_user_func($value));
195 4
            } elseif ((isset($value['class']) && is_subclass_of($value['class'], Behavior::class)) || (isset($value['__class']) && is_subclass_of($value['__class'], Behavior::class))) {
196 3
                $this->attachBehavior($name, Yii::createObject($value));
197 1
            } elseif (is_string($value) && is_subclass_of($value, Behavior::class, true)) {
198 1
                $this->attachBehavior($name, Yii::createObject($value));
199
            } else {
200
                throw new InvalidConfigException('Class is not of type ' . Behavior::class . ' or its subclasses');
201
            }
202
203 4
            return;
204
        }
205
206
        // behavior property
207 8
        $this->ensureBehaviors();
208 8
        foreach ($this->_behaviors as $behavior) {
209 3
            if ($behavior->canSetProperty($name)) {
210 3
                $behavior->$name = $value;
211 3
                return;
212
            }
213
        }
214
215 5
        if (method_exists($this, 'get' . $name)) {
216 4
            throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
217
        }
218
219 1
        throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
220
    }
221
222
    /**
223
     * Checks if a property is set, i.e. defined and not null.
224
     *
225
     * This method will check in the following order and act accordingly:
226
     *
227
     *  - a property defined by a setter: return whether the property is set
228
     *  - a property of a behavior: return whether the property is set
229
     *  - return `false` for non existing properties
230
     *
231
     * Do not call this method directly as it is a PHP magic method that
232
     * will be implicitly called when executing `isset($component->property)`.
233
     * @param string $name the property name or the event name
234
     * @return bool whether the named property is set
235
     * @see https://www.php.net/manual/en/function.isset.php
236
     */
237 76
    public function __isset($name)
238
    {
239 76
        $getter = 'get' . $name;
240 76
        if (method_exists($this, $getter)) {
241 75
            return $this->$getter() !== null;
242
        }
243
244
        // behavior property
245 3
        $this->ensureBehaviors();
246 3
        foreach ($this->_behaviors as $behavior) {
247 1
            if ($behavior->canGetProperty($name)) {
248 1
                return $behavior->$name !== null;
249
            }
250
        }
251
252 3
        return false;
253
    }
254
255
    /**
256
     * Sets a component property to be null.
257
     *
258
     * This method will check in the following order and act accordingly:
259
     *
260
     *  - a property defined by a setter: set the property value to be null
261
     *  - a property of a behavior: set the property value to be null
262
     *
263
     * Do not call this method directly as it is a PHP magic method that
264
     * will be implicitly called when executing `unset($component->property)`.
265
     * @param string $name the property name
266
     * @throws InvalidCallException if the property is read only.
267
     * @see https://www.php.net/manual/en/function.unset.php
268
     */
269 2
    public function __unset($name)
270
    {
271 2
        $setter = 'set' . $name;
272 2
        if (method_exists($this, $setter)) {
273 1
            $this->$setter(null);
274 1
            return;
275
        }
276
277
        // behavior property
278 2
        $this->ensureBehaviors();
279 2
        foreach ($this->_behaviors as $behavior) {
280 1
            if ($behavior->canSetProperty($name)) {
281 1
                $behavior->$name = null;
282 1
                return;
283
            }
284
        }
285
286 1
        throw new InvalidCallException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name);
287
    }
288
289
    /**
290
     * Calls the named method which is not a class method.
291
     *
292
     * This method will check if any attached behavior has
293
     * the named method and will execute it if available.
294
     *
295
     * Do not call this method directly as it is a PHP magic method that
296
     * will be implicitly called when an unknown method is being invoked.
297
     * @param string $name the method name
298
     * @param array $params method parameters
299
     * @return mixed the method return value
300
     * @throws UnknownMethodException when calling unknown method
301
     */
302 20
    public function __call($name, $params)
303
    {
304 20
        $this->ensureBehaviors();
305 20
        foreach ($this->_behaviors as $object) {
306 13
            if ($object->hasMethod($name)) {
307 12
                return call_user_func_array([$object, $name], $params);
308
            }
309
        }
310 9
        throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
311
    }
312
313
    /**
314
     * This method is called after the object is created by cloning an existing one.
315
     * It removes all behaviors because they are attached to the old object.
316
     */
317 38
    public function __clone()
318
    {
319 38
        $this->_events = [];
320 38
        $this->_eventWildcards = [];
321 38
        $this->_behaviors = null;
322
    }
323
324
    /**
325
     * Returns a value indicating whether a property is defined for this component.
326
     *
327
     * A property is defined if:
328
     *
329
     * - the class has a getter or setter method associated with the specified name
330
     *   (in this case, property name is case-insensitive);
331
     * - the class has a member variable with the specified name (when `$checkVars` is true);
332
     * - an attached behavior has a property of the given name (when `$checkBehaviors` is true).
333
     *
334
     * @param string $name the property name
335
     * @param bool $checkVars whether to treat member variables as properties
336
     * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
337
     * @return bool whether the property is defined
338
     * @see canGetProperty()
339
     * @see canSetProperty()
340
     */
341 5
    public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
342
    {
343 5
        return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors);
344
    }
345
346
    /**
347
     * Returns a value indicating whether a property can be read.
348
     *
349
     * A property can be read if:
350
     *
351
     * - the class has a getter method associated with the specified name
352
     *   (in this case, property name is case-insensitive);
353
     * - the class has a member variable with the specified name (when `$checkVars` is true);
354
     * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true).
355
     *
356
     * @param string $name the property name
357
     * @param bool $checkVars whether to treat member variables as properties
358
     * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
359
     * @return bool whether the property can be read
360
     * @see canSetProperty()
361
     */
362 7
    public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
363
    {
364 7
        if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) {
365 5
            return true;
366 7
        } elseif ($checkBehaviors) {
367 7
            $this->ensureBehaviors();
368 7
            foreach ($this->_behaviors as $behavior) {
369 1
                if ($behavior->canGetProperty($name, $checkVars)) {
370 1
                    return true;
371
                }
372
            }
373
        }
374
375 7
        return false;
376
    }
377
378
    /**
379
     * Returns a value indicating whether a property can be set.
380
     *
381
     * A property can be written if:
382
     *
383
     * - the class has a setter method associated with the specified name
384
     *   (in this case, property name is case-insensitive);
385
     * - the class has a member variable with the specified name (when `$checkVars` is true);
386
     * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true).
387
     *
388
     * @param string $name the property name
389
     * @param bool $checkVars whether to treat member variables as properties
390
     * @param bool $checkBehaviors whether to treat behaviors' properties as properties of this component
391
     * @return bool whether the property can be written
392
     * @see canGetProperty()
393
     */
394 18
    public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
395
    {
396 18
        if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) {
397 12
            return true;
398 7
        } elseif ($checkBehaviors) {
399 7
            $this->ensureBehaviors();
400 7
            foreach ($this->_behaviors as $behavior) {
401 1
                if ($behavior->canSetProperty($name, $checkVars)) {
402 1
                    return true;
403
                }
404
            }
405
        }
406
407 7
        return false;
408
    }
409
410
    /**
411
     * Returns a value indicating whether a method is defined.
412
     *
413
     * A method is defined if:
414
     *
415
     * - the class has a method with the specified name
416
     * - an attached behavior has a method with the given name (when `$checkBehaviors` is true).
417
     *
418
     * @param string $name the property name
419
     * @param bool $checkBehaviors whether to treat behaviors' methods as methods of this component
420
     * @return bool whether the method is defined
421
     */
422 57
    public function hasMethod($name, $checkBehaviors = true)
423
    {
424 57
        if (method_exists($this, $name)) {
425 45
            return true;
426 13
        } elseif ($checkBehaviors) {
427 13
            $this->ensureBehaviors();
428 13
            foreach ($this->_behaviors as $behavior) {
429 1
                if ($behavior->hasMethod($name)) {
430 1
                    return true;
431
                }
432
            }
433
        }
434
435 13
        return false;
436
    }
437
438
    /**
439
     * Returns a list of behaviors that this component should behave as.
440
     *
441
     * Child classes may override this method to specify the behaviors they want to behave as.
442
     *
443
     * The return value of this method should be an array of behavior objects or configurations
444
     * indexed by behavior names. A behavior configuration can be either a string specifying
445
     * the behavior class or an array of the following structure:
446
     *
447
     * ```php
448
     * 'behaviorName' => [
449
     *     'class' => 'BehaviorClass',
450
     *     'property1' => 'value1',
451
     *     'property2' => 'value2',
452
     * ]
453
     * ```
454
     *
455
     * Note that a behavior class must extend from [[Behavior]]. Behaviors can be attached using a name or anonymously.
456
     * When a name is used as the array key, using this name, the behavior can later be retrieved using [[getBehavior()]]
457
     * or be detached using [[detachBehavior()]]. Anonymous behaviors can not be retrieved or detached.
458
     *
459
     * Behaviors declared in this method will be attached to the component automatically (on demand).
460
     *
461
     * @return array the behavior configurations.
462
     */
463 2656
    public function behaviors()
464
    {
465 2656
        return [];
466
    }
467
468
    /**
469
     * Returns a value indicating whether there is any handler attached to the named event.
470
     * @param string $name the event name
471
     * @return bool whether there is any handler attached to the event.
472
     */
473 183
    public function hasEventHandlers($name)
474
    {
475 183
        $this->ensureBehaviors();
476
477 183
        if (!empty($this->_events[$name])) {
478 5
            return true;
479
        }
480
481 182
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
482 4
            if (!empty($handlers) && StringHelper::matchWildcard($wildcard, $name)) {
483 4
                return true;
484
            }
485
        }
486
487 182
        return Event::hasHandlers($this, $name);
488
    }
489
490
    /**
491
     * Attaches an event handler to an event.
492
     *
493
     * The event handler must be a valid PHP callback. The following are
494
     * some examples:
495
     *
496
     * ```
497
     * function ($event) { ... }         // anonymous function
498
     * [$object, 'handleClick']          // $object->handleClick()
499
     * ['Page', 'handleClick']           // Page::handleClick()
500
     * 'handleClick'                     // global function handleClick()
501
     * ```
502
     *
503
     * The event handler must be defined with the following signature,
504
     *
505
     * ```
506
     * function ($event)
507
     * ```
508
     *
509
     * where `$event` is an [[Event]] object which includes parameters associated with the event.
510
     *
511
     * Since 2.0.14 you can specify event name as a wildcard pattern:
512
     *
513
     * ```php
514
     * $component->on('event.group.*', function ($event) {
515
     *     Yii::trace($event->name . ' is triggered.');
516
     * });
517
     * ```
518
     *
519
     * @param string $name the event name
520
     * @param callable $handler the event handler
521
     * @param mixed $data the data to be passed to the event handler when the event is triggered.
522
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
523
     * @param bool $append whether to append new event handler to the end of the existing
524
     * handler list. If false, the new handler will be inserted at the beginning of the existing
525
     * handler list.
526
     * @see off()
527
     */
528 294
    public function on($name, $handler, $data = null, $append = true)
529
    {
530 294
        $this->ensureBehaviors();
531
532 294
        if (strpos($name, '*') !== false) {
533 5
            if ($append || empty($this->_eventWildcards[$name])) {
534 5
                $this->_eventWildcards[$name][] = [$handler, $data];
535
            } else {
536
                array_unshift($this->_eventWildcards[$name], [$handler, $data]);
537
            }
538 5
            return;
539
        }
540
541 291
        if ($append || empty($this->_events[$name])) {
542 291
            $this->_events[$name][] = [$handler, $data];
543
        } else {
544 6
            array_unshift($this->_events[$name], [$handler, $data]);
545
        }
546
    }
547
548
    /**
549
     * Detaches an existing event handler from this component.
550
     *
551
     * This method is the opposite of [[on()]].
552
     *
553
     * Note: in case wildcard pattern is passed for event name, only the handlers registered with this
554
     * wildcard will be removed, while handlers registered with plain names matching this wildcard will remain.
555
     *
556
     * @param string $name event name
557
     * @param callable|null $handler the event handler to be removed.
558
     * If it is null, all handlers attached to the named event will be removed.
559
     * @return bool if a handler is found and detached
560
     * @see on()
561
     */
562 91
    public function off($name, $handler = null)
563
    {
564 91
        $this->ensureBehaviors();
565 91
        if (empty($this->_events[$name]) && empty($this->_eventWildcards[$name])) {
566 6
            return false;
567
        }
568 90
        if ($handler === null) {
569 2
            unset($this->_events[$name], $this->_eventWildcards[$name]);
570 2
            return true;
571
        }
572
573 90
        $removed = false;
574
        // plain event names
575 90
        if (isset($this->_events[$name])) {
576 89
            foreach ($this->_events[$name] as $i => $event) {
577 89
                if ($event[0] === $handler) {
578 89
                    unset($this->_events[$name][$i]);
579 89
                    $removed = true;
580
                }
581
            }
582 89
            if ($removed) {
583 89
                $this->_events[$name] = array_values($this->_events[$name]);
584 89
                return true;
585
            }
586
        }
587
588
        // wildcard event names
589 2
        if (isset($this->_eventWildcards[$name])) {
590 1
            foreach ($this->_eventWildcards[$name] as $i => $event) {
591 1
                if ($event[0] === $handler) {
592 1
                    unset($this->_eventWildcards[$name][$i]);
593 1
                    $removed = true;
594
                }
595
            }
596 1
            if ($removed) {
597 1
                $this->_eventWildcards[$name] = array_values($this->_eventWildcards[$name]);
598
                // remove empty wildcards to save future redundant regex checks:
599 1
                if (empty($this->_eventWildcards[$name])) {
600 1
                    unset($this->_eventWildcards[$name]);
601
                }
602
            }
603
        }
604
605 2
        return $removed;
606
    }
607
608
    /**
609
     * Triggers an event.
610
     *
611
     * This method represents the happening of an event. It invokes all attached handlers for the event
612
     * including class-level handlers.
613
     *
614
     * @param string $name the event name
615
     * @param Event|null $event the event instance. If not set, a default [[Event]] object will be created.
616
     */
617 2603
    public function trigger($name, ?Event $event = null)
618
    {
619 2603
        $this->ensureBehaviors();
620
621 2603
        $eventHandlers = [];
622 2603
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
623 1
            if (StringHelper::matchWildcard($wildcard, $name)) {
624 1
                $eventHandlers[] = $handlers;
625
            }
626
        }
627 2603
        if (!empty($this->_events[$name])) {
628 276
            $eventHandlers[] = $this->_events[$name];
629
        }
630
631 2603
        if (!empty($eventHandlers)) {
632 277
            $eventHandlers = call_user_func_array('array_merge', $eventHandlers);
633 277
            if ($event === null) {
634 150
                $event = new Event();
635
            }
636 277
            if ($event->sender === null) {
637 277
                $event->sender = $this;
638
            }
639 277
            $event->handled = false;
640 277
            $event->name = $name;
641 277
            foreach ($eventHandlers as $handler) {
642 277
                $event->data = $handler[1];
643 277
                call_user_func($handler[0], $event);
644
                // stop further handling if the event is handled
645 271
                if ($event->handled) {
646 2
                    return;
647
                }
648
            }
649
        }
650
651
        // invoke class-level attached handlers
652 2602
        Event::trigger($this, $name, $event);
653
    }
654
655
    /**
656
     * Returns the named behavior object.
657
     * @param string $name the behavior name
658
     * @return Behavior|null the behavior object, or null if the behavior does not exist
659
     */
660 29
    public function getBehavior($name)
661
    {
662 29
        $this->ensureBehaviors();
663 29
        return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
664
    }
665
666
    /**
667
     * Returns all behaviors attached to this component.
668
     * @return Behavior[] list of behaviors attached to this component
669
     */
670 1
    public function getBehaviors()
671
    {
672 1
        $this->ensureBehaviors();
673 1
        return $this->_behaviors;
674
    }
675
676
    /**
677
     * Attaches a behavior to this component.
678
     * This method will create the behavior object based on the given
679
     * configuration. After that, the behavior object will be attached to
680
     * this component by calling the [[Behavior::attach()]] method.
681
     * @param string $name the name of the behavior.
682
     * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
683
     *
684
     *  - a [[Behavior]] object
685
     *  - a string specifying the behavior class
686
     *  - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
687
     *
688
     * @return Behavior the behavior object
689
     * @see detachBehavior()
690
     */
691 15
    public function attachBehavior($name, $behavior)
692
    {
693 15
        $this->ensureBehaviors();
694 15
        return $this->attachBehaviorInternal($name, $behavior);
695
    }
696
697
    /**
698
     * Attaches a list of behaviors to the component.
699
     * Each behavior is indexed by its name and should be a [[Behavior]] object,
700
     * a string specifying the behavior class, or an configuration array for creating the behavior.
701
     * @param array $behaviors list of behaviors to be attached to the component
702
     * @see attachBehavior()
703
     */
704 3
    public function attachBehaviors($behaviors)
705
    {
706 3
        $this->ensureBehaviors();
707 3
        foreach ($behaviors as $name => $behavior) {
708 3
            $this->attachBehaviorInternal($name, $behavior);
709
        }
710
    }
711
712
    /**
713
     * Detaches a behavior from the component.
714
     * The behavior's [[Behavior::detach()]] method will be invoked.
715
     * @param string $name the behavior's name.
716
     * @return Behavior|null the detached behavior. Null if the behavior does not exist.
717
     */
718 4
    public function detachBehavior($name)
719
    {
720 4
        $this->ensureBehaviors();
721 4
        if (isset($this->_behaviors[$name])) {
722 4
            $behavior = $this->_behaviors[$name];
723 4
            unset($this->_behaviors[$name]);
724 4
            $behavior->detach();
725 4
            return $behavior;
726
        }
727
728 1
        return null;
729
    }
730
731
    /**
732
     * Detaches all behaviors from the component.
733
     */
734 1
    public function detachBehaviors()
735
    {
736 1
        $this->ensureBehaviors();
737 1
        foreach ($this->_behaviors as $name => $behavior) {
738 1
            $this->detachBehavior($name);
739
        }
740
    }
741
742
    /**
743
     * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
744
     */
745 2657
    public function ensureBehaviors()
746
    {
747 2657
        if ($this->_behaviors === null) {
748 2657
            $this->_behaviors = [];
749 2657
            foreach ($this->behaviors() as $name => $behavior) {
750 259
                $this->attachBehaviorInternal($name, $behavior);
751
            }
752
        }
753
    }
754
755
    /**
756
     * Attaches a behavior to this component.
757
     * @param string|int $name the name of the behavior. If this is an integer, it means the behavior
758
     * is an anonymous one. Otherwise, the behavior is a named one and any existing behavior with the same name
759
     * will be detached first.
760
     * @param string|array|Behavior $behavior the behavior to be attached
761
     * @return Behavior the attached behavior.
762
     */
763 274
    private function attachBehaviorInternal($name, $behavior)
764
    {
765 274
        if (!($behavior instanceof Behavior)) {
766 259
            $behavior = Yii::createObject($behavior);
767
        }
768 274
        if (is_int($name)) {
769 9
            $behavior->attach($this);
770 9
            $this->_behaviors[] = $behavior;
771
        } else {
772 265
            if (isset($this->_behaviors[$name])) {
773 4
                $this->_behaviors[$name]->detach();
774
            }
775 265
            $behavior->attach($this);
776 265
            $this->_behaviors[$name] = $behavior;
777
        }
778
779 274
        return $behavior;
780
    }
781
}
782