PageRenderTime 111ms CodeModel.GetById 86ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/base/CComponent.php

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