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

/src/framework/base/CComponent.php

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