Component.php 14.7 KB
Newer Older
Qiang Xue committed
1
<?php
w  
Qiang Xue committed
2 3 4 5 6 7 8
/**
 * Component class file.
 *
 * @link http://www.yiiframework.com/
 * @copyright Copyright &copy; 2008-2012 Yii Software LLC
 * @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 17
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
w  
Qiang Xue committed
18
 * @since 2.0
w  
Qiang Xue committed
19
 */
Qiang Xue committed
20
class Component extends \yii\base\Object
Qiang Xue committed
21
{
Qiang Xue committed
22 23 24
	/**
	 * @var Vector[] the attached event handlers (event name => handlers)
	 */
Qiang Xue committed
25
	private $_e;
Qiang Xue committed
26 27 28
	/**
	 * @var Behavior[] the attached behaviors (behavior name => behavior)
	 */
Qiang Xue committed
29
	private $_b;
Qiang Xue committed
30 31

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

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

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

	/**
	 * Sets a component property to be null.
w  
Qiang Xue committed
145 146 147 148 149 150 151 152
	 * 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
153
	 * @throws BadPropertyException if the property is read only.
Qiang Xue committed
154 155 156
	 */
	public function __unset($name)
	{
Qiang Xue committed
157
		$setter = 'set' . $name;
158 159
		if (method_exists($this, $setter)) {
			// write property
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 $i => $behavior) {
				if (is_string($i) && $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
		if ($this->canGetProperty($name, false)) {
Qiang Xue committed
194 195
			$func = $this->$name;
			if ($func instanceof \Closure) {
Qiang Xue committed
196
				return call_user_func_array($func, $params);
Qiang Xue committed
197 198 199
			}
		}

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

Qiang Xue committed
207
		throw new BadMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
Qiang Xue committed
208 209 210 211 212 213 214 215 216 217 218 219 220
	}

	/**
	 * 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
221 222 223
	 *     'class' => 'BehaviorClass',
	 *     'property1' => 'value1',
	 *     'property2' => 'value2',
Qiang Xue committed
224 225 226
	 * )
	 * ~~~
	 *
Qiang Xue committed
227 228 229 230
	 * 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
231
	 *
Qiang Xue committed
232
	 * Behaviors declared in this method will be attached to the component automatically (on demand).
Qiang Xue committed
233 234 235 236 237 238
	 *
	 * @return array the behavior configurations.
	 */
	public function behaviors()
	{
		return array();
Qiang Xue committed
239 240 241
	}

	/**
Qiang Xue committed
242
	 * Returns a value indicating whether there is any handler attached to the named event.
Qiang Xue committed
243
	 * @param string $name the event name
w  
Qiang Xue committed
244
	 * @return boolean whether there is any handler attached to the event.
Qiang Xue committed
245
	 */
w  
Qiang Xue committed
246
	public function hasEventHandlers($name)
Qiang Xue committed
247
	{
Qiang Xue committed
248
		$this->ensureBehaviors();
w  
Qiang Xue committed
249
		return isset($this->_e[$name]) && $this->_e[$name]->getCount();
Qiang Xue committed
250 251 252 253
	}

	/**
	 * Returns the list of attached event handlers for an event.
w  
Qiang Xue committed
254 255 256
	 * You may manipulate the returned [[Vector]] object by adding or removing handlers.
	 * For example,
	 *
w  
Qiang Xue committed
257
	 * ~~~
w  
Qiang Xue committed
258 259
	 * $component->getEventHandlers($eventName)->insertAt(0, $eventHandler);
	 * ~~~
w  
Qiang Xue committed
260
	 *
Qiang Xue committed
261
	 * @param string $name the event name
w  
Qiang Xue committed
262 263
	 * @return Vector list of attached event handlers for the event
	 * @throws Exception if the event is not defined
Qiang Xue committed
264 265 266
	 */
	public function getEventHandlers($name)
	{
Qiang Xue committed
267 268
		if (!isset($this->_e[$name])) {
			$this->_e[$name] = new Vector;
Qiang Xue committed
269
		}
Qiang Xue committed
270 271
		$this->ensureBehaviors();
		return $this->_e[$name];
Qiang Xue committed
272 273 274 275 276
	}

	/**
	 * Attaches an event handler to an event.
	 *
w  
Qiang Xue committed
277 278
	 * This is equivalent to the following code:
	 *
w  
Qiang Xue committed
279
	 * ~~~
w  
Qiang Xue committed
280 281 282 283 284 285
	 * $component->getEventHandlers($eventName)->add($eventHandler);
	 * ~~~
	 *
	 * An event handler must be a valid PHP callback. The followings are
	 * some examples:
	 *
w  
Qiang Xue committed
286
	 * ~~~
Qiang Xue committed
287 288 289 290
	 * function($event) { ... }         // anonymous function
	 * array($object, 'handleClick')    // $object->handleClick()
	 * array('Page', 'handleClick')     // Page::handleClick()
	 * 'handleClick'                    // global function handleClick()
w  
Qiang Xue committed
291
	 * ~~~
Qiang Xue committed
292 293 294
	 *
	 * An event handler must be defined with the following signature,
	 *
w  
Qiang Xue committed
295
	 * ~~~
w  
Qiang Xue committed
296 297
	 * function handlerName($event) {}
	 * ~~~
Qiang Xue committed
298
	 *
w  
Qiang Xue committed
299
	 * where `$event` is an [[Event]] object which includes parameters associated with the event.
Qiang Xue committed
300 301 302
	 *
	 * @param string $name the event name
	 * @param callback $handler the event handler
Qiang Xue committed
303
	 * @see off
Qiang Xue committed
304
	 */
Qiang Xue committed
305
	public function on($name, $handler)
Qiang Xue committed
306 307 308 309 310 311
	{
		$this->getEventHandlers($name)->add($handler);
	}

	/**
	 * Detaches an existing event handler.
Qiang Xue committed
312
	 * This method is the opposite of [[on]].
Qiang Xue committed
313 314
	 * @param string $name event name
	 * @param callback $handler the event handler to be removed
Qiang Xue committed
315 316
	 * @return boolean if a handler is found and detached
	 * @see on
Qiang Xue committed
317
	 */
Qiang Xue committed
318
	public function off($name, $handler)
Qiang Xue committed
319
	{
w  
Qiang Xue committed
320
		return $this->getEventHandlers($name)->remove($handler) !== false;
Qiang Xue committed
321 322 323
	}

	/**
Qiang Xue committed
324
	 * Triggers an event.
Qiang Xue committed
325 326 327
	 * 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
328
	 * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
w  
Qiang Xue committed
329
	 * @throws Exception if the event is undefined or an event handler is invalid.
Qiang Xue committed
330
	 */
Qiang Xue committed
331
	public function trigger($name, $event = null)
Qiang Xue committed
332
	{
Qiang Xue committed
333
		$this->ensureBehaviors();
w  
Qiang Xue committed
334
		if (isset($this->_e[$name])) {
Qiang Xue committed
335 336
			if ($event === null) {
				$event = new Event($this);
Qiang Xue committed
337 338
			}
			if ($event instanceof Event) {
Qiang Xue committed
339
				$event->handled = false;
Qiang Xue committed
340
				$event->name = $name;
Qiang Xue committed
341
			}
w  
Qiang Xue committed
342
			foreach ($this->_e[$name] as $handler) {
Qiang Xue committed
343
				call_user_func($handler, $event);
w  
Qiang Xue committed
344 345
				// stop further handling if the event is handled
				if ($event instanceof Event && $event->handled) {
Qiang Xue committed
346
					return;
w  
Qiang Xue committed
347 348 349 350 351 352 353
				}
			}
		}
	}

	/**
	 * Returns the named behavior object.
Qiang Xue committed
354
	 * @param string $name the behavior name
w  
Qiang Xue committed
355 356
	 * @return Behavior the behavior object, or null if the behavior does not exist
	 */
Qiang Xue committed
357
	public function getBehavior($name)
w  
Qiang Xue committed
358
	{
Qiang Xue committed
359
		$this->ensureBehaviors();
Qiang Xue committed
360 361 362 363 364 365 366 367 368 369 370
		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
371 372 373 374 375 376 377
	}

	/**
	 * 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
378 379 380
	 * @param integer|string $name the name of the behavior. This can be a string or an integer (or empty string).
	 * If the former, it uniquely identifies this behavior. If the latter, the behavior becomes
	 * anonymous and its methods and properties will NOT be made available in this component.
Qiang Xue committed
381
	 * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
w  
Qiang Xue committed
382 383 384
	 *
	 *  - a [[Behavior]] object
	 *  - a string specifying the behavior class
Qiang Xue committed
385
	 *  - an object configuration array that will be passed to [[\Yii::createObject()]] to create the behavior object.
w  
Qiang Xue committed
386 387 388 389 390 391
	 *
	 * @return Behavior the behavior object
	 * @see detachBehavior
	 */
	public function attachBehavior($name, $behavior)
	{
Qiang Xue committed
392 393
		$this->ensureBehaviors();
		return $this->attachBehaviorInternal($name, $behavior);
w  
Qiang Xue committed
394 395 396 397 398
	}

	/**
	 * Attaches a list of behaviors to the component.
	 * Each behavior is indexed by its name and should be a [[Behavior]] object,
Qiang Xue committed
399
	 * a string specifying the behavior class, or an configuration array for creating the behavior.
w  
Qiang Xue committed
400 401 402 403 404
	 * @param array $behaviors list of behaviors to be attached to the component
	 * @see attachBehavior
	 */
	public function attachBehaviors($behaviors)
	{
Qiang Xue committed
405
		$this->ensureBehaviors();
w  
Qiang Xue committed
406
		foreach ($behaviors as $name => $behavior) {
Qiang Xue committed
407
			$this->attachBehaviorInternal($name, $behavior);
w  
Qiang Xue committed
408 409 410 411 412 413 414 415 416 417 418
		}
	}

	/**
	 * 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)
	{
Qiang Xue committed
419
		$this->ensureBehaviors();
w  
Qiang Xue committed
420 421 422
		if (isset($this->_b[$name])) {
			$behavior = $this->_b[$name];
			unset($this->_b[$name]);
Qiang Xue committed
423
			$behavior->detach($this);
w  
Qiang Xue committed
424
			return $behavior;
Qiang Xue committed
425 426
		} else {
			return null;
w  
Qiang Xue committed
427 428 429 430 431 432 433 434 435
		}
	}

	/**
	 * Detaches all behaviors from the component.
	 */
	public function detachBehaviors()
	{
		if ($this->_b !== null) {
Qiang Xue committed
436
			foreach ($this->_b as $name => $behavior) {
w  
Qiang Xue committed
437 438
				$this->detachBehavior($name);
			}
Qiang Xue committed
439
		}
Qiang Xue committed
440
		$this->_b = array();
Qiang Xue committed
441 442 443 444 445 446 447 448 449 450
	}

	/**
	 * 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
451
				$this->attachBehaviorInternal($name, $behavior);
Qiang Xue committed
452
			}
w  
Qiang Xue committed
453 454
		}
	}
Qiang Xue committed
455 456 457

	/**
	 * Attaches a behavior to this component.
Qiang Xue committed
458 459
	 * @param integer|string $name the name of the behavior. If it is an integer or an empty string,
	 * the behavior is anonymous and its methods and properties will NOT be made available to the owner component.
Qiang Xue committed
460 461 462 463 464 465 466 467
	 * @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
468 469 470 471 472 473 474 475 476 477
		if (is_int($name) || $name == '') {
			// anonymous behavior
			$behavior->attach($this);
			return $this->_b[] = $behavior;
		} else {
			if (isset($this->_b[$name])) {
				$this->_b[$name]->detach($this);
			}
			$behavior->attach($this);
			return $this->_b[$name] = $behavior;
Qiang Xue committed
478
		}
Qiang Xue committed
479
	}
Qiang Xue committed
480
}