Component::__set()   C
last analyzed

Complexity

Conditions 16
Paths 13

Size

Total Lines 47
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 16.0094

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
eloc 30
c 1
b 0
f 0
nc 13
nop 2
dl 0
loc 47
ccs 29
cts 30
cp 0.9667
crap 16.0094
rs 5.5666

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