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