PageRenderTime 207ms CodeModel.GetById 121ms app.highlight 18ms RepoModel.GetById 62ms app.codeStats 0ms

/demo/yii/base/CComponent.php

https://bitbucket.org/yourilima/yii-bootstrap
PHP | 686 lines | 294 code | 30 blank | 362 comment | 82 complexity | d4138ddc750db453268874154ffeacd6 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 * 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$
 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
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	 */
185	public function __isset($name)
186	{
187		$getter='get'.$name;
188		if(method_exists($this,$getter))
189			return $this->$getter()!==null;
190		else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
191		{
192			$name=strtolower($name);
193			return isset($this->_e[$name]) && $this->_e[$name]->getCount();
194		}
195		else if(is_array($this->_m))
196		{
197 			if(isset($this->_m[$name]))
198 				return true;
199			foreach($this->_m as $object)
200			{
201				if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
202					return $object->$name!==null;
203			}
204		}
205		return false;
206	}
207
208	/**
209	 * Sets a component property to be null.
210	 * Do not call this method. This is a PHP magic method that we override
211	 * to allow using unset() to set a component property to be null.
212	 * @param string $name the property name or the event name
213	 * @throws CException if the property is read only.
214	 * @return mixed
215	 */
216	public function __unset($name)
217	{
218		$setter='set'.$name;
219		if(method_exists($this,$setter))
220			$this->$setter(null);
221		else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
222			unset($this->_e[strtolower($name)]);
223		else if(is_array($this->_m))
224		{
225			if(isset($this->_m[$name]))
226				$this->detachBehavior($name);
227			else
228			{
229				foreach($this->_m as $object)
230				{
231					if($object->getEnabled())
232					{
233						if(property_exists($object,$name))
234							return $object->$name=null;
235						else if($object->canSetProperty($name))
236							return $object->$setter(null);
237					}
238				}
239			}
240		}
241		else if(method_exists($this,'get'.$name))
242			throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
243				array('{class}'=>get_class($this), '{property}'=>$name)));
244	}
245
246	/**
247	 * Calls the named method which is not a class method.
248	 * Do not call this method. This is a PHP magic method that we override
249	 * to implement the behavior feature.
250	 * @param string $name the method name
251	 * @param array $parameters method parameters
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) && $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	 * @return IBehavior the behavior object
323	 */
324	public function attachBehavior($name,$behavior)
325	{
326		if(!($behavior instanceof IBehavior))
327			$behavior=Yii::createComponent($behavior);
328		$behavior->setEnabled(true);
329		$behavior->attach($this);
330		return $this->_m[$name]=$behavior;
331	}
332
333	/**
334	 * Detaches a behavior from the component.
335	 * The behavior's {@link IBehavior::detach} method will be invoked.
336	 * @param string $name the behavior's name. It uniquely identifies the behavior.
337	 * @return IBehavior the detached behavior. Null if the behavior does not exist.
338	 */
339	public function detachBehavior($name)
340	{
341		if(isset($this->_m[$name]))
342		{
343			$this->_m[$name]->detach($this);
344			$behavior=$this->_m[$name];
345			unset($this->_m[$name]);
346			return $behavior;
347		}
348	}
349
350	/**
351	 * Enables all behaviors attached to this component.
352	 */
353	public function enableBehaviors()
354	{
355		if($this->_m!==null)
356		{
357			foreach($this->_m as $behavior)
358				$behavior->setEnabled(true);
359		}
360	}
361
362	/**
363	 * Disables all behaviors attached to this component.
364	 */
365	public function disableBehaviors()
366	{
367		if($this->_m!==null)
368		{
369			foreach($this->_m as $behavior)
370				$behavior->setEnabled(false);
371		}
372	}
373
374	/**
375	 * Enables an attached behavior.
376	 * A behavior is only effective when it is enabled.
377	 * A behavior is enabled when first attached.
378	 * @param string $name the behavior's name. It uniquely identifies the behavior.
379	 */
380	public function enableBehavior($name)
381	{
382		if(isset($this->_m[$name]))
383			$this->_m[$name]->setEnabled(true);
384	}
385
386	/**
387	 * Disables an attached behavior.
388	 * A behavior is only effective when it is enabled.
389	 * @param string $name the behavior's name. It uniquely identifies the behavior.
390	 */
391	public function disableBehavior($name)
392	{
393		if(isset($this->_m[$name]))
394			$this->_m[$name]->setEnabled(false);
395	}
396
397	/**
398	 * Determines whether a property is defined.
399	 * A property is defined if there is a getter or setter method
400	 * defined in the class. Note, property names are case-insensitive.
401	 * @param string $name the property name
402	 * @return boolean whether the property is defined
403	 * @see canGetProperty
404	 * @see canSetProperty
405	 */
406	public function hasProperty($name)
407	{
408		return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);
409	}
410
411	/**
412	 * Determines whether a property can be read.
413	 * A property can be read if the class has a getter method
414	 * for the property name. Note, property name is case-insensitive.
415	 * @param string $name the property name
416	 * @return boolean whether the property can be read
417	 * @see canSetProperty
418	 */
419	public function canGetProperty($name)
420	{
421		return method_exists($this,'get'.$name);
422	}
423
424	/**
425	 * Determines whether a property can be set.
426	 * A property can be written if the class has a setter method
427	 * for the property name. Note, property name is case-insensitive.
428	 * @param string $name the property name
429	 * @return boolean whether the property can be written
430	 * @see canGetProperty
431	 */
432	public function canSetProperty($name)
433	{
434		return method_exists($this,'set'.$name);
435	}
436
437	/**
438	 * Determines whether an event is defined.
439	 * An event is defined if the class has a method named like 'onXXX'.
440	 * Note, event name is case-insensitive.
441	 * @param string $name the event name
442	 * @return boolean whether an event is defined
443	 */
444	public function hasEvent($name)
445	{
446		return !strncasecmp($name,'on',2) && method_exists($this,$name);
447	}
448
449	/**
450	 * Checks whether the named event has attached handlers.
451	 * @param string $name the event name
452	 * @return boolean whether an event has been attached one or several handlers
453	 */
454	public function hasEventHandler($name)
455	{
456		$name=strtolower($name);
457		return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0;
458	}
459
460	/**
461	 * Returns the list of attached event handlers for an event.
462	 * @param string $name the event name
463	 * @return CList list of attached event handlers for the event
464	 * @throws CException if the event is not defined
465	 */
466	public function getEventHandlers($name)
467	{
468		if($this->hasEvent($name))
469		{
470			$name=strtolower($name);
471			if(!isset($this->_e[$name]))
472				$this->_e[$name]=new CList;
473			return $this->_e[$name];
474		}
475		else
476			throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
477				array('{class}'=>get_class($this), '{event}'=>$name)));
478	}
479
480	/**
481	 * Attaches an event handler to an event.
482	 *
483	 * An event handler must be a valid PHP callback, i.e., a string referring to
484	 * a global function name, or an array containing two elements with
485	 * the first element being an object and the second element a method name
486	 * of the object.
487	 *
488	 * An event handler must be defined with the following signature,
489	 * <pre>
490	 * function handlerName($event) {}
491	 * </pre>
492	 * where $event includes parameters associated with the event.
493	 *
494	 * This is a convenient method of attaching a handler to an event.
495	 * It is equivalent to the following code:
496	 * <pre>
497	 * $component->getEventHandlers($eventName)->add($eventHandler);
498	 * </pre>
499	 *
500	 * Using {@link getEventHandlers}, one can also specify the excution order
501	 * of multiple handlers attaching to the same event. For example:
502	 * <pre>
503	 * $component->getEventHandlers($eventName)->insertAt(0,$eventHandler);
504	 * </pre>
505	 * makes the handler to be invoked first.
506	 *
507	 * @param string $name the event name
508	 * @param callback $handler the event handler
509	 * @throws CException if the event is not defined
510	 * @see detachEventHandler
511	 */
512	public function attachEventHandler($name,$handler)
513	{
514		$this->getEventHandlers($name)->add($handler);
515	}
516
517	/**
518	 * Detaches an existing event handler.
519	 * This method is the opposite of {@link attachEventHandler}.
520	 * @param string $name event name
521	 * @param callback $handler the event handler to be removed
522	 * @return boolean if the detachment process is successful
523	 * @see attachEventHandler
524	 */
525	public function detachEventHandler($name,$handler)
526	{
527		if($this->hasEventHandler($name))
528			return $this->getEventHandlers($name)->remove($handler)!==false;
529		else
530			return false;
531	}
532
533	/**
534	 * Raises an event.
535	 * This method represents the happening of an event. It invokes
536	 * all attached handlers for the event.
537	 * @param string $name the event name
538	 * @param CEvent $event the event parameter
539	 * @throws CException if the event is undefined or an event handler is invalid.
540	 */
541	public function raiseEvent($name,$event)
542	{
543		$name=strtolower($name);
544		if(isset($this->_e[$name]))
545		{
546			foreach($this->_e[$name] as $handler)
547			{
548				if(is_string($handler))
549					call_user_func($handler,$event);
550				else if(is_callable($handler,true))
551				{
552					if(is_array($handler))
553					{
554						// an array: 0 - object, 1 - method name
555						list($object,$method)=$handler;
556						if(is_string($object))	// static method call
557							call_user_func($handler,$event);
558						else if(method_exists($object,$method))
559							$object->$method($event);
560						else
561							throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',
562								array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1])));
563					}
564					else // PHP 5.3: anonymous function
565						call_user_func($handler,$event);
566				}
567				else
568					throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',
569						array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler))));
570				// stop further handling if param.handled is set true
571				if(($event instanceof CEvent) && $event->handled)
572					return;
573			}
574		}
575		else if(YII_DEBUG && !$this->hasEvent($name))
576			throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
577				array('{class}'=>get_class($this), '{event}'=>$name)));
578	}
579
580	/**
581	 * Evaluates a PHP expression or callback under the context of this component.
582	 *
583	 * Valid PHP callback can be class method name in the form of
584	 * array(ClassName/Object, MethodName), or anonymous function (only available in PHP 5.3.0 or above).
585	 *
586	 * If a PHP callback is used, the corresponding function/method signature should be
587	 * <pre>
588	 * function foo($param1, $param2, ..., $component) { ... }
589	 * </pre>
590	 * where the array elements in the second parameter to this method will be passed
591	 * to the callback as $param1, $param2, ...; and the last parameter will be the component itself.
592	 *
593	 * If a PHP expression is used, the second parameter will be "extracted" into PHP variables
594	 * that can be directly accessed in the expression. See {@link http://us.php.net/manual/en/function.extract.php PHP extract}
595	 * for more details. In the expression, the component object can be accessed using $this.
596	 *
597	 * @param mixed $_expression_ a PHP expression or PHP callback to be evaluated.
598	 * @param array $_data_ additional parameters to be passed to the above expression/callback.
599	 * @return mixed the expression result
600	 * @since 1.1.0
601	 */
602	public function evaluateExpression($_expression_,$_data_=array())
603	{
604		if(is_string($_expression_) && !function_exists($_expression_))
605		{
606			extract($_data_);
607			return eval('return '.$_expression_.';');
608		}
609		else
610		{
611			$_data_[]=$this;
612			return call_user_func_array($_expression_, $_data_);
613		}
614	}
615}
616
617
618/**
619 * CEvent is the base class for all event classes.
620 *
621 * It encapsulates the parameters associated with an event.
622 * The {@link sender} property describes who raises the event.
623 * And the {@link handled} property indicates if the event is handled.
624 * If an event handler sets {@link handled} to true, those handlers
625 * that are not invoked yet will not be invoked anymore.
626 *
627 * @author Qiang Xue <qiang.xue@gmail.com>
628 * @version $Id$
629 * @package system.base
630 * @since 1.0
631 */
632class CEvent extends CComponent
633{
634	/**
635	 * @var object the sender of this event
636	 */
637	public $sender;
638	/**
639	 * @var boolean whether the event is handled. Defaults to false.
640	 * When a handler sets this true, the rest of the uninvoked event handlers will not be invoked anymore.
641	 */
642	public $handled=false;
643	/**
644	 * @var mixed additional event parameters.
645	 * @since 1.1.7
646	 */
647	public $params;
648
649	/**
650	 * Constructor.
651	 * @param mixed $sender sender of the event
652	 * @param mixed $params additional parameters for the event
653	 */
654	public function __construct($sender=null,$params=null)
655	{
656		$this->sender=$sender;
657		$this->params=$params;
658	}
659}
660
661
662/**
663 * CEnumerable is the base class for all enumerable types.
664 *
665 * To define an enumerable type, extend CEnumberable and define string constants.
666 * Each constant represents an enumerable value.
667 * The constant name must be the same as the constant value.
668 * For example,
669 * <pre>
670 * class TextAlign extends CEnumerable
671 * {
672 *     const Left='Left';
673 *     const Right='Right';
674 * }
675 * </pre>
676 * Then, one can use the enumerable values such as TextAlign::Left and
677 * TextAlign::Right.
678 *
679 * @author Qiang Xue <qiang.xue@gmail.com>
680 * @version $Id$
681 * @package system.base
682 * @since 1.0
683 */
684class CEnumerable
685{
686}