Passed
Push — master ( 920ce2...ff3aee )
by Alexander
16:26 queued 08:17
created

Component::__set()   C

Complexity

Conditions 12
Paths 11

Size

Total Lines 43
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 12.0654

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 26
c 1
b 0
f 0
nc 11
nop 2
dl 0
loc 43
ccs 24
cts 26
cp 0.9231
crap 12.0654
rs 6.9666

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