PageRenderTime 552ms CodeModel.GetById 213ms app.highlight 161ms RepoModel.GetById 171ms app.codeStats 0ms

/framework/base/CComponent.php

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