PageRenderTime 18ms CodeModel.GetById 2ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/base/CComponent.php

http://github.com/yiisoft/yii
PHP | 696 lines | 301 code | 30 blank | 365 comment | 54 complexity | 6b968bdbbfed48a6e63a6fe1fe590429 MD5 | raw file
  1<?php
  2/**
  3 * This file contains the foundation classes for component-based and event-driven programming.
  4 *
  5 * @author Qiang Xue <qiang.xue@gmail.com>
  6 * @link http://www.yiiframework.com/
  7 * @copyright 2008-2013 Yii Software LLC
  8 * @license http://www.yiiframework.com/license/
  9 */
 10
 11/**
 12 * CComponent is the base class for all components.
 13 *
 14 * CComponent implements the protocol of defining, using properties and events.
 15 *
 16 * A property is defined by a getter method, and/or a setter method.
 17 * Properties can be accessed in the way like accessing normal object members.
 18 * Reading or writing a property will cause the invocation of the corresponding
 19 * getter or setter method, e.g
 20 * <pre>
 21 * $a=$component->text;     // equivalent to $a=$component->getText();
 22 * $component->text='abc';  // equivalent to $component->setText('abc');
 23 * </pre>
 24 * The signatures of getter and setter methods are as follows,
 25 * <pre>
 26 * // getter, defines a readable property 'text'
 27 * public function getText() { ... }
 28 * // setter, defines a writable property 'text' with $value to be set to the property
 29 * public function setText($value) { ... }
 30 * </pre>
 31 *
 32 * An event is defined by the presence of a method whose name starts with 'on'.
 33 * The event name is the method name. When an event is raised, functions
 34 * (called event handlers) attached to the event will be invoked automatically.
 35 *
 36 * An event can be raised by calling {@link raiseEvent} method, upon which
 37 * the attached event handlers will be invoked automatically in the order they
 38 * are attached to the event. Event handlers must have the following signature,
 39 * <pre>
 40 * function eventHandler($event) { ... }
 41 * </pre>
 42 * where $event includes parameters associated with the event.
 43 *
 44 * To attach an event handler to an event, see {@link attachEventHandler}.
 45 * You can also use the following syntax:
 46 * <pre>
 47 * $component->onClick=$callback;    // or $component->onClick->add($callback);
 48 * </pre>
 49 * where $callback refers to a valid PHP callback. Below we show some callback examples:
 50 * <pre>
 51 * 'handleOnClick'                   // handleOnClick() is a global function
 52 * array($object,'handleOnClick')    // using $object->handleOnClick()
 53 * array('Page','handleOnClick')     // using Page::handleOnClick()
 54 * </pre>
 55 *
 56 * To raise an event, use {@link raiseEvent}. The on-method defining an event is
 57 * commonly written like the following:
 58 * <pre>
 59 * public function onClick($event)
 60 * {
 61 *     $this->raiseEvent('onClick',$event);
 62 * }
 63 * </pre>
 64 * where <code>$event</code> is an instance of {@link CEvent} or its child class.
 65 * One can then raise the event by calling the on-method instead of {@link raiseEvent} directly.
 66 *
 67 * Both property names and event names are case-insensitive.
 68 *
 69 * CComponent supports behaviors. A behavior is an
 70 * instance of {@link IBehavior} which is attached to a component. The methods of
 71 * the behavior can be invoked as if they belong to the component. Multiple behaviors
 72 * can be attached to the same component.
 73 *
 74 * To attach a behavior to a component, call {@link attachBehavior}; and to detach the behavior
 75 * from the component, call {@link detachBehavior}.
 76 *
 77 * A behavior can be temporarily enabled or disabled by calling {@link enableBehavior}
 78 * or {@link disableBehavior}, respectively. When disabled, the behavior methods cannot
 79 * be invoked via the component.
 80 *
 81 * Starting from version 1.1.0, a behavior's properties (either its public member variables or
 82 * its properties defined via getters and/or setters) can be accessed through the component it
 83 * is attached to.
 84 *
 85 * @author Qiang Xue <qiang.xue@gmail.com>
 86 * @package system.base
 87 * @since 1.0
 88 */
 89class CComponent
 90{
 91	private $_e;
 92	private $_m;
 93
 94	/**
 95	 * Returns a property value, an event handler list or a behavior based on its name.
 96	 * Do not call this method. This is a PHP magic method that we override
 97	 * to allow using the following syntax to read a property or obtain event handlers:
 98	 * <pre>
 99	 * $value=$component->propertyName;
100	 * $handlers=$component->eventName;
101	 * </pre>
102	 * @param string $name the property name or event name
103	 * @return mixed the property value, event handlers attached to the event, or the named behavior
104	 * @throws CException if the property or event is not defined
105	 * @see __set
106	 */
107	public function __get($name)
108	{
109		$getter='get'.$name;
110		if(method_exists($this,$getter))
111			return $this->$getter();
112		elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
113		{
114			// duplicating getEventHandlers() here for performance
115			$name=strtolower($name);
116			if(!isset($this->_e[$name]))
117				$this->_e[$name]=new CList;
118			return $this->_e[$name];
119		}
120		elseif(isset($this->_m[$name]))
121			return $this->_m[$name];
122		elseif(is_array($this->_m))
123		{
124			foreach($this->_m as $object)
125			{
126				if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
127					return $object->$name;
128			}
129		}
130		throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
131			array('{class}'=>get_class($this), '{property}'=>$name)));
132	}
133
134	/**
135	 * Sets value of a component property.
136	 * Do not call this method. This is a PHP magic method that we override
137	 * to allow using the following syntax to set a property or attach an event handler
138	 * <pre>
139	 * $this->propertyName=$value;
140	 * $this->eventName=$callback;
141	 * </pre>
142	 * @param string $name the property name or the event name
143	 * @param mixed $value the property value or callback
144	 * @return mixed
145	 * @throws CException if the property/event is not defined or the property is read only.
146	 * @see __get
147	 */
148	public function __set($name,$value)
149	{
150		$setter='set'.$name;
151		if(method_exists($this,$setter))
152			return $this->$setter($value);
153		elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
154		{
155			// duplicating getEventHandlers() here for performance
156			$name=strtolower($name);
157			if(!isset($this->_e[$name]))
158				$this->_e[$name]=new CList;
159			return $this->_e[$name]->add($value);
160		}
161		elseif(is_array($this->_m))
162		{
163			foreach($this->_m as $object)
164			{
165				if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name)))
166					return $object->$name=$value;
167			}
168		}
169		if(method_exists($this,'get'.$name))
170			throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
171				array('{class}'=>get_class($this), '{property}'=>$name)));
172		else
173			throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
174				array('{class}'=>get_class($this), '{property}'=>$name)));
175	}
176
177	/**
178	 * Checks if a property value is null.
179	 * Do not call this method. This is a PHP magic method that we override
180	 * to allow using isset() to detect if a component property is set or not.
181	 * @param string $name the property name or the event name
182	 * @return boolean
183	 */
184	public function __isset($name)
185	{
186		$getter='get'.$name;
187		if(method_exists($this,$getter))
188			return $this->$getter()!==null;
189		elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
190		{
191			$name=strtolower($name);
192			return isset($this->_e[$name]) && $this->_e[$name]->getCount();
193		}
194		elseif(is_array($this->_m))
195		{
196 			if(isset($this->_m[$name]))
197 				return true;
198			foreach($this->_m as $object)
199			{
200				if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
201					return $object->$name!==null;
202			}
203		}
204		return false;
205	}
206
207	/**
208	 * Sets a component property to be null.
209	 * Do not call this method. This is a PHP magic method that we override
210	 * to allow using unset() to set a component property to be null.
211	 * @param string $name the property name or the event name
212	 * @throws CException if the property is read only.
213	 * @return mixed
214	 */
215	public function __unset($name)
216	{
217		$setter='set'.$name;
218		if(method_exists($this,$setter))
219			$this->$setter(null);
220		elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
221			unset($this->_e[strtolower($name)]);
222		elseif(is_array($this->_m))
223		{
224			if(isset($this->_m[$name]))
225				$this->detachBehavior($name);
226			else
227			{
228				foreach($this->_m as $object)
229				{
230					if($object->getEnabled())
231					{
232						if(property_exists($object,$name))
233							return $object->$name=null;
234						elseif($object->canSetProperty($name))
235							return $object->$setter(null);
236					}
237				}
238			}
239		}
240		elseif(method_exists($this,'get'.$name))
241			throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
242				array('{class}'=>get_class($this), '{property}'=>$name)));
243	}
244
245	/**
246	 * Calls the named method which is not a class method.
247	 * Do not call this method. This is a PHP magic method that we override
248	 * to implement the behavior feature.
249	 * @param string $name the method name
250	 * @param array $parameters method parameters
251	 * @throws CException if current class and its behaviors do not have a method or closure with the given name
252	 * @return mixed the method return value
253	 */
254	public function __call($name,$parameters)
255	{
256		if($this->_m!==null)
257		{
258			foreach($this->_m as $object)
259			{
260				if($object->getEnabled() && method_exists($object,$name))
261					return call_user_func_array(array($object,$name),$parameters);
262			}
263		}
264		if(class_exists('Closure', false) && ($this->canGetProperty($name) || property_exists($this, $name)) && $this->$name instanceof Closure)
265			return call_user_func_array($this->$name, $parameters);
266		throw new CException(Yii::t('yii','{class} and its behaviors do not have a method or closure named "{name}".',
267			array('{class}'=>get_class($this), '{name}'=>$name)));
268	}
269
270	/**
271	 * Returns the named behavior object.
272	 * The name 'asa' stands for 'as a'.
273	 * @param string $behavior the behavior name
274	 * @return IBehavior the behavior object, or null if the behavior does not exist
275	 */
276	public function asa($behavior)
277	{
278		return isset($this->_m[$behavior]) ? $this->_m[$behavior] : null;
279	}
280
281	/**
282	 * Attaches a list of behaviors to the component.
283	 * Each behavior is indexed by its name and should be an instance of
284	 * {@link IBehavior}, a string specifying the behavior class, or an
285	 * array of the following structure:
286	 * <pre>
287	 * array(
288	 *     'class'=>'path.to.BehaviorClass',
289	 *     'property1'=>'value1',
290	 *     'property2'=>'value2',
291	 * )
292	 * </pre>
293	 * @param array $behaviors list of behaviors to be attached to the component
294	 */
295	public function attachBehaviors($behaviors)
296	{
297		foreach($behaviors as $name=>$behavior)
298			$this->attachBehavior($name,$behavior);
299	}
300
301	/**
302	 * Detaches all behaviors from the component.
303	 */
304	public function detachBehaviors()
305	{
306		if($this->_m!==null)
307		{
308			foreach($this->_m as $name=>$behavior)
309				$this->detachBehavior($name);
310			$this->_m=null;
311		}
312	}
313
314	/**
315	 * Attaches a behavior to this component.
316	 * This method will create the behavior object based on the given
317	 * configuration. After that, the behavior object will be initialized
318	 * by calling its {@link IBehavior::attach} method.
319	 * @param string $name the behavior's name. It should uniquely identify this behavior.
320	 * @param mixed $behavior the behavior configuration. This is passed as the first
321	 * parameter to {@link YiiBase::createComponent} to create the behavior object.
322	 * You can also pass an already created behavior instance (the new behavior will replace an already created
323	 * behavior with the same name, if it exists).
324	 * @return IBehavior the behavior object
325	 */
326	public function attachBehavior($name,$behavior)
327	{
328		if(!($behavior instanceof IBehavior))
329			$behavior=Yii::createComponent($behavior);
330		$behavior->setEnabled(true);
331		$behavior->attach($this);
332		return $this->_m[$name]=$behavior;
333	}
334
335	/**
336	 * Detaches a behavior from the component.
337	 * The behavior's {@link IBehavior::detach} method will be invoked.
338	 * @param string $name the behavior's name. It uniquely identifies the behavior.
339	 * @return IBehavior the detached behavior. Null if the behavior does not exist.
340	 */
341	public function detachBehavior($name)
342	{
343		if(isset($this->_m[$name]))
344		{
345			$this->_m[$name]->detach($this);
346			$behavior=$this->_m[$name];
347			unset($this->_m[$name]);
348			return $behavior;
349		}
350	}
351
352	/**
353	 * Enables all behaviors attached to this component.
354	 */
355	public function enableBehaviors()
356	{
357		if($this->_m!==null)
358		{
359			foreach($this->_m as $behavior)
360				$behavior->setEnabled(true);
361		}
362	}
363
364	/**
365	 * Disables all behaviors attached to this component.
366	 */
367	public function disableBehaviors()
368	{
369		if($this->_m!==null)
370		{
371			foreach($this->_m as $behavior)
372				$behavior->setEnabled(false);
373		}
374	}
375
376	/**
377	 * Enables an attached behavior.
378	 * A behavior is only effective when it is enabled.
379	 * A behavior is enabled when first attached.
380	 * @param string $name the behavior's name. It uniquely identifies the behavior.
381	 */
382	public function enableBehavior($name)
383	{
384		if(isset($this->_m[$name]))
385			$this->_m[$name]->setEnabled(true);
386	}
387
388	/**
389	 * Disables an attached behavior.
390	 * A behavior is only effective when it is enabled.
391	 * @param string $name the behavior's name. It uniquely identifies the behavior.
392	 */
393	public function disableBehavior($name)
394	{
395		if(isset($this->_m[$name]))
396			$this->_m[$name]->setEnabled(false);
397	}
398
399	/**
400	 * Determines whether a property is defined.
401	 * A property is defined if there is a getter or setter method
402	 * defined in the class. Note, property names are case-insensitive.
403	 * @param string $name the property name
404	 * @return boolean whether the property is defined
405	 * @see canGetProperty
406	 * @see canSetProperty
407	 */
408	public function hasProperty($name)
409	{
410		return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);
411	}
412
413	/**
414	 * Determines whether a property can be read.
415	 * A property can be read if the class has a getter method
416	 * for the property name. Note, property name is case-insensitive.
417	 * @param string $name the property name
418	 * @return boolean whether the property can be read
419	 * @see canSetProperty
420	 */
421	public function canGetProperty($name)
422	{
423		return method_exists($this,'get'.$name);
424	}
425
426	/**
427	 * Determines whether a property can be set.
428	 * A property can be written if the class has a setter method
429	 * for the property name. Note, property name is case-insensitive.
430	 * @param string $name the property name
431	 * @return boolean whether the property can be written
432	 * @see canGetProperty
433	 */
434	public function canSetProperty($name)
435	{
436		return method_exists($this,'set'.$name);
437	}
438
439	/**
440	 * Determines whether an event is defined.
441	 * An event is defined if the class has a method named like 'onXXX'.
442	 * Note, event name is case-insensitive.
443	 * @param string $name the event name
444	 * @return boolean whether an event is defined
445	 */
446	public function hasEvent($name)
447	{
448		return !strncasecmp($name,'on',2) && method_exists($this,$name);
449	}
450
451	/**
452	 * Checks whether the named event has attached handlers.
453	 * @param string $name the event name
454	 * @return boolean whether an event has been attached one or several handlers
455	 */
456	public function hasEventHandler($name)
457	{
458		$name=strtolower($name);
459		return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0;
460	}
461
462	/**
463	 * Returns the list of attached event handlers for an event.
464	 * @param string $name the event name
465	 * @return CList list of attached event handlers for the event
466	 * @throws CException if the event is not defined
467	 */
468	public function getEventHandlers($name)
469	{
470		if($this->hasEvent($name))
471		{
472			$name=strtolower($name);
473			if(!isset($this->_e[$name]))
474				$this->_e[$name]=new CList;
475			return $this->_e[$name];
476		}
477		else
478			throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
479				array('{class}'=>get_class($this), '{event}'=>$name)));
480	}
481
482	/**
483	 * Attaches an event handler to an event.
484	 *
485	 * An event handler must be a valid PHP callback, i.e., a string referring to
486	 * a global function name, or an array containing two elements with
487	 * the first element being an object and the second element a method name
488	 * of the object.
489	 *
490	 * An event handler must be defined with the following signature,
491	 * <pre>
492	 * function handlerName($event) {}
493	 * </pre>
494	 * where $event includes parameters associated with the event.
495	 *
496	 * This is a convenient method of attaching a handler to an event.
497	 * It is equivalent to the following code:
498	 * <pre>
499	 * $component->getEventHandlers($eventName)->add($eventHandler);
500	 * </pre>
501	 *
502	 * Using {@link getEventHandlers}, one can also specify the execution order
503	 * of multiple handlers attaching to the same event. For example:
504	 * <pre>
505	 * $component->getEventHandlers($eventName)->insertAt(0,$eventHandler);
506	 * </pre>
507	 * makes the handler to be invoked first.
508	 *
509	 * @param string $name the event name
510	 * @param callback $handler the event handler
511	 * @throws CException if the event is not defined
512	 * @see detachEventHandler
513	 */
514	public function attachEventHandler($name,$handler)
515	{
516		$this->getEventHandlers($name)->add($handler);
517	}
518
519	/**
520	 * Detaches an existing event handler.
521	 * This method is the opposite of {@link attachEventHandler}.
522	 * @param string $name event name
523	 * @param callback $handler the event handler to be removed
524	 * @return boolean if the detachment process is successful
525	 * @see attachEventHandler
526	 */
527	public function detachEventHandler($name,$handler)
528	{
529		if($this->hasEventHandler($name))
530			return $this->getEventHandlers($name)->remove($handler)!==false;
531		else
532			return false;
533	}
534
535	/**
536	 * Raises an event.
537	 * This method represents the happening of an event. It invokes
538	 * all attached handlers for the event.
539	 * @param string $name the event name
540	 * @param CEvent $event the event parameter
541	 * @throws CException if the event is undefined or an event handler is invalid.
542	 */
543	public function raiseEvent($name,$event)
544	{
545		$name=strtolower($name);
546		if(isset($this->_e[$name]))
547		{
548			foreach($this->_e[$name] as $handler)
549			{
550				if(is_string($handler))
551					call_user_func($handler,$event);
552				elseif(is_callable($handler,true))
553				{
554					if(is_array($handler))
555					{
556						// an array: 0 - object, 1 - method name
557						list($object,$method)=$handler;
558						if(is_string($object))	// static method call
559							call_user_func($handler,$event);
560						elseif(method_exists($object,$method))
561							$object->$method($event);
562						else
563							throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',
564								array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1])));
565					}
566					else // PHP 5.3: anonymous function
567						call_user_func($handler,$event);
568				}
569				else
570					throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',
571						array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler))));
572				// stop further handling if param.handled is set true
573				if(($event instanceof CEvent) && $event->handled)
574					return;
575			}
576		}
577		elseif(YII_DEBUG && !$this->hasEvent($name))
578			throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
579				array('{class}'=>get_class($this), '{event}'=>$name)));
580	}
581
582	/**
583	 * Evaluates a PHP expression or callback under the context of this component.
584	 *
585	 * Valid PHP callback can be class method name in the form of
586	 * array(ClassName/Object, MethodName), or anonymous function (only available in PHP 5.3.0 or above).
587	 *
588	 * If a PHP callback is used, the corresponding function/method signature should be
589	 * <pre>
590	 * function foo($param1, $param2, ..., $component) { ... }
591	 * </pre>
592	 * where the array elements in the second parameter to this method will be passed
593	 * to the callback as $param1, $param2, ...; and the last parameter will be the component itself.
594	 *
595	 * If a PHP expression is used, the second parameter will be "extracted" into PHP variables
596	 * that can be directly accessed in the expression. See {@link http://us.php.net/manual/en/function.extract.php PHP extract}
597	 * for more details. In the expression, the component object can be accessed using $this.
598	 *
599	 * A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
600	 * please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
601	 *
602	 * @param mixed $_expression_ a PHP expression or PHP callback to be evaluated.
603	 * @param array $_data_ additional parameters to be passed to the above expression/callback.
604	 * @return mixed the expression result
605	 * @since 1.1.0
606	 */
607	public function evaluateExpression($_expression_,$_data_=array())
608	{
609		if(is_string($_expression_))
610		{
611			extract($_data_);
612			try
613			{
614				return eval('return ' . $_expression_ . ';');
615			}
616			catch (ParseError $e)
617			{
618				return false;
619			}
620		}
621		else
622		{
623			$_data_[]=$this;
624			return call_user_func_array($_expression_, $_data_);
625		}
626	}
627}
628
629
630/**
631 * CEvent is the base class for all event classes.
632 *
633 * It encapsulates the parameters associated with an event.
634 * The {@link sender} property describes who raises the event.
635 * And the {@link handled} property indicates if the event is handled.
636 * If an event handler sets {@link handled} to true, those handlers
637 * that are not invoked yet will not be invoked anymore.
638 *
639 * @author Qiang Xue <qiang.xue@gmail.com>
640 * @package system.base
641 * @since 1.0
642 */
643class CEvent extends CComponent
644{
645	/**
646	 * @var object the sender of this event
647	 */
648	public $sender;
649	/**
650	 * @var boolean whether the event is handled. Defaults to false.
651	 * When a handler sets this true, the rest of the uninvoked event handlers will not be invoked anymore.
652	 */
653	public $handled=false;
654	/**
655	 * @var mixed additional event parameters.
656	 * @since 1.1.7
657	 */
658	public $params;
659
660	/**
661	 * Constructor.
662	 * @param mixed $sender sender of the event
663	 * @param mixed $params additional parameters for the event
664	 */
665	public function __construct($sender=null,$params=null)
666	{
667		$this->sender=$sender;
668		$this->params=$params;
669	}
670}
671
672
673/**
674 * CEnumerable is the base class for all enumerable types.
675 *
676 * To define an enumerable type, extend CEnumberable and define string constants.
677 * Each constant represents an enumerable value.
678 * The constant name must be the same as the constant value.
679 * For example,
680 * <pre>
681 * class TextAlign extends CEnumerable
682 * {
683 *     const Left='Left';
684 *     const Right='Right';
685 * }
686 * </pre>
687 * Then, one can use the enumerable values such as TextAlign::Left and
688 * TextAlign::Right.
689 *
690 * @author Qiang Xue <qiang.xue@gmail.com>
691 * @package system.base
692 * @since 1.0
693 */
694class CEnumerable
695{
696}