Component.php 17.5 KB
Newer Older
Qiang Xue committed
1
<?php
w  
Qiang Xue committed
2 3
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
w  
Qiang Xue committed
5 6
 * @license http://www.yiiframework.com/license/
 */
Qiang Xue committed
7 8 9

namespace yii\base;

Qiang Xue committed
10 11
use Yii;

w  
Qiang Xue committed
12
/**
Qiang Xue committed
13
 * @include @yii/base/Component.md
w  
Qiang Xue committed
14
 * @author Qiang Xue <qiang.xue@gmail.com>
w  
Qiang Xue committed
15
 * @since 2.0
w  
Qiang Xue committed
16
 */
Qiang Xue committed
17
class Component extends Object
Qiang Xue committed
18
{
Qiang Xue committed
19
	/**
Qiang Xue committed
20
	 * @var array the attached event handlers (event name => handlers)
Qiang Xue committed
21
	 */
Qiang Xue committed
22
	private $_events;
Qiang Xue committed
23 24 25
	/**
	 * @var Behavior[] the attached behaviors (behavior name => behavior)
	 */
Qiang Xue committed
26
	private $_behaviors;
Qiang Xue committed
27 28

	/**
w  
Qiang Xue committed
29 30 31 32 33 34 35 36 37 38
	 * Returns the value of a component property.
	 * This method will check in the following order and act accordingly:
	 *
	 *  - a property defined by a getter: return the getter result
	 *  - a property of a behavior: return the behavior property value
	 *
	 * Do not call this method directly as it is a PHP magic method that
	 * will be implicitly called when executing `$value = $component->property;`.
	 * @param string $name the property name
	 * @return mixed the property value, event handlers attached to the event,
Qiang Xue committed
39
	 * the behavior, or the value of a behavior's property
Qiang Xue committed
40
	 * @throws UnknownPropertyException if the property is not defined
Qiang Xue committed
41 42 43 44 45
	 * @see __set
	 */
	public function __get($name)
	{
		$getter = 'get' . $name;
46 47
		if (method_exists($this, $getter)) {
			// read property, e.g. getName()
Qiang Xue committed
48
			return $this->$getter();
49 50
		} else {
			// behavior property
Qiang Xue committed
51
			$this->ensureBehaviors();
Qiang Xue committed
52
			foreach ($this->_behaviors as $behavior) {
Qiang Xue committed
53
				if ($behavior->canGetProperty($name)) {
Qiang Xue committed
54
					return $behavior->$name;
Qiang Xue committed
55
				}
Qiang Xue committed
56 57
			}
		}
Qiang Xue committed
58
		throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
Qiang Xue committed
59 60 61
	}

	/**
62
	 * Sets the value of a component property.
w  
Qiang Xue committed
63 64 65
	 * This method will check in the following order and act accordingly:
	 *
	 *  - a property defined by a setter: set the property value
Qiang Xue committed
66 67
	 *  - an event in the format of "on xyz": attach the handler to the event "xyz"
	 *  - a behavior in the format of "as xyz": attach the behavior named as "xyz"
w  
Qiang Xue committed
68 69 70 71
	 *  - a property of a behavior: set the behavior property value
	 *
	 * Do not call this method directly as it is a PHP magic method that
	 * will be implicitly called when executing `$component->property = $value;`.
Qiang Xue committed
72
	 * @param string $name the property name or the event name
w  
Qiang Xue committed
73
	 * @param mixed $value the property value
74 75
	 * @throws UnknownPropertyException if the property is not defined
	 * @throws InvalidCallException if the property is read-only.
Qiang Xue committed
76 77
	 * @see __get
	 */
Qiang Xue committed
78
	public function __set($name, $value)
Qiang Xue committed
79
	{
Qiang Xue committed
80
		$setter = 'set' . $name;
Qiang Xue committed
81 82
		if (method_exists($this, $setter)) {
			// set property
Qiang Xue committed
83 84
			$this->$setter($value);
			return;
Qiang Xue committed
85 86
		} elseif (strncmp($name, 'on ', 3) === 0) {
			// on event: attach event handler
Qiang Xue committed
87
			$this->on(trim(substr($name, 3)), $value);
Qiang Xue committed
88
			return;
Qiang Xue committed
89 90 91
		} elseif (strncmp($name, 'as ', 3) === 0) {
			// as behavior: attach behavior
			$name = trim(substr($name, 3));
Qiang Xue committed
92
			$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
93
			return;
94 95
		} else {
			// behavior property
Qiang Xue committed
96
			$this->ensureBehaviors();
Qiang Xue committed
97
			foreach ($this->_behaviors as $behavior) {
Qiang Xue committed
98
				if ($behavior->canSetProperty($name)) {
Qiang Xue committed
99 100
					$behavior->$name = $value;
					return;
Qiang Xue committed
101
				}
Qiang Xue committed
102 103
			}
		}
Qiang Xue committed
104
		if (method_exists($this, 'get' . $name)) {
Qiang Xue committed
105
			throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
Qiang Xue committed
106
		} else {
Qiang Xue committed
107
			throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
Qiang Xue committed
108
		}
Qiang Xue committed
109 110 111 112
	}

	/**
	 * Checks if a property value is null.
w  
Qiang Xue committed
113 114 115 116 117 118 119
	 * This method will check in the following order and act accordingly:
	 *
	 *  - a property defined by a setter: return whether the property value is null
	 *  - a property of a behavior: return whether the property value is null
	 *
	 * Do not call this method directly as it is a PHP magic method that
	 * will be implicitly called when executing `isset($component->property)`.
Qiang Xue committed
120
	 * @param string $name the property name or the event name
w  
Qiang Xue committed
121
	 * @return boolean whether the named property is null
Qiang Xue committed
122 123 124
	 */
	public function __isset($name)
	{
Qiang Xue committed
125
		$getter = 'get' . $name;
126
		if (method_exists($this, $getter)) {
Qiang Xue committed
127
			return $this->$getter() !== null;
128 129
		} else {
			// behavior property
Qiang Xue committed
130
			$this->ensureBehaviors();
Qiang Xue committed
131
			foreach ($this->_behaviors as $behavior) {
Qiang Xue committed
132
				if ($behavior->canGetProperty($name)) {
Qiang Xue committed
133
					return $behavior->$name !== null;
Qiang Xue committed
134
				}
Qiang Xue committed
135 136 137 138 139 140 141
			}
		}
		return false;
	}

	/**
	 * Sets a component property to be null.
w  
Qiang Xue committed
142 143 144 145 146 147 148 149
	 * This method will check in the following order and act accordingly:
	 *
	 *  - a property defined by a setter: set the property value to be null
	 *  - a property of a behavior: set the property value to be null
	 *
	 * Do not call this method directly as it is a PHP magic method that
	 * will be implicitly called when executing `unset($component->property)`.
	 * @param string $name the property name
150
	 * @throws InvalidCallException if the property is read only.
Qiang Xue committed
151 152 153
	 */
	public function __unset($name)
	{
Qiang Xue committed
154
		$setter = 'set' . $name;
155
		if (method_exists($this, $setter)) {
Qiang Xue committed
156 157
			$this->$setter(null);
			return;
158 159
		} else {
			// behavior property
Qiang Xue committed
160
			$this->ensureBehaviors();
Qiang Xue committed
161
			foreach ($this->_behaviors as $behavior) {
Qiang Xue committed
162
				if ($behavior->canSetProperty($name)) {
Qiang Xue committed
163 164
					$behavior->$name = null;
					return;
Qiang Xue committed
165
				}
Qiang Xue committed
166
			}
Qiang Xue committed
167 168
		}
		if (method_exists($this, 'get' . $name)) {
Qiang Xue committed
169
			throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '.' . $name);
w  
Qiang Xue committed
170
		}
Qiang Xue committed
171 172 173 174
	}

	/**
	 * Calls the named method which is not a class method.
w  
Qiang Xue committed
175 176 177 178 179 180 181
	 * If the name refers to a component property whose value is
	 * an anonymous function, the method will execute the function.
	 * Otherwise, it will check if any attached behavior has
	 * the named method and will execute it if available.
	 *
	 * Do not call this method directly as it is a PHP magic method that
	 * will be implicitly called when an unknown method is being invoked.
Qiang Xue committed
182
	 * @param string $name the method name
Qiang Xue committed
183
	 * @param array $params method parameters
Qiang Xue committed
184
	 * @return mixed the method return value
Qiang Xue committed
185
	 * @throws UnknownMethodException when calling unknown method
Qiang Xue committed
186
	 */
Qiang Xue committed
187
	public function __call($name, $params)
Qiang Xue committed
188
	{
Qiang Xue committed
189 190 191
		$getter = 'get' . $name;
		if (method_exists($this, $getter)) {
			$func = $this->$getter();
Qiang Xue committed
192
			if ($func instanceof \Closure) {
Qiang Xue committed
193
				return call_user_func_array($func, $params);
Qiang Xue committed
194 195 196
			}
		}

Qiang Xue committed
197
		$this->ensureBehaviors();
Qiang Xue committed
198
		foreach ($this->_behaviors as $object) {
Qiang Xue committed
199
			if (method_exists($object, $name)) {
Qiang Xue committed
200
				return call_user_func_array(array($object, $name), $params);
w  
Qiang Xue committed
201
			}
Qiang Xue committed
202
		}
Qiang Xue committed
203

Qiang Xue committed
204
		throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
Qiang Xue committed
205 206
	}

Qiang Xue committed
207 208 209 210 211 212
	/**
	 * This method is called after the object is created by cloning an existing one.
	 * It removes all behaviors because they are attached to the old object.
	 */
	public function __clone()
	{
Qiang Xue committed
213 214
		$this->_events = null;
		$this->_behaviors = null;
Qiang Xue committed
215 216
	}

Qiang Xue committed
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
	/**
	 * Returns a value indicating whether a property is defined for this component.
	 * A property is defined if:
	 *
	 * - the class has a getter or setter method associated with the specified name
	 *   (in this case, property name is case-insensitive);
	 * - the class has a member variable with the specified name (when `$checkVar` is true);
	 * - an attached behavior has a property of the given name (when `$checkBehavior` is true).
	 *
	 * @param string $name the property name
	 * @param boolean $checkVar whether to treat member variables as properties
	 * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component
	 * @return boolean whether the property is defined
	 * @see canGetProperty
	 * @see canSetProperty
	 */
	public function hasProperty($name, $checkVar = true, $checkBehavior = true)
	{
		return $this->canGetProperty($name, $checkVar, $checkBehavior) || $this->canSetProperty($name, $checkVar, $checkBehavior);
	}

	/**
	 * Returns a value indicating whether a property can be read.
	 * A property can be read if:
	 *
	 * - the class has a getter method associated with the specified name
	 *   (in this case, property name is case-insensitive);
	 * - the class has a member variable with the specified name (when `$checkVar` is true);
	 * - an attached behavior has a readable property of the given name (when `$checkBehavior` is true).
	 *
	 * @param string $name the property name
	 * @param boolean $checkVar whether to treat member variables as properties
	 * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component
	 * @return boolean whether the property can be read
	 * @see canSetProperty
	 */
	public function canGetProperty($name, $checkVar = true, $checkBehavior = true)
	{
		if (method_exists($this, 'get' . $name) || $checkVar && property_exists($this, $name)) {
			return true;
		} else {
			$this->ensureBehaviors();
Qiang Xue committed
259
			foreach ($this->_behaviors as $behavior) {
Qiang Xue committed
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
				if ($behavior->canGetProperty($name, $checkVar)) {
					return true;
				}
			}
			return false;
		}
	}

	/**
	 * Returns a value indicating whether a property can be set.
	 * A property can be written if:
	 *
	 * - the class has a setter method associated with the specified name
	 *   (in this case, property name is case-insensitive);
	 * - the class has a member variable with the specified name (when `$checkVar` is true);
	 * - an attached behavior has a writable property of the given name (when `$checkBehavior` is true).
	 *
	 * @param string $name the property name
	 * @param boolean $checkVar whether to treat member variables as properties
	 * @param boolean $checkBehavior whether to treat behaviors' properties as properties of this component
	 * @return boolean whether the property can be written
	 * @see canGetProperty
	 */
	public function canSetProperty($name, $checkVar = true, $checkBehavior = true)
	{
		if (method_exists($this, 'set' . $name) || $checkVar && property_exists($this, $name)) {
			return true;
		} else {
			$this->ensureBehaviors();
Qiang Xue committed
289
			foreach ($this->_behaviors as $behavior) {
Qiang Xue committed
290 291 292 293 294 295 296 297
				if ($behavior->canSetProperty($name, $checkVar)) {
					return true;
				}
			}
			return false;
		}
	}

Qiang Xue committed
298 299 300 301 302 303 304 305 306 307 308
	/**
	 * Returns a list of behaviors that this component should behave as.
	 *
	 * Child classes may override this method to specify the behaviors they want to behave as.
	 *
	 * The return value of this method should be an array of behavior objects or configurations
	 * indexed by behavior names. A behavior configuration can be either a string specifying
	 * the behavior class or an array of the following structure:
	 *
	 * ~~~
	 * 'behaviorName' => array(
Qiang Xue committed
309 310 311
	 *     'class' => 'BehaviorClass',
	 *     'property1' => 'value1',
	 *     'property2' => 'value2',
Qiang Xue committed
312 313 314
	 * )
	 * ~~~
	 *
Qiang Xue committed
315 316 317 318
	 * Note that a behavior class must extend from [[Behavior]]. Behavior names can be strings
	 * or integers. If the former, they uniquely identify the behaviors. If the latter, the corresponding
	 * behaviors are anonymous and their properties and methods will NOT be made available via the component
	 * (however, the behaviors can still respond to the component's events).
Qiang Xue committed
319
	 *
Qiang Xue committed
320
	 * Behaviors declared in this method will be attached to the component automatically (on demand).
Qiang Xue committed
321 322 323 324 325 326
	 *
	 * @return array the behavior configurations.
	 */
	public function behaviors()
	{
		return array();
Qiang Xue committed
327 328 329
	}

	/**
Qiang Xue committed
330
	 * Returns a value indicating whether there is any handler attached to the named event.
Qiang Xue committed
331
	 * @param string $name the event name
w  
Qiang Xue committed
332
	 * @return boolean whether there is any handler attached to the event.
Qiang Xue committed
333
	 */
w  
Qiang Xue committed
334
	public function hasEventHandlers($name)
Qiang Xue committed
335
	{
Qiang Xue committed
336
		$this->ensureBehaviors();
Qiang Xue committed
337
		return !empty($this->_events[$name]);
Qiang Xue committed
338 339 340 341 342
	}

	/**
	 * Attaches an event handler to an event.
	 *
w  
Qiang Xue committed
343 344 345
	 * An event handler must be a valid PHP callback. The followings are
	 * some examples:
	 *
w  
Qiang Xue committed
346
	 * ~~~
Qiang Xue committed
347
	 * function ($event) { ... }         // anonymous function
Qiang Xue committed
348 349 350
	 * array($object, 'handleClick')    // $object->handleClick()
	 * array('Page', 'handleClick')     // Page::handleClick()
	 * 'handleClick'                    // global function handleClick()
w  
Qiang Xue committed
351
	 * ~~~
Qiang Xue committed
352 353 354
	 *
	 * An event handler must be defined with the following signature,
	 *
w  
Qiang Xue committed
355
	 * ~~~
Qiang Xue committed
356
	 * function ($event)
w  
Qiang Xue committed
357
	 * ~~~
Qiang Xue committed
358
	 *
w  
Qiang Xue committed
359
	 * where `$event` is an [[Event]] object which includes parameters associated with the event.
Qiang Xue committed
360 361
	 *
	 * @param string $name the event name
Qiang Xue committed
362 363 364
	 * @param callback $handler the event handler
	 * @param mixed $data the data to be passed to the event handler when the event is triggered.
	 * When the event handler is invoked, this data can be accessed via [[Event::data]].
Qiang Xue committed
365
	 * @see off()
Qiang Xue committed
366
	 */
Qiang Xue committed
367
	public function on($name, $handler, $data = null)
Qiang Xue committed
368
	{
Qiang Xue committed
369 370
		$this->ensureBehaviors();
		$this->_events[$name][] = array($handler, $data);
Qiang Xue committed
371 372 373
	}

	/**
Qiang Xue committed
374 375
	 * Detaches an existing event handler from this component.
	 * This method is the opposite of [[on()]].
Qiang Xue committed
376
	 * @param string $name event name
Qiang Xue committed
377 378
	 * @param callback $handler the event handler to be removed.
	 * If it is null, all handlers attached to the named event will be removed.
Qiang Xue committed
379
	 * @return boolean if a handler is found and detached
Qiang Xue committed
380
	 * @see on()
Qiang Xue committed
381
	 */
Qiang Xue committed
382
	public function off($name, $handler = null)
Qiang Xue committed
383
	{
Qiang Xue committed
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
		$this->ensureBehaviors();
		if (isset($this->_events[$name])) {
			if ($handler === null) {
				$this->_events[$name] = array();
			} else {
				$removed = false;
				foreach ($this->_events[$name] as $i => $event) {
					if ($event[0] === $handler) {
						unset($this->_events[$name][$i]);
						$removed = true;
					}
				}
				if ($removed) {
					$this->_events[$name] = array_values($this->_events[$name]);
				}
				return $removed;
			}
		}
		return false;
Qiang Xue committed
403 404 405
	}

	/**
Qiang Xue committed
406
	 * Triggers an event.
Qiang Xue committed
407 408 409
	 * This method represents the happening of an event. It invokes
	 * all attached handlers for the event.
	 * @param string $name the event name
Qiang Xue committed
410
	 * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
Qiang Xue committed
411
	 */
Qiang Xue committed
412
	public function trigger($name, $event = null)
Qiang Xue committed
413
	{
Qiang Xue committed
414
		$this->ensureBehaviors();
Qiang Xue committed
415
		if (!empty($this->_events[$name])) {
Qiang Xue committed
416
			if ($event === null) {
Qiang Xue committed
417 418 419 420
				$event = new Event;
			}
			if ($event->sender === null) {
				$event->sender = $this;
Qiang Xue committed
421
			}
Qiang Xue committed
422 423
			$event->handled = false;
			$event->name = $name;
Qiang Xue committed
424 425 426
			foreach ($this->_events[$name] as $handler) {
				$event->data = $handler[1];
				call_user_func($handler[0], $event);
w  
Qiang Xue committed
427 428
				// stop further handling if the event is handled
				if ($event instanceof Event && $event->handled) {
Qiang Xue committed
429
					return;
w  
Qiang Xue committed
430 431 432 433 434 435 436
				}
			}
		}
	}

	/**
	 * Returns the named behavior object.
Qiang Xue committed
437
	 * @param string $name the behavior name
w  
Qiang Xue committed
438 439
	 * @return Behavior the behavior object, or null if the behavior does not exist
	 */
Qiang Xue committed
440
	public function getBehavior($name)
w  
Qiang Xue committed
441
	{
Qiang Xue committed
442
		$this->ensureBehaviors();
Qiang Xue committed
443
		return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
Qiang Xue committed
444 445 446 447 448 449 450 451 452
	}

	/**
	 * Returns all behaviors attached to this component.
	 * @return Behavior[] list of behaviors attached to this component
	 */
	public function getBehaviors()
	{
		$this->ensureBehaviors();
Qiang Xue committed
453
		return $this->_behaviors;
w  
Qiang Xue committed
454 455 456 457 458 459
	}

	/**
	 * Attaches a behavior to this component.
	 * This method will create the behavior object based on the given
	 * configuration. After that, the behavior object will be attached to
Qiang Xue committed
460
	 * this component by calling the [[Behavior::attach()]] method.
Qiang Xue committed
461
	 * @param string $name the name of the behavior.
Qiang Xue committed
462
	 * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
w  
Qiang Xue committed
463 464 465
	 *
	 *  - a [[Behavior]] object
	 *  - a string specifying the behavior class
Qiang Xue committed
466
	 *  - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
w  
Qiang Xue committed
467 468 469 470 471 472
	 *
	 * @return Behavior the behavior object
	 * @see detachBehavior
	 */
	public function attachBehavior($name, $behavior)
	{
Qiang Xue committed
473 474
		$this->ensureBehaviors();
		return $this->attachBehaviorInternal($name, $behavior);
w  
Qiang Xue committed
475 476 477 478 479
	}

	/**
	 * Attaches a list of behaviors to the component.
	 * Each behavior is indexed by its name and should be a [[Behavior]] object,
Qiang Xue committed
480
	 * a string specifying the behavior class, or an configuration array for creating the behavior.
w  
Qiang Xue committed
481 482 483 484 485
	 * @param array $behaviors list of behaviors to be attached to the component
	 * @see attachBehavior
	 */
	public function attachBehaviors($behaviors)
	{
Qiang Xue committed
486
		$this->ensureBehaviors();
w  
Qiang Xue committed
487
		foreach ($behaviors as $name => $behavior) {
Qiang Xue committed
488
			$this->attachBehaviorInternal($name, $behavior);
w  
Qiang Xue committed
489 490 491 492 493
		}
	}

	/**
	 * Detaches a behavior from the component.
Qiang Xue committed
494
	 * The behavior's [[Behavior::detach()]] method will be invoked.
w  
Qiang Xue committed
495 496 497 498 499
	 * @param string $name the behavior's name.
	 * @return Behavior the detached behavior. Null if the behavior does not exist.
	 */
	public function detachBehavior($name)
	{
500
		$this->ensureBehaviors();
Qiang Xue committed
501 502 503
		if (isset($this->_behaviors[$name])) {
			$behavior = $this->_behaviors[$name];
			unset($this->_behaviors[$name]);
504
			$behavior->detach();
w  
Qiang Xue committed
505
			return $behavior;
Qiang Xue committed
506 507
		} else {
			return null;
w  
Qiang Xue committed
508 509 510 511 512 513 514 515
		}
	}

	/**
	 * Detaches all behaviors from the component.
	 */
	public function detachBehaviors()
	{
516
		$this->ensureBehaviors();
Qiang Xue committed
517 518
		if ($this->_behaviors !== null) {
			foreach ($this->_behaviors as $name => $behavior) {
w  
Qiang Xue committed
519 520
				$this->detachBehavior($name);
			}
Qiang Xue committed
521
		}
Qiang Xue committed
522
		$this->_behaviors = array();
Qiang Xue committed
523 524 525 526 527 528 529
	}

	/**
	 * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
	 */
	public function ensureBehaviors()
	{
Qiang Xue committed
530 531
		if ($this->_behaviors === null) {
			$this->_behaviors = array();
Qiang Xue committed
532
			foreach ($this->behaviors() as $name => $behavior) {
Qiang Xue committed
533
				$this->attachBehaviorInternal($name, $behavior);
Qiang Xue committed
534
			}
w  
Qiang Xue committed
535 536
		}
	}
Qiang Xue committed
537 538 539

	/**
	 * Attaches a behavior to this component.
Qiang Xue committed
540
	 * @param string $name the name of the behavior.
Qiang Xue committed
541 542 543 544 545 546
	 * @param string|array|Behavior $behavior the behavior to be attached
	 * @return Behavior the attached behavior.
	 */
	private function attachBehaviorInternal($name, $behavior)
	{
		if (!($behavior instanceof Behavior)) {
Qiang Xue committed
547
			$behavior = Yii::createObject($behavior);
Qiang Xue committed
548
		}
Qiang Xue committed
549 550
		if (isset($this->_behaviors[$name])) {
			$this->_behaviors[$name]->detach();
Qiang Xue committed
551
		}
Qiang Xue committed
552
		$behavior->attach($this);
Qiang Xue committed
553
		return $this->_behaviors[$name] = $behavior;
Qiang Xue committed
554
	}
Qiang Xue committed
555
}