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 817
    public function __get($name)
135
    {
136 817
        $getter = 'get' . $name;
137 817
        if (method_exists($this, $getter)) {
138
            // read property, e.g. getName()
139 812
            return $this->$getter();
140
        }
141
142
        // behavior property
143 7
        $this->ensureBehaviors();
144 7
        foreach ($this->_behaviors as $behavior) {
145 4
            if ($behavior->canGetProperty($name)) {
146 3
                return $behavior->$name;
147
            }
148
        }
149
150 4
        if (method_exists($this, 'set' . $name)) {
151 1
            throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
152
        }
153
154 3
        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 2348
    public function __set($name, $value)
176
    {
177 2348
        $setter = 'set' . $name;
178 2348
        if (method_exists($this, $setter)) {
179
            // set property
180 2347
            $this->$setter($value);
181
182 2347
            return;
183 21
        } elseif (strncmp($name, 'on ', 3) === 0) {
184
            // on event: attach event handler
185 11
            $this->on(trim(substr($name, 3)), $value);
186
187 11
            return;
188 10
        } 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 5
        $this->ensureBehaviors();
210 5
        foreach ($this->_behaviors as $behavior) {
211 3
            if ($behavior->canSetProperty($name)) {
212 3
                $behavior->$name = $value;
213 3
                return;
214
            }
215
        }
216
217 2
        if (method_exists($this, 'get' . $name)) {
218 1
            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 14
    public function __call($name, $params)
305
    {
306 14
        $this->ensureBehaviors();
307 14
        foreach ($this->_behaviors as $object) {
308 13
            if ($object->hasMethod($name)) {
309 12
                return call_user_func_array([$object, $name], $params);
310
            }
311
        }
312 3
        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 7
    public function __clone()
320
    {
321 7
        $this->_events = [];
322 7
        $this->_eventWildcards = [];
323 7
        $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 2
    public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
344
    {
345 2
        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 4
    public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
365
    {
366 4
        if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) {
367 2
            return true;
368 4
        } elseif ($checkBehaviors) {
369 4
            $this->ensureBehaviors();
370 4
            foreach ($this->_behaviors as $behavior) {
371 1
                if ($behavior->canGetProperty($name, $checkVars)) {
372 1
                    return true;
373
                }
374
            }
375
        }
376
377 4
        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 9
    public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
397
    {
398 9
        if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) {
399 6
            return true;
400 4
        } elseif ($checkBehaviors) {
401 4
            $this->ensureBehaviors();
402 4
            foreach ($this->_behaviors as $behavior) {
403 1
                if ($behavior->canSetProperty($name, $checkVars)) {
404 1
                    return true;
405
                }
406
            }
407
        }
408
409 4
        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
     * @phpstan-return array<int|string, class-string|array{class: class-string, ...}>
466
     * @psalm-return array<int|string, class-string|array{class: class-string, ...}>
467
     */
468 612
    public function behaviors()
469
    {
470 612
        return [];
471
    }
472
473
    /**
474
     * Returns a value indicating whether there is any handler attached to the named event.
475
     * @param string $name the event name
476
     * @return bool whether there is any handler attached to the event.
477
     */
478 94
    public function hasEventHandlers($name)
479
    {
480 94
        $this->ensureBehaviors();
481
482 94
        if (!empty($this->_events[$name])) {
483 5
            return true;
484
        }
485
486 93
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
487 4
            if (!empty($handlers) && StringHelper::matchWildcard($wildcard, $name)) {
488 4
                return true;
489
            }
490
        }
491
492 93
        return Event::hasHandlers($this, $name);
493
    }
494
495
    /**
496
     * Attaches an event handler to an event.
497
     *
498
     * The event handler must be a valid PHP callback. The following are
499
     * some examples:
500
     *
501
     * ```
502
     * function ($event) { ... }         // anonymous function
503
     * [$object, 'handleClick']          // $object->handleClick()
504
     * ['Page', 'handleClick']           // Page::handleClick()
505
     * 'handleClick'                     // global function handleClick()
506
     * ```
507
     *
508
     * The event handler must be defined with the following signature,
509
     *
510
     * ```
511
     * function ($event)
512
     * ```
513
     *
514
     * where `$event` is an [[Event]] object which includes parameters associated with the event.
515
     *
516
     * Since 2.0.14 you can specify event name as a wildcard pattern:
517
     *
518
     * ```php
519
     * $component->on('event.group.*', function ($event) {
520
     *     Yii::trace($event->name . ' is triggered.');
521
     * });
522
     * ```
523
     *
524
     * @param string $name the event name
525
     * @param callable $handler the event handler
526
     * @param mixed $data the data to be passed to the event handler when the event is triggered.
527
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
528
     * @param bool $append whether to append new event handler to the end of the existing
529
     * handler list. If false, the new handler will be inserted at the beginning of the existing
530
     * handler list.
531
     * @see off()
532
     */
533 169
    public function on($name, $handler, $data = null, $append = true)
534
    {
535 169
        $this->ensureBehaviors();
536
537 169
        if (strpos($name, '*') !== false) {
538 5
            if ($append || empty($this->_eventWildcards[$name])) {
539 5
                $this->_eventWildcards[$name][] = [$handler, $data];
540
            } else {
541
                array_unshift($this->_eventWildcards[$name], [$handler, $data]);
542
            }
543 5
            return;
544
        }
545
546 166
        if ($append || empty($this->_events[$name])) {
547 166
            $this->_events[$name][] = [$handler, $data];
548
        } else {
549 6
            array_unshift($this->_events[$name], [$handler, $data]);
550
        }
551
    }
552
553
    /**
554
     * Detaches an existing event handler from this component.
555
     *
556
     * This method is the opposite of [[on()]].
557
     *
558
     * Note: in case wildcard pattern is passed for event name, only the handlers registered with this
559
     * wildcard will be removed, while handlers registered with plain names matching this wildcard will remain.
560
     *
561
     * @param string $name event name
562
     * @param callable|null $handler the event handler to be removed.
563
     * If it is null, all handlers attached to the named event will be removed.
564
     * @return bool if a handler is found and detached
565
     * @see on()
566
     */
567 91
    public function off($name, $handler = null)
568
    {
569 91
        $this->ensureBehaviors();
570 91
        if (empty($this->_events[$name]) && empty($this->_eventWildcards[$name])) {
571 6
            return false;
572
        }
573 90
        if ($handler === null) {
574 2
            unset($this->_events[$name], $this->_eventWildcards[$name]);
575 2
            return true;
576
        }
577
578 90
        $removed = false;
579
        // plain event names
580 90
        if (isset($this->_events[$name])) {
581 89
            foreach ($this->_events[$name] as $i => $event) {
582 89
                if ($event[0] === $handler) {
583 89
                    unset($this->_events[$name][$i]);
584 89
                    $removed = true;
585
                }
586
            }
587 89
            if ($removed) {
588 89
                $this->_events[$name] = array_values($this->_events[$name]);
589 89
                return true;
590
            }
591
        }
592
593
        // wildcard event names
594 2
        if (isset($this->_eventWildcards[$name])) {
595 1
            foreach ($this->_eventWildcards[$name] as $i => $event) {
596 1
                if ($event[0] === $handler) {
597 1
                    unset($this->_eventWildcards[$name][$i]);
598 1
                    $removed = true;
599
                }
600
            }
601 1
            if ($removed) {
602 1
                $this->_eventWildcards[$name] = array_values($this->_eventWildcards[$name]);
603
                // remove empty wildcards to save future redundant regex checks:
604 1
                if (empty($this->_eventWildcards[$name])) {
605 1
                    unset($this->_eventWildcards[$name]);
606
                }
607
            }
608
        }
609
610 2
        return $removed;
611
    }
612
613
    /**
614
     * Triggers an event.
615
     *
616
     * This method represents the happening of an event. It invokes all attached handlers for the event
617
     * including class-level handlers.
618
     *
619
     * @param string $name the event name
620
     * @param Event|null $event the event instance. If not set, a default [[Event]] object will be created.
621
     */
622 563
    public function trigger($name, ?Event $event = null)
623
    {
624 563
        $this->ensureBehaviors();
625
626 563
        $eventHandlers = [];
627 563
        foreach ($this->_eventWildcards as $wildcard => $handlers) {
628 1
            if (StringHelper::matchWildcard($wildcard, $name)) {
629 1
                $eventHandlers[] = $handlers;
630
            }
631
        }
632 563
        if (!empty($this->_events[$name])) {
633 154
            $eventHandlers[] = $this->_events[$name];
634
        }
635
636 563
        if (!empty($eventHandlers)) {
637 155
            $eventHandlers = call_user_func_array('array_merge', $eventHandlers);
638 155
            if ($event === null) {
639 29
                $event = new Event();
640
            }
641 155
            if ($event->sender === null) {
642 155
                $event->sender = $this;
643
            }
644 155
            $event->handled = false;
645 155
            $event->name = $name;
646 155
            foreach ($eventHandlers as $handler) {
647 155
                $event->data = $handler[1];
648 155
                call_user_func($handler[0], $event);
649
                // stop further handling if the event is handled
650 149
                if ($event->handled) {
651 2
                    return;
652
                }
653
            }
654
        }
655
656
        // invoke class-level attached handlers
657 562
        Event::trigger($this, $name, $event);
658
    }
659
660
    /**
661
     * Returns the named behavior object.
662
     * @param string $name the behavior name
663
     * @return Behavior|null the behavior object, or null if the behavior does not exist
664
     */
665 32
    public function getBehavior($name)
666
    {
667 32
        $this->ensureBehaviors();
668 32
        return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
669
    }
670
671
    /**
672
     * Returns all behaviors attached to this component.
673
     * @return Behavior[] list of behaviors attached to this component
674
     */
675 1
    public function getBehaviors()
676
    {
677 1
        $this->ensureBehaviors();
678 1
        return $this->_behaviors;
679
    }
680
681
    /**
682
     * Attaches a behavior to this component.
683
     * This method will create the behavior object based on the given
684
     * configuration. After that, the behavior object will be attached to
685
     * this component by calling the [[Behavior::attach()]] method.
686
     * @param string $name the name of the behavior.
687
     * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
688
     *
689
     *  - a [[Behavior]] object
690
     *  - a string specifying the behavior class
691
     *  - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
692
     *
693
     * @return Behavior the behavior object
694
     * @see detachBehavior()
695
     */
696 15
    public function attachBehavior($name, $behavior)
697
    {
698 15
        $this->ensureBehaviors();
699 15
        return $this->attachBehaviorInternal($name, $behavior);
700
    }
701
702
    /**
703
     * Attaches a list of behaviors to the component.
704
     * Each behavior is indexed by its name and should be a [[Behavior]] object,
705
     * a string specifying the behavior class, or an configuration array for creating the behavior.
706
     * @param array $behaviors list of behaviors to be attached to the component
707
     * @see attachBehavior()
708
     */
709 3
    public function attachBehaviors($behaviors)
710
    {
711 3
        $this->ensureBehaviors();
712 3
        foreach ($behaviors as $name => $behavior) {
713 3
            $this->attachBehaviorInternal($name, $behavior);
714
        }
715
    }
716
717
    /**
718
     * Detaches a behavior from the component.
719
     * The behavior's [[Behavior::detach()]] method will be invoked.
720
     * @param string $name the behavior's name.
721
     * @return Behavior|null the detached behavior. Null if the behavior does not exist.
722
     */
723 4
    public function detachBehavior($name)
724
    {
725 4
        $this->ensureBehaviors();
726 4
        if (isset($this->_behaviors[$name])) {
727 4
            $behavior = $this->_behaviors[$name];
728 4
            unset($this->_behaviors[$name]);
729 4
            $behavior->detach();
730 4
            return $behavior;
731
        }
732
733 1
        return null;
734
    }
735
736
    /**
737
     * Detaches all behaviors from the component.
738
     */
739 1
    public function detachBehaviors()
740
    {
741 1
        $this->ensureBehaviors();
742 1
        foreach ($this->_behaviors as $name => $behavior) {
743 1
            $this->detachBehavior($name);
744
        }
745
    }
746
747
    /**
748
     * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
749
     */
750 613
    public function ensureBehaviors()
751
    {
752 613
        if ($this->_behaviors === null) {
753 613
            $this->_behaviors = [];
754 613
            foreach ($this->behaviors() as $name => $behavior) {
755 135
                $this->attachBehaviorInternal($name, $behavior);
756
            }
757
        }
758
    }
759
760
    /**
761
     * Attaches a behavior to this component.
762
     * @param string|int $name the name of the behavior. If this is an integer, it means the behavior
763
     * is an anonymous one. Otherwise, the behavior is a named one and any existing behavior with the same name
764
     * will be detached first.
765
     * @param string|array|Behavior $behavior the behavior to be attached
766
     * @return Behavior the attached behavior.
767
     */
768 150
    private function attachBehaviorInternal($name, $behavior)
769
    {
770 150
        if (!($behavior instanceof Behavior)) {
771 135
            $behavior = Yii::createObject($behavior);
772
        }
773 150
        if (is_int($name)) {
774 8
            $behavior->attach($this);
775 8
            $this->_behaviors[] = $behavior;
776
        } else {
777 142
            if (isset($this->_behaviors[$name])) {
778 4
                $this->_behaviors[$name]->detach();
779
            }
780 142
            $behavior->attach($this);
781 142
            $this->_behaviors[$name] = $behavior;
782
        }
783
784 150
        return $behavior;
785
    }
786
}
787