Component.php 17.3 KB
Newer Older
Qiang Xue committed
1
<?php
w  
Qiang Xue committed
2 3 4 5
/**
 * Component class file.
 *
 * @link http://www.yiiframework.com/
Qiang Xue committed
6
 * @copyright Copyright &copy; 2008 Yii Software LLC
w  
Qiang Xue committed
7 8
 * @license http://www.yiiframework.com/license/
 */
Qiang Xue committed
9 10 11

namespace yii\base;

w  
Qiang Xue committed
12
/**
Qiang Xue committed
13
 * Component is the base class that provides the *property*, *event* and *behavior* features.
w  
Qiang Xue committed
14
 *
Qiang Xue committed
15
 * @include @yii/base/Component.md
w  
Qiang Xue committed
16
 *
Qiang Xue committed
17 18
 * @property Behavior[] behaviors list of behaviors currently attached to this component
 *
w  
Qiang Xue committed
19
 * @author Qiang Xue <qiang.xue@gmail.com>
w  
Qiang Xue committed
20
 * @since 2.0
w  
Qiang Xue committed
21
 */
Qiang Xue committed
22
class Component extends \yii\base\Object
Qiang Xue committed
23
{
Qiang Xue committed
24 25 26
	/**
	 * @var Vector[] the attached event handlers (event name => handlers)
	 */
Qiang Xue committed
27
	private $_e;
Qiang Xue committed
28 29 30
	/**
	 * @var Behavior[] the attached behaviors (behavior name => behavior)
	 */
Qiang Xue committed
31
	private $_b;
Qiang Xue committed
32 33

	/**
w  
Qiang Xue committed
34 35 36 37 38 39 40 41 42 43
	 * 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
44
	 * the behavior, or the value of a behavior's property
Qiang Xue committed
45
	 * @throws BadPropertyException if the property is not defined
Qiang Xue committed
46 47 48 49 50
	 * @see __set
	 */
	public function __get($name)
	{
		$getter = 'get' . $name;
51 52
		if (method_exists($this, $getter)) {
			// read property, e.g. getName()
Qiang Xue committed
53
			return $this->$getter();
54 55
		} else {
			// behavior property
Qiang Xue committed
56
			$this->ensureBehaviors();
Qiang Xue committed
57 58
			foreach ($this->_b as $behavior) {
				if ($behavior->canGetProperty($name)) {
Qiang Xue committed
59
					return $behavior->$name;
Qiang Xue committed
60
				}
Qiang Xue committed
61 62
			}
		}
Qiang Xue committed
63
		throw new BadPropertyException('Getting unknown property: ' . get_class($this) . '.' . $name);
Qiang Xue committed
64 65 66
	}

	/**
67
	 * Sets the value of a component property.
w  
Qiang Xue committed
68 69 70
	 * This method will check in the following order and act accordingly:
	 *
	 *  - a property defined by a setter: set the property value
Qiang Xue committed
71 72
	 *  - 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
73 74 75 76
	 *  - 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
77
	 * @param string $name the property name or the event name
w  
Qiang Xue committed
78
	 * @param mixed $value the property value
Qiang Xue committed
79
	 * @throws BadPropertyException if the property is not defined or read-only.
Qiang Xue committed
80 81
	 * @see __get
	 */
Qiang Xue committed
82
	public function __set($name, $value)
Qiang Xue committed
83
	{
Qiang Xue committed
84
		$setter = 'set' . $name;
Qiang Xue committed
85 86
		if (method_exists($this, $setter)) {
			// set property
Qiang Xue committed
87 88
			$this->$setter($value);
			return;
Qiang Xue committed
89 90
		} elseif (strncmp($name, 'on ', 3) === 0) {
			// on event: attach event handler
Qiang Xue committed
91
			$name = trim(substr($name, 3));
Qiang Xue committed
92 93
			$this->getEventHandlers($name)->add($value);
			return;
Qiang Xue committed
94 95 96
		} elseif (strncmp($name, 'as ', 3) === 0) {
			// as behavior: attach behavior
			$name = trim(substr($name, 3));
97
			$this->attachBehavior($name, $value instanceof Behavior ? $value : \Yii::createObject($value));
98 99
		} else {
			// behavior property
Qiang Xue committed
100
			$this->ensureBehaviors();
Qiang Xue committed
101 102
			foreach ($this->_b as $behavior) {
				if ($behavior->canSetProperty($name)) {
Qiang Xue committed
103 104
					$behavior->$name = $value;
					return;
Qiang Xue committed
105
				}
Qiang Xue committed
106 107
			}
		}
Qiang Xue committed
108
		if (method_exists($this, 'get' . $name)) {
Qiang Xue committed
109
			throw new BadPropertyException('Setting read-only property: ' . get_class($this) . '.' . $name);
Qiang Xue committed
110
		} else {
Qiang Xue committed
111
			throw new BadPropertyException('Setting unknown property: ' . get_class($this) . '.' . $name);
Qiang Xue committed
112
		}
Qiang Xue committed
113 114 115 116
	}

	/**
	 * Checks if a property value is null.
w  
Qiang Xue committed
117 118 119 120 121 122 123
	 * 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
124
	 * @param string $name the property name or the event name
w  
Qiang Xue committed
125
	 * @return boolean whether the named property is null
Qiang Xue committed
126 127 128
	 */
	public function __isset($name)
	{
Qiang Xue committed
129
		$getter = 'get' . $name;
130
		if (method_exists($this, $getter)) {
Qiang Xue committed
131
			return $this->$getter() !== null;
132 133
		} else {
			// behavior property
Qiang Xue committed
134
			$this->ensureBehaviors();
Qiang Xue committed
135 136
			foreach ($this->_b as $behavior) {
				if ($behavior->canGetProperty($name)) {
Qiang Xue committed
137
					return $behavior->$name !== null;
Qiang Xue committed
138
				}
Qiang Xue committed
139 140 141 142 143 144 145
			}
		}
		return false;
	}

	/**
	 * Sets a component property to be null.
w  
Qiang Xue committed
146 147 148 149 150 151 152 153
	 * 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
Qiang Xue committed
154
	 * @throws BadPropertyException if the property is read only.
Qiang Xue committed
155 156 157
	 */
	public function __unset($name)
	{
Qiang Xue committed
158
		$setter = 'set' . $name;
159
		if (method_exists($this, $setter)) {
Qiang Xue committed
160 161
			$this->$setter(null);
			return;
162 163
		} else {
			// behavior property
Qiang Xue committed
164
			$this->ensureBehaviors();
Qiang Xue committed
165 166
			foreach ($this->_b as $behavior) {
				if ($behavior->canSetProperty($name)) {
Qiang Xue committed
167 168
					$behavior->$name = null;
					return;
Qiang Xue committed
169
				}
Qiang Xue committed
170
			}
Qiang Xue committed
171 172
		}
		if (method_exists($this, 'get' . $name)) {
Qiang Xue committed
173
			throw new BadPropertyException('Unsetting read-only property: ' . get_class($this) . '.' . $name);
w  
Qiang Xue committed
174
		}
Qiang Xue committed
175 176 177 178
	}

	/**
	 * Calls the named method which is not a class method.
w  
Qiang Xue committed
179 180 181 182 183 184 185
	 * 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
186
	 * @param string $name the method name
Qiang Xue committed
187
	 * @param array $params method parameters
Qiang Xue committed
188
	 * @return mixed the method return value
Qiang Xue committed
189
	 * @throws BadMethodException when calling unknown method
Qiang Xue committed
190
	 */
Qiang Xue committed
191
	public function __call($name, $params)
Qiang Xue committed
192
	{
Qiang Xue committed
193 194 195
		$getter = 'get' . $name;
		if (method_exists($this, $getter)) {
			$func = $this->$getter();
Qiang Xue committed
196
			if ($func instanceof \Closure) {
Qiang Xue committed
197
				return call_user_func_array($func, $params);
Qiang Xue committed
198 199 200
			}
		}

Qiang Xue committed
201
		$this->ensureBehaviors();
Qiang Xue committed
202 203
		foreach ($this->_b as $object) {
			if (method_exists($object, $name)) {
Qiang Xue committed
204
				return call_user_func_array(array($object, $name), $params);
w  
Qiang Xue committed
205
			}
Qiang Xue committed
206
		}
Qiang Xue committed
207

Qiang Xue committed
208
		throw new BadMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
Qiang Xue committed
209 210
	}

Qiang Xue committed
211 212 213 214 215 216
	/**
	 * 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
217
		$this->_e = null;
Qiang Xue committed
218 219 220
		$this->_b = null;
	}

Qiang Xue committed
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 259 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 289 290 291 292 293 294 295 296 297 298 299 300 301
	/**
	 * 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();
			foreach ($this->_b as $behavior) {
				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();
			foreach ($this->_b as $behavior) {
				if ($behavior->canSetProperty($name, $checkVar)) {
					return true;
				}
			}
			return false;
		}
	}

Qiang Xue committed
302 303 304 305 306 307 308 309 310 311 312
	/**
	 * 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
313 314 315
	 *     'class' => 'BehaviorClass',
	 *     'property1' => 'value1',
	 *     'property2' => 'value2',
Qiang Xue committed
316 317 318
	 * )
	 * ~~~
	 *
Qiang Xue committed
319 320 321 322
	 * 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
323
	 *
Qiang Xue committed
324
	 * Behaviors declared in this method will be attached to the component automatically (on demand).
Qiang Xue committed
325 326 327 328 329 330
	 *
	 * @return array the behavior configurations.
	 */
	public function behaviors()
	{
		return array();
Qiang Xue committed
331 332 333
	}

	/**
Qiang Xue committed
334
	 * Returns a value indicating whether there is any handler attached to the named event.
Qiang Xue committed
335
	 * @param string $name the event name
w  
Qiang Xue committed
336
	 * @return boolean whether there is any handler attached to the event.
Qiang Xue committed
337
	 */
w  
Qiang Xue committed
338
	public function hasEventHandlers($name)
Qiang Xue committed
339
	{
Qiang Xue committed
340
		$this->ensureBehaviors();
w  
Qiang Xue committed
341
		return isset($this->_e[$name]) && $this->_e[$name]->getCount();
Qiang Xue committed
342 343 344 345
	}

	/**
	 * Returns the list of attached event handlers for an event.
w  
Qiang Xue committed
346 347 348
	 * You may manipulate the returned [[Vector]] object by adding or removing handlers.
	 * For example,
	 *
w  
Qiang Xue committed
349
	 * ~~~
w  
Qiang Xue committed
350 351
	 * $component->getEventHandlers($eventName)->insertAt(0, $eventHandler);
	 * ~~~
w  
Qiang Xue committed
352
	 *
Qiang Xue committed
353
	 * @param string $name the event name
w  
Qiang Xue committed
354
	 * @return Vector list of attached event handlers for the event
Qiang Xue committed
355 356 357
	 */
	public function getEventHandlers($name)
	{
Qiang Xue committed
358 359
		if (!isset($this->_e[$name])) {
			$this->_e[$name] = new Vector;
Qiang Xue committed
360
		}
Qiang Xue committed
361 362
		$this->ensureBehaviors();
		return $this->_e[$name];
Qiang Xue committed
363 364 365 366 367
	}

	/**
	 * Attaches an event handler to an event.
	 *
w  
Qiang Xue committed
368 369
	 * This is equivalent to the following code:
	 *
w  
Qiang Xue committed
370
	 * ~~~
w  
Qiang Xue committed
371 372 373 374 375 376
	 * $component->getEventHandlers($eventName)->add($eventHandler);
	 * ~~~
	 *
	 * An event handler must be a valid PHP callback. The followings are
	 * some examples:
	 *
w  
Qiang Xue committed
377
	 * ~~~
Qiang Xue committed
378 379 380 381
	 * function($event) { ... }         // anonymous function
	 * array($object, 'handleClick')    // $object->handleClick()
	 * array('Page', 'handleClick')     // Page::handleClick()
	 * 'handleClick'                    // global function handleClick()
w  
Qiang Xue committed
382
	 * ~~~
Qiang Xue committed
383 384 385
	 *
	 * An event handler must be defined with the following signature,
	 *
w  
Qiang Xue committed
386
	 * ~~~
w  
Qiang Xue committed
387 388
	 * function handlerName($event) {}
	 * ~~~
Qiang Xue committed
389
	 *
w  
Qiang Xue committed
390
	 * where `$event` is an [[Event]] object which includes parameters associated with the event.
Qiang Xue committed
391 392
	 *
	 * @param string $name the event name
Qiang Xue committed
393
	 * @param string|array|\Closure $handler the event handler
Qiang Xue committed
394
	 * @see off()
Qiang Xue committed
395
	 */
Qiang Xue committed
396
	public function on($name, $handler)
Qiang Xue committed
397 398 399 400 401
	{
		$this->getEventHandlers($name)->add($handler);
	}

	/**
Qiang Xue committed
402 403
	 * Detaches an existing event handler from this component.
	 * This method is the opposite of [[on()]].
Qiang Xue committed
404
	 * @param string $name event name
Qiang Xue committed
405
	 * @param string|array|\Closure $handler the event handler to be removed
Qiang Xue committed
406
	 * @return boolean if a handler is found and detached
Qiang Xue committed
407
	 * @see on()
Qiang Xue committed
408
	 */
Qiang Xue committed
409
	public function off($name, $handler)
Qiang Xue committed
410
	{
w  
Qiang Xue committed
411
		return $this->getEventHandlers($name)->remove($handler) !== false;
Qiang Xue committed
412 413 414
	}

	/**
Qiang Xue committed
415
	 * Triggers an event.
Qiang Xue committed
416 417 418
	 * 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
419
	 * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
Qiang Xue committed
420
	 */
Qiang Xue committed
421
	public function trigger($name, $event = null)
Qiang Xue committed
422
	{
Qiang Xue committed
423
		$this->ensureBehaviors();
w  
Qiang Xue committed
424
		if (isset($this->_e[$name])) {
Qiang Xue committed
425 426
			if ($event === null) {
				$event = new Event($this);
Qiang Xue committed
427
			}
Qiang Xue committed
428 429
			$event->handled = false;
			$event->name = $name;
w  
Qiang Xue committed
430
			foreach ($this->_e[$name] as $handler) {
Qiang Xue committed
431
				call_user_func($handler, $event);
w  
Qiang Xue committed
432 433
				// stop further handling if the event is handled
				if ($event instanceof Event && $event->handled) {
Qiang Xue committed
434
					return;
w  
Qiang Xue committed
435 436 437 438 439 440 441
				}
			}
		}
	}

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

	/**
	 * Returns all behaviors attached to this component.
	 * @return Behavior[] list of behaviors attached to this component
	 */
	public function getBehaviors()
	{
		$this->ensureBehaviors();
		return $this->_b;
w  
Qiang Xue committed
459 460 461 462 463 464 465
	}

	/**
	 * 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
	 * this component by calling the [[Behavior::attach]] method.
Qiang Xue committed
466
	 * @param string $name the name of the behavior.
Qiang Xue committed
467
	 * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
w  
Qiang Xue committed
468 469 470
	 *
	 *  - a [[Behavior]] object
	 *  - a string specifying the behavior class
Qiang Xue committed
471
	 *  - an object configuration array that will be passed to [[\Yii::createObject()]] to create the behavior object.
w  
Qiang Xue committed
472 473 474 475 476 477
	 *
	 * @return Behavior the behavior object
	 * @see detachBehavior
	 */
	public function attachBehavior($name, $behavior)
	{
Qiang Xue committed
478 479
		$this->ensureBehaviors();
		return $this->attachBehaviorInternal($name, $behavior);
w  
Qiang Xue committed
480 481 482 483 484
	}

	/**
	 * Attaches a list of behaviors to the component.
	 * Each behavior is indexed by its name and should be a [[Behavior]] object,
Qiang Xue committed
485
	 * a string specifying the behavior class, or an configuration array for creating the behavior.
w  
Qiang Xue committed
486 487 488 489 490
	 * @param array $behaviors list of behaviors to be attached to the component
	 * @see attachBehavior
	 */
	public function attachBehaviors($behaviors)
	{
Qiang Xue committed
491
		$this->ensureBehaviors();
w  
Qiang Xue committed
492
		foreach ($behaviors as $name => $behavior) {
Qiang Xue committed
493
			$this->attachBehaviorInternal($name, $behavior);
w  
Qiang Xue committed
494 495 496 497 498 499 500 501 502 503 504 505 506 507
		}
	}

	/**
	 * Detaches a behavior from the component.
	 * The behavior's [[Behavior::detach]] method will be invoked.
	 * @param string $name the behavior's name.
	 * @return Behavior the detached behavior. Null if the behavior does not exist.
	 */
	public function detachBehavior($name)
	{
		if (isset($this->_b[$name])) {
			$behavior = $this->_b[$name];
			unset($this->_b[$name]);
Qiang Xue committed
508
			$behavior->detach($this);
w  
Qiang Xue committed
509
			return $behavior;
Qiang Xue committed
510 511
		} else {
			return null;
w  
Qiang Xue committed
512 513 514 515 516 517 518 519 520
		}
	}

	/**
	 * Detaches all behaviors from the component.
	 */
	public function detachBehaviors()
	{
		if ($this->_b !== null) {
Qiang Xue committed
521
			foreach ($this->_b as $name => $behavior) {
w  
Qiang Xue committed
522 523
				$this->detachBehavior($name);
			}
Qiang Xue committed
524
		}
Qiang Xue committed
525
		$this->_b = array();
Qiang Xue committed
526 527 528 529 530 531 532 533 534 535
	}

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

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