3 * TComponent, TPropertyValue classes
5 * @author Qiang Xue <qiang.xue@gmail.com>
7 * Global Events, intra-object events, Class behaviors, expanded behaviors
8 * @author Brad Anderson <javalizard@mac.com>
10 * @link http://www.pradosoft.com/
11 * @copyright Copyright © 2005-2014 PradoSoft
12 * @license http://www.pradosoft.com/license/
19 * TComponent is the base class for all PRADO components.
20 * TComponent implements the protocol of defining, using properties, behaviors,
23 * A property is defined by a getter method, and/or a setter method.
24 * Properties can be accessed in the way like accessing normal object members.
25 * Reading or writing a property will cause the invocation of the corresponding
26 * getter or setter method, e.g.,
28 * $a=$this->Text; // equivalent to $a=$this->getText();
29 * $this->Text='abc'; // equivalent to $this->setText('abc');
31 * The signatures of getter and setter methods are as follows,
33 * // getter, defines a readable property 'Text'
34 * function getText() { ... }
35 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
36 * function setText($value) { ... }
38 * Property names are case-insensitive. It is recommended that they are written
39 * in the format of concatenated words, with the first letter of each word
40 * capitalized (e.g. DisplayMode, ItemStyle).
42 * Javascript Get and Set
44 * Since Prado 3.2 a new class of javascript-friendly properties have been introduced
45 * to better deal with potential security problems like cross-site scripting issues.
46 * All the data that gets sent clientside inside a javascript block is now encoded by default.
47 * Sometimes there's the need to bypass this encoding and be able to send raw javascript code.
48 * This new class of javascript-friendly properties are identified by their name
49 * starting with 'js' (case insensitive):
51 * // getter, defines a readable property 'Text'
52 * function getJsText() { ... }
53 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
54 * function setJsText(TJavaScriptLiteral $value) { ... }
56 * Js-friendly properties can be accessed using both their Js-less name and their Js-enabled name:
58 * // set some simple text as property value
59 * $component->Text = 'text';
60 * // set some javascript code as property value
61 * $component->JsText = 'raw javascript';
63 * In the first case, the property value will automatically gets encoded when sent
64 * clientside inside a javascript block.
65 * In the second case, the property will be 'marked' as being a safe javascript
66 * statement and will not be encoded when rendered inside a javascript block.
67 * This special handling makes use of the {@link TJavaScriptLiteral} class.
71 * An event is defined by the presence of a method whose name starts with 'on'.
72 * The event name is the method name and is thus case-insensitive.
73 * An event can be attached with one or several methods (called event handlers).
74 * An event can be raised by calling {@link raiseEvent} method, upon which
75 * the attached event handlers will be invoked automatically in the order they
76 * are attached to the event. Event handlers must have the following signature,
78 * function eventHandlerFuncName($sender,$param) { ... }
80 * where $sender refers to the object who is responsible for the raising of the event,
81 * and $param refers to a structure that may contain event-specific information.
82 * To raise an event (assuming named as 'Click') of a component, use
84 * $component->raiseEvent('OnClick');
85 * $component->raiseEvent('OnClick', $this, $param);
87 * To attach an event handler to an event, use one of the following ways,
89 * $component->OnClick=$callback; // or $component->OnClick->add($callback);
90 * $component->attachEventHandler('OnClick',$callback);
92 * The first two ways make use of the fact that $component->OnClick refers to
93 * the event handler list {@link TPriorityList} for the 'OnClick' event.
94 * The variable $callback contains the definition of the event handler that can
95 * be either a string referring to a global function name, or an array whose
96 * first element refers to an object and second element a method name/path that
97 * is reachable by the object, e.g.
98 * - 'buttonClicked' : buttonClicked($sender,$param);
99 * - array($object,'buttonClicked') : $object->buttonClicked($sender,$param);
100 * - array($object,'MainContent.SubmitButton.buttonClicked') :
101 * $object->MainContent->SubmitButton->buttonClicked($sender,$param);
103 * With the addition of behaviors, a more expansive event model is needed. There
104 * are two new event types (global and dynamic events) as well as a more comprehensive
105 * behavior model that includes class wide behaviors.
107 * A global event is defined by all events whose name starts with 'fx'.
108 * The event name is potentially a method name and is thus case-insensitive. All 'fx' events
109 * are valid as the whole 'fx' event/method space is global in nature. Any object may patch into
110 * any global event by defining that event as a method. Global events have priorities
111 * just like 'on' events; so as to be able to order the event execution. Due to the
112 * nature of all events which start with 'fx' being valid, in effect, every object
113 * has every 'fx' global event. It is simply an issue of tapping into the desired
116 * A global event that starts with 'fx' can be called even if the object does not
117 * implement the method of the global event. A call to a non-existing 'fx' method
118 * will, at minimal, function and return null. If a method argument list has a first
119 * parameter, it will be returned instead of null. This allows filtering and chaining.
120 * 'fx' methods do not automatically install and uninstall. To install and uninstall an
121 * object's global event listeners, call the object's {@link listen} and
122 * {@link unlisten} methods, respectively. An object may auto-install its global event
123 * during {@link __construct} by overriding {@link getAutoGlobalListen} and returning true.
125 * As of PHP version 5.3, nulled objects without code references will still continue to persist
126 * in the global event queue because {@link __destruct} is not automatically called. In the common
127 * __destruct method, if an object is listening to global events, then {@link unlisten} is called.
128 * {@link unlisten} is required to be manually called before an object is
129 * left without references if it is currently listening to any global events. This includes
130 * class wide behaviors.
132 * An object that contains a method that starts with 'fx' will have those functions
133 * automatically receive those events of the same name after {@link listen} is called on the object.
135 * An object may listen to a global event without defining an 'fx' method of the same name by
136 * adding an object method to the global event list. For example
138 * $component->fxGlobalCheck=$callback; // or $component->OnClick->add($callback);
139 * $component->attachEventHandler('fxGlobalCheck',array($object, 'someMethod'));
142 * Events between Objects and their behaviors, Dynamic Events
144 * An intra-object/behavior event is defined by methods that start with 'dy'. Just as with
145 * 'fx' global events, every object has every dynamic event. Any call to a method that
146 * starts with 'dy' will be handled, regardless of whether it is implemented. These
147 * events are for communicating with attached behaviors.
149 * Dynamic events can be used in a variety of ways. They can be used to tell behaviors
150 * when a non-behavior method is called. Dynamic events could be used as data filters.
151 * They could also be used to specify when a piece of code is to be run, eg. should the
152 * loop process be performed on a particular piece of data. In this way, some control
153 * is handed to the behaviors over the process and/or data.
155 * If there are no handlers for an 'fx' or 'dy' event, it will return the first
156 * parameter of the argument list. If there are no arguments, these events
157 * will return null. If there are handlers an 'fx' method will be called directly
158 * within the object. Global 'fx' events are triggered by calling {@link raiseEvent}.
159 * For dynamic events where there are behaviors that respond to the dynamic events, a
160 * {@link TCallChain} is developed. A call chain allows the behavior dynamic event
161 * implementations to call further implementing behaviors within a chain.
163 * If an object implements {@link IDynamicMethods}, all global and object dynamic
164 * events will be sent to {@link __dycall}. In the case of global events, all
165 * global events will trigger this method. In the case of behaviors, all undefined
166 * dynamic events which are called will be passed through to this method.
171 * There are two types of behaviors. There are individual object behaviors and
172 * there are class wide behaviors. Class behaviors depend upon object behaviors.
174 * When a new class implements {@link IBehavior} or {@link IClassBehavior} or
175 * extends {@link TBehavior} or {@link TClassBehavior}, it may be added to an
176 * object by calling the object's {@link attachBehavior}. The behaviors associated
177 * name can then be used to {@link enableBehavior} or {@link disableBehavior}
178 * the specific behavior.
180 * All behaviors may be turned on and off via {@link enableBehaviors} and
181 * {@link disableBehaviors}, respectively. To check if behaviors are on or off
182 * a call to {@link getBehaviorsEnabled} will provide the variable.
184 * Attaching and detaching whole sets of behaviors is done using
185 * {@link attachBehaviors} and {@link detachBehaviors}. {@link clearBehaviors}
186 * removes all of an object's behaviors.
188 * {@link asa} returns a behavior of a specific name. {@link isa} is the
189 * behavior inclusive function that acts as the PHP operator {@link instanceof}.
190 * A behavior could provide the functionality of a specific class thus causing
191 * the host object to act similarly to a completely different class. A behavior
192 * would then implement {@link IInstanceCheck} to provide the identity of the
195 * Class behaviors are similar to object behaviors except that the class behavior
196 * is the implementation for all instances of the class. A class behavior
197 * will have the object upon which is being called be prepended to the parameter
198 * list. This way the object is known across the class behavior implementation.
200 * Class behaviors are attached using {@link attachClassBehavior} and detached
201 * using {@link detachClassBehavior}. Class behaviors are important in that
202 * they will be applied to all new instances of a particular class. In this way
203 * class behaviors become default behaviors to a new instances of a class in
204 * {@link __construct}. Detaching a class behavior will remove the behavior
205 * from the default set of behaviors created for an object when the object
208 * Class behaviors are also added to all existing instances via the global 'fx'
209 * event mechanism. When a new class behavior is added, the event
210 * {@link fxAttachClassBehavior} is raised and all existing instances that are
211 * listening to this global event (primarily after {@link listen} is called)
212 * will have this new behavior attached. A similar process is used when
213 * detaching class behaviors. Any objects listening to the global 'fx' event
214 * {@link fxDetachClassBehavior} will have a class behavior removed.
216 * Dynamic Intra-Object Events
218 * Dynamic events start with 'dy'. This mechanism is used to allow objects
219 * to communicate with their behaviors directly. The entire 'dy' event space
220 * is valid. All attached, enabled behaviors that implement a dynamic event
221 * are called when the host object calls the dynamic event. If there is no
222 * implementation or behaviors, this returns null when no parameters are
223 * supplied and will return the first parameter when there is at least one
224 * parameter in the dynamic event.
226 * null == $this->dyBehaviorEvent();
227 * 5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event
230 * Dynamic events can be chained together within behaviors to allow for data
231 * filtering. Dynamic events are implemented within behaviors by defining the
234 * class TObjectBehavior extends TBehavior {
235 * public function dyBehaviorEvent($param1, $callchain) {
236 * //Do something, eg: $param1 += 13;
237 * return $callchain->dyBehaviorEvent($param1);
241 * This implementation of a behavior and dynamic event will flow through to the
242 * next behavior implementing the dynamic event. The first parameter is always
243 * return when it is supplied. Otherwise a dynamic event returns null.
245 * In the case of a class behavior, the object is also prepended to the dynamic
248 * class TObjectClassBehavior extends TClassBehavior {
249 * public function dyBehaviorEvent($hostobject, $param1, $callchain) {
250 * //Do something, eg: $param1 += $hostobject->getNumber();
251 * return $callchain->dyBehaviorEvent($param1);
255 * When calling a dynamic event, only the parameters are passed. The host object
256 * and the call chain are built into the framework.
258 * Global Event and Dynamic event catching
260 * Given that all global 'fx' events and dynamic 'dy' events are valid and
261 * operational, there is a mechanism for catching events called that are not
262 * implemented (similar to the built-in PHP method {@link __call}). When
263 * a dynamic or global event is called but a behavior does not implement it,
264 * yet desires to know when an undefined dynamic event is run, the behavior
265 * implements the interface {@link IDynamicMethods} and method {@link __dycall}.
267 * In the case of dynamic events, {@link __dycall} is supplied with the method
268 * name and its parameters. When a global event is raised, via {@link raiseEvent},
269 * the method is the event name and the parameters are supplied.
271 * When implemented, this catch-all mechanism is called for event global event event
272 * when implemented outside of a behavior. Within a behavior, it will also be called
273 * when the object to which the behavior is attached calls any unimplemented dynamic
274 * event. This is the fall-back mechanism for informing a class and/or behavior
275 * of when an global and/or undefined dynamic event is executed.
277 * @author Qiang Xue <qiang.xue@gmail.com>
278 * @author Brad Anderson <javalizard@mac.com>
285 * @var array event handler lists
290 * @var boolean if listening is enabled. Automatically turned on or off in
291 * constructor according to {@link getAutoGlobalListen}. Default false, off
293 private $_listeningenabled=false;
296 * @var array static registered global event handler lists
298 private static $_ue=array();
301 * @var boolean if object behaviors are on or off. default true, on
303 private $_behaviorsenabled=true;
306 * @var TPriorityMap list of object behaviors
311 * @var array static global class behaviors, these behaviors are added upon instantiation of a class
313 private static $_um=array();
317 * @const string the name of the global {@link raiseEvent} listener
319 const GLOBAL_RAISE_EVENT_LISTENER='fxGlobalListener';
323 * The common __construct
324 * If desired by the new object, this will auto install and listen to global event functions
325 * as defined by the object via 'fx' methods. This also attaches any predefined behaviors.
326 * This function installs all class behaviors in a class hierarchy from the deepest subclass
327 * through each parent to the top most class, TComponent.
329 public function __construct() {
330 if($this->getAutoGlobalListen())
333 $classes=array_reverse($this->getClassHierarchy(true));
334 foreach($classes as $class) {
335 if(isset(self::$_um[$class]))
336 $this->attachBehaviors(self::$_um[$class]);
342 * Tells TComponent whether or not to automatically listen to global events.
343 * Defaults to false because PHP variable cleanup is affected if this is true.
344 * When unsetting a variable that is listening to global events, {@link unlisten}
345 * must explicitly be called when cleaning variables allocation or else the global
346 * event registry will contain references to the old object. This is true for PHP 5.4
348 * Override this method by a subclass to change the setting. When set to true, this
349 * will enable {@link __construct} to call {@link listen}.
351 * @return boolean whether or not to auto listen to global events during {@link __construct}, default false
353 public function getAutoGlobalListen() {
359 * The common __destruct
360 * This unlistens from the global event routines if listening
362 * PHP 5.3 does not __destruct objects when they are nulled and thus unlisten must be
363 * called must be explicitly called.
365 public function __destruct() {
366 if($this->_listeningenabled)
372 * This utility function is a private array filter method. The array values
373 * that start with 'fx' are filtered in.
375 private function filter_prado_fx($name) {
376 return strncasecmp($name,'fx',2)===0;
381 * This returns an array of the class name and the names of all its parents. The base object first,
382 * {@link TComponent}, and the deepest subclass is last.
383 * @param boolean optional should the names be all lowercase true/false
384 * @return array array of strings being the class hierarchy of $this.
386 public function getClassHierarchy($lowercase = false)
388 $class=get_class($this);
389 $classes=array($class);
390 while($class=get_parent_class($class)){array_unshift($classes,$class);}
392 return array_map('strtolower',$classes);
398 * This adds an object's fx event handlers into the global broadcaster to listen into any
399 * broadcast global events called through {@link raiseEvent}
401 * Behaviors may implement the function:
403 * public function dyListen($globalEvents[, $chain]) {
404 * $this->listen(); //eg
407 * to be executed when listen is called. All attached behaviors are notified through dyListen.
409 * @return numeric the number of global events that were registered to the global event registry
411 public function listen() {
412 if($this->_listeningenabled)
415 $fx=array_filter(get_class_methods($this),array($this,'filter_prado_fx'));
417 foreach($fx as $func)
418 $this->attachEventHandler($func,array($this,$func));
420 if(is_a($this,'IDynamicMethods')) {
421 $this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER,array($this,'__dycall'));
422 array_push($fx,TComponent::GLOBAL_RAISE_EVENT_LISTENER);
425 $this->_listeningenabled=true;
427 $this->dyListen($fx);
433 * this removes an object's fx events from the global broadcaster
435 * Behaviors may implement the function:
437 * public function dyUnlisten($globalEvents[, $chain]) {
438 * $this->behaviorUnlisten(); //eg
441 * to be executed when listen is called. All attached behaviors are notified through dyUnlisten.
443 * @return numeric the number of global events that were unregistered from the global event registry
445 public function unlisten() {
446 if(!$this->_listeningenabled)
449 $fx=array_filter(get_class_methods($this),array($this,'filter_prado_fx'));
451 foreach($fx as $func)
452 $this->detachEventHandler($func,array($this,$func));
454 if(is_a($this,'IDynamicMethods')) {
455 $this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER,array($this,'__dycall'));
456 array_push($fx,TComponent::GLOBAL_RAISE_EVENT_LISTENER);
459 $this->_listeningenabled=false;
461 $this->dyUnlisten($fx);
467 * Gets the state of listening to global events
468 * @return boolean is Listening to global broadcast enabled
470 public function getListeningToGlobalEvents()
472 return $this->_listeningenabled;
478 * Do not call this method directly. This is a PHP magic method that we override
479 * to allow behaviors, dynamic events (intra-object/behavior events),
480 * undefined dynamic and global events, and
481 * to allow using the following syntax to call a property setter or getter.
483 * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
484 * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
487 * Additional object behaviors override class behaviors.
488 * dynamic and global events do not fail even if they aren't implemented.
489 * Any intra-object/behavior dynamic events that are not implemented by the behavior
490 * return the first function paramater or null when no parameters are specified.
492 * @param string method name that doesn't exist and is being called on the object
493 * @param mixed method parameters
494 * @throws TInvalidOperationException If the property is not defined or read-only or
495 * method is undefined
496 * @return mixed result of the method call, or false if 'fx' or 'dy' function but
497 * is not found in the class, otherwise it runs
499 public function __call($method, $args)
501 $getset=substr($method,0,3);
502 if(($getset=='get')||($getset=='set'))
504 $propname=substr($method,3);
505 $jsmethod=$getset.'js'.$propname;
506 if(method_exists($this,$jsmethod))
509 if($args[0]&&!($args[0] instanceof TJavaScriptString))
510 $args[0]=new TJavaScriptString($args[0]);
511 return call_user_func_array(array($this,$jsmethod),$args);
514 if (($getset=='set')&&method_exists($this,'getjs'.$propname))
515 throw new TInvalidOperationException('component_property_readonly',get_class($this),$method);
518 if($this->_m!==null&&$this->_behaviorsenabled)
520 if(strncasecmp($method,'dy',2)===0)
522 $callchain=new TCallChain($method);
523 foreach($this->_m->toArray() as $behavior)
525 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&(method_exists($behavior,$method)||($behavior instanceof IDynamicMethods)))
527 $behavior_args=$args;
528 if($behavior instanceof IClassBehavior)
529 array_unshift($behavior_args,$this);
530 $callchain->addCall(array($behavior,$method),$behavior_args);
534 if($callchain->getCount()>0)
535 return call_user_func_array(array($callchain,'call'),$args);
539 foreach($this->_m->toArray() as $behavior)
541 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&method_exists($behavior,$method))
543 if($behavior instanceof IClassBehavior)
544 array_unshift($args,$this);
545 return call_user_func_array(array($behavior,$method),$args);
551 if(strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
553 if($this instanceof IDynamicMethods)
554 return $this->__dycall($method,$args);
555 return isset($args[0])?$args[0]:null;
558 throw new TApplicationException('component_method_undefined',get_class($this),$method);
563 * Returns a property value or an event handler list by property or event name.
564 * Do not call this method. This is a PHP magic method that we override
565 * to allow using the following syntax to read a property:
567 * $value=$component->PropertyName;
568 * $value=$component->jsPropertyName; // return JavaScript literal
570 * and to obtain the event handler list for an event,
572 * $eventHandlerList=$component->EventName;
574 * This will also return the global event handler list when specifing an 'fx'
577 * $globalEventHandlerList=$component->fxEventName;
579 * When behaviors are enabled, this will return the behavior of a specific
580 * name, a property of a behavior, or an object 'on' event defined by the behavior.
581 * @param string the property name or the event name
582 * @return mixed the property value or the event handler list as {@link TPriorityList}
583 * @throws TInvalidOperationException if the property/event is not defined.
585 public function __get($name)
587 if(method_exists($this,$getter='get'.$name))
589 // getting a property
590 return $this->$getter();
592 else if(method_exists($this,$jsgetter='getjs'.$name))
594 // getting a javascript property
595 return (string)$this->$jsgetter();
597 else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
599 // getting an event (handler list)
600 $name=strtolower($name);
601 if(!isset($this->_e[$name]))
602 $this->_e[$name]=new TPriorityList;
603 return $this->_e[$name];
605 else if(strncasecmp($name,'fx',2)===0)
607 // getting a global event (handler list)
608 $name=strtolower($name);
609 if(!isset(self::$_ue[$name]))
610 self::$_ue[$name]=new TPriorityList;
611 return self::$_ue[$name];
613 else if($this->_behaviorsenabled)
615 // getting a behavior property/event (handler list)
616 if(isset($this->_m[$name]))
617 return $this->_m[$name];
618 else if($this->_m!==null)
620 foreach($this->_m->toArray() as $behavior)
622 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&
623 (property_exists($behavior,$name)||$behavior->canGetProperty($name)||$behavior->hasEvent($name)))
624 return $behavior->$name;
628 throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
632 * Sets value of a component property.
633 * Do not call this method. This is a PHP magic method that we override
634 * to allow using the following syntax to set a property or attach an event handler.
636 * $this->PropertyName=$value;
637 * $this->jsPropertyName=$value; // $value will be treated as a JavaScript literal
638 * $this->EventName=$handler;
639 * $this->fxEventName=$handler; //global event listener
641 * When behaviors are enabled, this will also set a behaviors properties and events.
642 * @param string the property name or event name
643 * @param mixed the property value or event handler
644 * @throws TInvalidOperationException If the property is not defined or read-only.
646 public function __set($name,$value)
648 if(method_exists($this,$setter='set'.$name))
650 if(strncasecmp($name,'js',2)===0&&$value&&!($value instanceof TJavaScriptLiteral))
651 $value = new TJavaScriptLiteral($value);
652 return $this->$setter($value);
654 else if(method_exists($this,$jssetter='setjs'.$name))
656 if($value&&!($value instanceof TJavaScriptString))
657 $value=new TJavaScriptString($value);
658 return $this->$jssetter($value);
660 else if((strncasecmp($name,'on',2)===0&&method_exists($this,$name))||strncasecmp($name,'fx',2)===0)
662 return $this->attachEventHandler($name,$value);
664 else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
667 foreach($this->_m->toArray() as $behavior)
669 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&
670 (property_exists($behavior,$name)||$behavior->canSetProperty($name)||$behavior->hasEvent($name))) {
671 $behavior->$name=$value;
675 if($sets)return $value;
679 if(method_exists($this,'get'.$name)||method_exists($this,'getjs'.$name))
681 throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
685 throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
690 * Checks if a property value is null, there are no events in the object
691 * event list or global event list registered under the name, and, if
692 * behaviors are enabled,
693 * Do not call this method. This is a PHP magic method that we override
694 * to allow using isset() to detect if a component property is set or not.
695 * This also works for global events. When behaviors are enabled, it
696 * will check for a behavior of the specified name, and also check
697 * the behavior for events and properties.
698 * @param string the property name or the event name
701 public function __isset($name)
703 if(method_exists($this,$getter='get'.$name))
704 return $this->$getter()!==null;
705 else if(method_exists($this,$jsgetter='getjs'.$name))
706 return $this->$jsgetter()!==null;
707 else if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
709 $name=strtolower($name);
710 return isset($this->_e[$name])&&$this->_e[$name]->getCount();
712 else if(strncasecmp($name,'fx',2)===0)
714 $name=strtolower($name);
715 return isset(self::$_ue[$name])&&self::$_ue[$name]->getCount();
717 else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
719 if(isset($this->_m[$name]))
721 foreach($this->_m->toArray() as $behavior)
723 if((!($behavior instanceof IBehavior)||$behavior->getEnabled()))
724 return isset($behavior->$name);
733 * Sets a component property to be null. Clears the object or global
734 * events. When enabled, loops through all behaviors and unsets the
736 * Do not call this method. This is a PHP magic method that we override
737 * to allow using unset() to set a component property to be null.
738 * @param string the property name or the event name
739 * @throws TInvalidOperationException if the property is read only.
742 public function __unset($name)
744 if(method_exists($this,$setter='set'.$name))
745 $this->$setter(null);
746 else if(method_exists($this,$jssetter='setjs'.$name))
747 $this->$jssetter(null);
748 else if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
749 $this->_e[strtolower($name)]->clear();
750 else if(strncasecmp($name,'fx',2)===0)
751 $this->getEventHandlers($name)->remove(array($this, $name));
752 else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
754 if(isset($this->_m[$name]))
755 $this->detachBehavior($name);
758 foreach($this->_m->toArray() as $behavior)
760 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())) {
761 unset($behavior->$name);
765 if(!$unset&&method_exists($this,'get'.$name))
766 throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
768 } else if(method_exists($this,'get'.$name))
769 throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
773 * Determines whether a property is defined.
774 * A property is defined if there is a getter or setter method
775 * defined in the class. Note, property names are case-insensitive.
776 * @param string the property name
777 * @return boolean whether the property is defined
779 public function hasProperty($name)
781 return $this->canGetProperty($name)||$this->canSetProperty($name);
785 * Determines whether a property can be read.
786 * A property can be read if the class has a getter method
787 * for the property name. Note, property name is case-insensitive.
788 * This also checks for getjs. When enabled, it loops through all
789 * active behaviors for the get property when undefined by the object.
790 * @param string the property name
791 * @return boolean whether the property can be read
793 public function canGetProperty($name)
795 if(method_exists($this,'get'.$name)||method_exists($this,'getjs'.$name))
797 else if($this->_m!==null&&$this->_behaviorsenabled)
799 foreach($this->_m->toArray() as $behavior)
801 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->canGetProperty($name))
809 * Determines whether a property can be set.
810 * A property can be written if the class has a setter method
811 * for the property name. Note, property name is case-insensitive.
812 * This also checks for setjs. When enabled, it loops through all
813 * active behaviors for the set property when undefined by the object.
814 * @param string the property name
815 * @return boolean whether the property can be written
817 public function canSetProperty($name)
819 if(method_exists($this,'set'.$name)||method_exists($this,'setjs'.$name))
821 else if($this->_m!==null&&$this->_behaviorsenabled)
823 foreach($this->_m->toArray() as $behavior)
825 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->canSetProperty($name))
833 * Evaluates a property path.
834 * A property path is a sequence of property names concatenated by '.' character.
835 * For example, 'Parent.Page' refers to the 'Page' property of the component's
836 * 'Parent' property value (which should be a component also).
837 * When a property is not defined by an object, this also loops through all
838 * active behaviors of the object.
839 * @param string property path
840 * @return mixed the property path value
842 public function getSubProperty($path)
845 foreach(explode('.',$path) as $property)
846 $object=$object->$property;
851 * Sets a value to a property path.
852 * A property path is a sequence of property names concatenated by '.' character.
853 * For example, 'Parent.Page' refers to the 'Page' property of the component's
854 * 'Parent' property value (which should be a component also).
855 * When a property is not defined by an object, this also loops through all
856 * active behaviors of the object.
857 * @param string property path
858 * @param mixed the property path value
860 public function setSubProperty($path,$value)
863 if(($pos=strrpos($path,'.'))===false)
867 $object=$this->getSubProperty(substr($path,0,$pos));
868 $property=substr($path,$pos+1);
870 $object->$property=$value;
874 * Determines whether an event is defined.
875 * An event is defined if the class has a method whose name is the event name
876 * prefixed with 'on', 'fx', or 'dy'.
877 * Every object responds to every 'fx' and 'dy' event as they are in a universally
878 * accepted event space. 'on' event must be declared by the object.
879 * When enabled, this will loop through all active behaviors for 'on' events
880 * defined by the behavior.
881 * Note, event name is case-insensitive.
882 * @param string the event name
885 public function hasEvent($name)
887 if((strncasecmp($name,'on',2)===0&&method_exists($this,$name))||strncasecmp($name,'fx',2)===0||strncasecmp($name,'dy',2)===0)
890 else if($this->_m!==null&&$this->_behaviorsenabled)
892 foreach($this->_m->toArray() as $behavior)
894 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEvent($name))
902 * Checks if an event has any handlers. This function also checks through all
903 * the behaviors for 'on' events when behaviors are enabled.
904 * 'dy' dynamic events are not handled by this function.
905 * @param string the event name
906 * @return boolean whether an event has been attached one or several handlers
908 public function hasEventHandler($name)
910 $name=strtolower($name);
911 if(strncasecmp($name,'fx',2)===0)
912 return isset(self::$_ue[$name])&&self::$_ue[$name]->getCount()>0;
915 if(isset($this->_e[$name])&&$this->_e[$name]->getCount()>0)
917 else if($this->_m!==null&&$this->_behaviorsenabled) {
918 foreach($this->_m->toArray() as $behavior)
920 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEventHandler($name))
929 * Returns the list of attached event handlers for an 'on' or 'fx' event. This function also
930 * checks through all the behaviors for 'on' event lists when behaviors are enabled.
931 * @return TPriorityList list of attached event handlers for an event
932 * @throws TInvalidOperationException if the event is not defined
934 public function getEventHandlers($name)
936 if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
938 $name=strtolower($name);
939 if(!isset($this->_e[$name]))
940 $this->_e[$name]=new TPriorityList;
941 return $this->_e[$name];
943 else if(strncasecmp($name,'fx',2)===0)
945 $name=strtolower($name);
946 if(!isset(self::$_ue[$name]))
947 self::$_ue[$name]=new TPriorityList;
948 return self::$_ue[$name];
950 else if($this->_m!==null&&$this->_behaviorsenabled)
952 foreach($this->_m->toArray() as $behavior)
954 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEvent($name))
955 return $behavior->getEventHandlers($name);
958 throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
962 * Attaches an event handler to an event.
964 * The handler must be a valid PHP callback, i.e., a string referring to
965 * a global function name, or an array containing two elements with
966 * the first element being an object and the second element a method name
967 * of the object. In Prado, you can also use method path to refer to
968 * an event handler. For example, array($object,'Parent.buttonClicked')
969 * uses a method path that refers to the method $object->Parent->buttonClicked(...).
971 * The event handler must be of the following signature,
973 * function handlerName($sender, $param) {}
974 * function handlerName($sender, $param, $name) {}
976 * where $sender represents the object that raises the event,
977 * and $param is the event parameter. $name refers to the event name
980 * This is a convenient method to add an event handler.
981 * It is equivalent to {@link getEventHandlers}($name)->add($handler).
982 * For complete management of event handlers, use {@link getEventHandlers}
983 * to get the event handler list first, and then do various
984 * {@link TPriorityList} operations to append, insert or remove
985 * event handlers. You may also do these operations like
986 * getting and setting properties, e.g.,
988 * $component->OnClick[]=array($object,'buttonClicked');
989 * $component->OnClick->insertAt(0,array($object,'buttonClicked'));
991 * which are equivalent to the following
993 * $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));
994 * $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
997 * Due to the nature of {@link getEventHandlers}, any active behaviors defining
998 * new 'on' events, this method will pass through to the behavior transparently.
1000 * @param string the event name
1001 * @param callback the event handler
1002 * @param numeric|null the priority of the handler, defaults to null which translates into the
1003 * default priority of 10.0 within {@link TPriorityList}
1004 * @throws TInvalidOperationException if the event does not exist
1006 public function attachEventHandler($name,$handler,$priority=null)
1008 $this->getEventHandlers($name)->add($handler,$priority);
1012 * Detaches an existing event handler.
1013 * This method is the opposite of {@link attachEventHandler}. It will detach
1014 * any 'on' events definedb by an objects active behaviors as well.
1015 * @param string event name
1016 * @param callback the event handler to be removed
1017 * @param numeric|false|null the priority of the handler, defaults to false which translates
1018 * to an item of any priority within {@link TPriorityList}; null means the default priority
1019 * @return boolean if the removal is successful
1021 public function detachEventHandler($name,$handler,$priority=false)
1023 if($this->hasEventHandler($name))
1027 $this->getEventHandlers($name)->remove($handler,$priority);
1038 * Raises an event. This raises both inter-object 'on' events and global 'fx' events.
1039 * This method represents the happening of an event and will
1040 * invoke all attached event handlers for the event in {@link TPriorityList} order.
1041 * This method does not handle intra-object/behavior dynamic 'dy' events.
1043 * There are ways to handle event responses. By defailt {@link EVENT_RESULT_FILTER},
1044 * all event responses are stored in an array, filtered for null responses, and returned.
1045 * If {@link EVENT_RESULT_ALL} is specified, all returned results will be stored along
1046 * with the sender and param in an array
1048 * $result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
1051 * If {@link EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
1052 * fed forward as the parameters for the next event. This allows for events to filter data
1053 * directly by affecting the event parameters
1055 * If a callable function is set in the response type or the post function filter is specified then the
1056 * result of each called event handler is post processed by the callable function. Used in
1057 * combination with {@link EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
1059 * When raising a global 'fx' event, registered handlers in the global event list for
1060 * {@link GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers. In this way,
1061 * these global events are always raised for every global 'fx' event. The registered handlers for global
1062 * raiseEvent events have priorities. Any registered global raiseEvent event handlers with a priority less than zero
1063 * are added before the main event handlers being raised and any registered global raiseEvent event handlers
1064 * with a priority equal or greater than zero are added after the main event handlers being raised. In this way
1065 * all {@link GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
1067 * Behaviors may implement the following functions:
1069 * public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1070 * return $name; //eg, the event name may be filtered/changed
1072 * public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name[, $chain]) {
1073 * return true; //should this particular handler be executed? true/false
1075 * public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response[, $chain]) {
1076 * //contains the per handler response
1078 * public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1079 * return $responses;
1082 * to be executed when raiseEvent is called. The 'intra' dynamic events are called per handler in
1085 * dyPreRaiseEvent has the effect of being able to change the event being raised. This intra
1086 * object/behavior event returns the name of the desired event to be raised. It will pass through
1087 * if no dynamic event is specified, or if the original event name is returned.
1088 * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
1089 * called for a specific raised event (and associated event arguments)
1090 * dyIntraRaiseEventPostHandler does not return anything. This allows behaviors to access the results
1091 * of an event handler in the per handler loop.
1092 * dyPostRaiseEvent returns the responses. This allows for any post processing of the event
1093 * results from the sum of all event handlers
1095 * When handling a catch-all {@link __dycall}, the method name is the name of the event
1096 * and the parameters are the sender, the param, and then the name of the event.
1098 * @param string the event name
1099 * @param mixed the event sender object
1100 * @param TEventParameter the event parameter
1101 * @param numeric how the results of the event are tabulated. default: {@link EVENT_RESULT_FILTER} The default filters out
1102 * null responses. optional
1103 * @param function any per handler filtering of the response result needed is passed through
1104 * this if not null. default: null. optional
1105 * @return mixed the results of the event
1106 * @throws TInvalidOperationException if the event is undefined
1107 * @throws TInvalidDataValueException If an event handler is invalid
1109 public function raiseEvent($name,$sender,$param,$responsetype=null,$postfunction=null)
1112 if(is_callable($responsetype))
1114 $postfunction=$responsetype;
1118 if($responsetype===null)
1119 $responsetype=TEventResults::EVENT_RESULT_FILTER;
1121 $name=strtolower($name);
1124 $name=$this->dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction);
1126 if($this->hasEventHandler($name)||$this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER))
1128 $handlers=$this->getEventHandlers($name);
1129 $handlerArray=$handlers->toArray();
1130 if(strncasecmp($name,'fx',2)===0&&$this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER))
1132 $globalhandlers=$this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
1133 $handlerArray=array_merge($globalhandlers->toArrayBelowPriority(0),$handlerArray,$globalhandlers->toArrayAbovePriority(0));
1136 foreach($handlerArray as $handler)
1138 if($this->dyIntraRaiseEventTestHandler($handler,$sender,$param,$name)===false)
1141 if(is_string($handler))
1143 if(($pos=strrpos($handler,'.'))!==false)
1145 $object=$this->getSubProperty(substr($handler,0,$pos));
1146 $method=substr($handler,$pos+1);
1147 if(method_exists($object,$method)||strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
1149 if($method=='__dycall')
1150 $response=$object->__dycall($name,array($sender,$param,$name));
1152 $response=$object->$method($sender,$param,$name);
1155 throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler);
1158 $response=call_user_func($handler,$sender,$param,$name);
1160 else if(is_callable($handler,true))
1162 list($object,$method)=$handler;
1163 if(is_string($object))
1164 $response=call_user_func($handler,$sender,$param,$name);
1167 if(($pos=strrpos($method,'.'))!==false)
1169 $object=$this->getSubProperty(substr($method,0,$pos));
1170 $method=substr($method,$pos+1);
1172 if(method_exists($object,$method)||strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
1174 if($method=='__dycall')
1175 $response=$object->__dycall($name,array($sender,$param,$name));
1177 $response=$object->$method($sender,$param,$name);
1180 throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler[1]);
1184 throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,gettype($handler));
1186 $this->dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response);
1189 $response=call_user_func_array($postfunction,array($sender,$param,$this,$response));
1191 if($responsetype&TEventResults::EVENT_RESULT_ALL)
1192 $responses[]=array('sender'=>$sender,'param'=>$param,'response'=>$response);
1194 $responses[]=$response;
1196 if($response!==null&&($responsetype&TEventResults::EVENT_RESULT_FEED_FORWARD))
1201 else if(strncasecmp($name,'on',2)===0&&!$this->hasEvent($name))
1202 throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
1204 if($responsetype&TEventResults::EVENT_RESULT_FILTER)
1205 $responses=array_filter($responses);
1207 $responses=$this->dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction);
1213 * Evaluates a PHP expression in the context of this control.
1215 * Behaviors may implement the function:
1217 * public function dyEvaluateExpressionFilter($expression, $chain) {
1218 * return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
1221 * to be executed when evaluateExpression is called. All attached behaviors are notified through
1222 * dyEvaluateExpressionFilter. The chaining is important in this function due to the filtering
1223 * pass-through effect.
1225 * @param string PHP expression
1226 * @return mixed the expression result
1227 * @throws TInvalidOperationException if the expression is invalid
1229 public function evaluateExpression($expression)
1231 $expression=$this->dyEvaluateExpressionFilter($expression);
1234 if(eval("\$result=$expression;")===false)
1235 throw new Exception('');
1240 throw new TInvalidOperationException('component_expression_invalid',get_class($this),$expression,$e->getMessage());
1245 * Evaluates a list of PHP statements.
1247 * Behaviors may implement the function:
1249 * public function dyEvaluateStatementsFilter($statements, $chain) {
1250 * return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
1253 * to be executed when evaluateStatements is called. All attached behaviors are notified through
1254 * dyEvaluateStatementsFilter. The chaining is important in this function due to the filtering
1255 * pass-through effect.
1257 * @param string PHP statements
1258 * @return string content echoed or printed by the PHP statements
1259 * @throws TInvalidOperationException if the statements are invalid
1261 public function evaluateStatements($statements)
1263 $statements=$this->dyEvaluateStatementsFilter($statements);
1267 if(eval($statements)===false)
1268 throw new Exception('');
1269 $content=ob_get_contents();
1275 throw new TInvalidOperationException('component_statements_invalid',get_class($this),$statements,$e->getMessage());
1280 * This method is invoked after the component is instantiated by a template.
1281 * When this method is invoked, the component's properties have been initialized.
1282 * The default implementation of this method will invoke
1283 * the potential parent component's {@link addParsedObject}.
1284 * This method can be overridden.
1286 * Behaviors may implement the function:
1288 * public function dyCreatedOnTemplate($parent, $chain) {
1289 * return $chain->dyCreatedOnTemplate($parent); //example
1292 * to be executed when createdOnTemplate is called. All attached behaviors are notified through
1293 * dyCreatedOnTemplate.
1295 * @param TComponent potential parent of this control
1296 * @see addParsedObject
1298 public function createdOnTemplate($parent)
1300 $parent=$this->dyCreatedOnTemplate($parent);
1301 $parent->addParsedObject($this);
1305 * Processes an object that is created during parsing template.
1306 * The object can be either a component or a static text string.
1307 * This method can be overridden to customize the handling of newly created objects in template.
1308 * Only framework developers and control developers should use this method.
1310 * Behaviors may implement the function:
1312 * public function dyAddParsedObject($object[, $chain]) {
1315 * to be executed when addParsedObject is called. All attached behaviors are notified through
1316 * dyAddParsedObject.
1318 * @param string|TComponent text string or component parsed and instantiated in template
1319 * @see createdOnTemplate
1321 public function addParsedObject($object)
1323 $this->dyAddParsedObject($object);
1328 * This is the method registered for all instanced objects should a class behavior be added after
1329 * the class is instanced. Only when the class to which the behavior is being added is in this
1330 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
1331 * @param $sender the application
1332 * @param $param TClassBehaviorEventParameter
1335 public function fxAttachClassBehavior($sender,$param) {
1336 if(in_array($param->getClass(),$this->getClassHierarchy(true)))
1337 return $this->attachBehavior($param->getName(),$param->getBehavior(),$param->getPriority());
1342 * This is the method registered for all instanced objects should a class behavior be removed after
1343 * the class is instanced. Only when the class to which the behavior is being added is in this
1344 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
1345 * @param $sender the application
1346 * @param $param TClassBehaviorEventParameter
1349 public function fxDetachClassBehavior($sender,$param) {
1350 if(in_array($param->getClass(),$this->getClassHierarchy(true)))
1351 return $this->detachBehavior($param->getName(),$param->getPriority());
1356 * This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
1357 * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
1358 * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
1359 * This is done so class behaviors are added last first.
1360 * @param string name the key of the class behavior
1361 * @param object|string class behavior or name of the object behavior per instance
1362 * @param string|class string of class or class on which to attach this behavior. Defaults to null which will error
1363 * but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
1366 * TPanel::attachClassBehavior('javascripts', (new TJsPanelBehavior())->init($this));
1368 * @param numeric|null priority of behavior, default: null the default priority of the {@link TPriorityList} Optional.
1369 * @throws TInvalidOperationException if the class behavior is being added to a {@link TComponent}; due to recursion.
1370 * @throws TInvalidOperationException if the class behavior is already defined
1373 public static function attachClassBehavior($name,$behavior,$class=null,$priority=null) {
1374 if(!$class&&function_exists('get_called_class'))
1375 $class=get_called_class();
1377 throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1379 if(!is_string($name))
1380 $name=get_class($name);
1381 $class=strtolower($class);
1382 if($class==='tcomponent')
1383 throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
1384 if(empty(self::$_um[$class]))
1385 self::$_um[$class]=array();
1386 if(isset(self::$_um[$class][$name]))
1387 throw new TInvalidOperationException('component_class_behavior_defined',$class,$name);
1388 $param=new TClassBehaviorEventParameter($class,$name,$behavior,$priority);
1389 self::$_um[$class]=array($name=>$param)+self::$_um[$class];
1390 $behaviorObject=is_string($behavior)?new $behavior:$behavior;
1391 return $behaviorObject->raiseEvent('fxAttachClassBehavior',null,$param);
1396 * This will remove a behavior from a class. It unregisters it from future instances and
1397 * pulls the changes from all the instances that are listening as well.
1398 * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
1399 * @param $name the key of the class behavior
1400 * @param $class string class on which to attach this behavior. Defaults to null.
1401 * @param $priority numeric|null|false priority. false is any priority, null is default
1402 * {@link TPriorityList} priority, and numeric is a specific priority.
1403 * @throws Exception if the the class cannot be derived from Late Static Binding and is not
1404 * not supplied as a parameter.
1407 public static function detachClassBehavior($name,$class=null,$priority=false) {
1408 if(!$class&&function_exists('get_called_class'))
1409 $class=get_called_class();
1411 throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1413 $class=strtolower($class);
1414 if(!is_string($name))
1415 $name=get_class($name);
1416 if(empty(self::$_um[$class])||!isset(self::$_um[$class][$name]))
1418 $param=self::$_um[$class][$name];
1419 $behavior=$param->getBehavior();
1420 unset(self::$_um[$class][$name]);
1421 $behaviorObject=is_string($behavior)?new $behavior:$behavior;
1422 return $behaviorObject->raiseEvent('fxDetachClassBehavior',null,$param);
1426 * Returns the named behavior object.
1427 * The name 'asa' stands for 'as a'.
1428 * @param string the behavior name
1429 * @return IBehavior the behavior object, or null if the behavior does not exist
1432 public function asa($behaviorname)
1434 return isset($this->_m[$behaviorname])?$this->_m[$behaviorname]:null;
1438 * Returns whether or not the object or any of the behaviors are of a particular class.
1439 * The name 'isa' stands for 'is a'. This first checks if $this is an instanceof the class.
1440 * It then checks each Behavior. If a behavior implements {@link IInstanceCheck},
1441 * then the behavior can determine what it is an instanceof. If this behavior function returns true,
1442 * then this method returns true. If the behavior instance checking function returns false,
1443 * then no further checking is performed as it is assumed to be correct.
1445 * If the behavior instance check function returns nothing or null or the behavior
1446 * doesn't implement the {@link IInstanceCheck} interface, then the default instanceof occurs.
1447 * The default isa behavior is to check if the behavior is an instanceof the class.
1449 * The behavior {@link IInstanceCheck} is to allow a behavior to have the host object
1450 * act as a completely different object.
1452 * @param class or string
1453 * @return boolean whether or not the object or a behavior is an instance of a particular class
1456 public function isa($class)
1458 if($this instanceof $class)
1460 if($this->_m!==null&&$this->_behaviorsenabled)
1461 foreach($this->_m->toArray() as $behavior){
1462 if(($behavior instanceof IBehavior)&&!$behavior->getEnabled())
1466 if(($behavior->isa('IInstanceCheck'))&&$check=$behavior->isinstanceof($class,$this))
1468 if($check===null&&($behavior->isa($class)))
1475 * Attaches a list of behaviors to the component.
1476 * Each behavior is indexed by its name and should be an instance of
1477 * {@link IBehavior}, a string specifying the behavior class, or a
1478 * {@link TClassBehaviorEventParameter}.
1479 * @param array list of behaviors to be attached to the component
1482 public function attachBehaviors($behaviors)
1484 foreach($behaviors as $name=>$behavior)
1485 if($behavior instanceof TClassBehaviorEventParameter)
1486 $this->attachBehavior($behavior->getName(),$behavior->getBehavior(),$behavior->getPriority());
1488 $this->attachBehavior($name,$behavior);
1492 * Detaches select behaviors from the component.
1493 * Each behavior is indexed by its name and should be an instance of
1494 * {@link IBehavior}, a string specifying the behavior class, or a
1495 * {@link TClassBehaviorEventParameter}.
1496 * @param array list of behaviors to be detached from the component
1499 public function detachBehaviors($behaviors)
1501 if($this->_m!==null)
1503 foreach($behaviors as $name=>$behavior)
1504 if($behavior instanceof TClassBehaviorEventParameter)
1505 $this->detachBehavior($behavior->getName(),$behavior->getPriority());
1507 $this->detachBehavior(is_string($behavior)?$behavior:$name);
1512 * Detaches all behaviors from the component.
1515 public function clearBehaviors()
1517 if($this->_m!==null)
1519 foreach($this->_m->toArray() as $name=>$behavior)
1520 $this->detachBehavior($name);
1526 * Attaches a behavior to this component.
1527 * This method will create the behavior object based on the given
1528 * configuration. After that, the behavior object will be initialized
1529 * by calling its {@link IBehavior::attach} method.
1531 * Already attached behaviors may implement the function:
1533 * public function dyAttachBehavior($name,$behavior[, $chain]) {
1536 * to be executed when attachBehavior is called. All attached behaviors are notified through
1539 * @param string the behavior's name. It should uniquely identify this behavior.
1540 * @param mixed the behavior configuration. This is passed as the first
1541 * parameter to {@link YiiBase::createComponent} to create the behavior object.
1542 * @return IBehavior the behavior object
1545 public function attachBehavior($name,$behavior,$priority=null)
1547 if(is_string($behavior))
1548 $behavior=Prado::createComponent($behavior);
1549 if(!($behavior instanceof IBaseBehavior))
1550 throw new TInvalidDataTypeException('component_not_a_behavior',get_class($behavior));
1551 if($behavior instanceof IBehavior)
1552 $behavior->setEnabled(true);
1553 if($this->_m===null)
1554 $this->_m=new TPriorityMap;
1555 $behavior->attach($this);
1556 $this->dyAttachBehavior($name,$behavior);
1557 $this->_m->add($name,$behavior,$priority);
1562 * Detaches a behavior from the component.
1563 * The behavior's {@link IBehavior::detach} method will be invoked.
1565 * Behaviors may implement the function:
1567 * public function dyDetachBehavior($name,$behavior[, $chain]) {
1570 * to be executed when detachBehavior is called. All attached behaviors are notified through
1573 * @param string the behavior's name. It uniquely identifies the behavior.
1574 * @param numeric the behavior's priority. This defaults to false, aka any priority.
1575 * @return IBehavior the detached behavior. Null if the behavior does not exist.
1578 public function detachBehavior($name,$priority=false)
1580 if($this->_m!=null&&isset($this->_m[$name]))
1582 $this->_m[$name]->detach($this);
1583 $behavior=$this->_m->itemAt($name);
1584 $this->_m->remove($name,$priority);
1585 $this->dyDetachBehavior($name,$behavior);
1591 * Enables all behaviors attached to this component independent of the behaviors
1593 * Behaviors may implement the function:
1595 * public function dyEnableBehaviors($name,$behavior[, $chain]) {
1598 * to be executed when enableBehaviors is called. All attached behaviors are notified through
1599 * dyEnableBehaviors.
1602 public function enableBehaviors()
1604 if(!$this->_behaviorsenabled)
1606 $this->_behaviorsenabled=true;
1607 $this->dyEnableBehaviors();
1612 * Disables all behaviors attached to this component independent of the behaviors
1614 * Behaviors may implement the function:
1616 * public function dyDisableBehaviors($name,$behavior[, $chain]) {
1619 * to be executed when disableBehaviors is called. All attached behaviors are notified through
1620 * dyDisableBehaviors.
1623 public function disableBehaviors()
1625 if($this->_behaviorsenabled)
1627 $this->dyDisableBehaviors();
1628 $this->_behaviorsenabled=false;
1634 * Returns if all the behaviors are turned on or off for the object.
1635 * @return boolean whether or not all behaviors are enabled (true) or not (false)
1638 public function getBehaviorsEnabled()
1640 return $this->_behaviorsenabled;
1644 * Enables an attached object behavior. This cannot enable or disable whole class behaviors.
1645 * A behavior is only effective when it is enabled.
1646 * A behavior is enabled when first attached.
1648 * Behaviors may implement the function:
1650 * public function dyEnableBehavior($name,$behavior[, $chain]) {
1653 * to be executed when enableBehavior is called. All attached behaviors are notified through
1656 * @param string the behavior's name. It uniquely identifies the behavior.
1659 public function enableBehavior($name)
1661 if($this->_m!=null&&isset($this->_m[$name])){
1662 if($this->_m[$name] instanceof IBehavior) {
1663 $this->_m[$name]->setEnabled(true);
1664 $this->dyEnableBehavior($name,$this->_m[$name]);
1673 * Disables an attached behavior. This cannot enable or disable whole class behaviors.
1674 * A behavior is only effective when it is enabled.
1676 * Behaviors may implement the function:
1678 * public function dyDisableBehavior($name,$behavior[, $chain]) {
1681 * to be executed when disableBehavior is called. All attached behaviors are notified through
1682 * dyDisableBehavior.
1684 * @param string the behavior's name. It uniquely identifies the behavior.
1687 public function disableBehavior($name)
1689 if($this->_m!=null&&isset($this->_m[$name])){
1690 if($this->_m[$name] instanceof IBehavior) {
1691 $this->_m[$name]->setEnabled(false);
1692 $this->dyDisableBehavior($name,$this->_m[$name]);
1701 * Do not call this method. This is a PHP magic method that will be called automatically
1702 * after any unserialization; it can perform reinitialization tasks on the object.
1704 public function __wakeup()
1706 if ($this->_e===null)
1707 $this->_e = array();
1711 * Returns an array with the names of all variables of that object that should be serialized.
1712 * Do not call this method. This is a PHP magic method that will be called automatically
1713 * prior to any serialization.
1715 public function __sleep()
1718 $a = array_keys($a);
1720 if($this->_listeningenabled===false)
1721 $exprops[] = "\0TComponent\0_listeningenabled";
1722 if($this->_behaviorsenabled===true)
1723 $exprops[] = "\0TComponent\0_behaviorsenabled";
1724 if ($this->_e===array())
1725 $exprops[] = "\0TComponent\0_e";
1726 if ($this->_m===null)
1727 $exprops[] = "\0TComponent\0_m";
1728 return array_diff($a,$exprops);
1734 * IDynamicMethods interface.
1735 * IDynamicMethods marks an object to receive undefined global or dynamic events.
1737 * @author Brad Anderson <javalizard@mac.com>
1742 interface IDynamicMethods
1744 public function __dycall($method,$args);
1750 * TClassBehaviorEventParameter class.
1751 * TClassBehaviorEventParameter is the parameter sent with the class behavior changes.
1753 * @author Brad Anderson <javalizard@mac.com>
1758 class TClassBehaviorEventParameter extends TEventParameter
1766 * Holds the parameters for the Class Behavior Events
1767 * @param string $class this is the class to get the behavior
1768 * @param string $name the name of the behavior
1769 * @param object $behavior this is the behavior to implement the class behavior
1771 public function __construct($class,$name,$behavior,$priority)
1773 $this->_class=$class;
1775 $this->_behavior=$behavior;
1776 $this->_priority=$priority;
1780 * This is the class to get the behavior
1781 * @return string the class to get the behavior
1783 public function getClass()
1785 return $this->_class;
1789 * name of the behavior
1790 * @return string the name to get the behavior
1792 public function getName()
1794 return $this->_name;
1798 * This is the behavior which the class is to get
1799 * @return object the behavior to implement
1801 public function getBehavior()
1803 return $this->_behavior;
1807 * This is the priority which the behavior is to get
1808 * @return numeric the priority of the behavior
1810 public function getPriority()
1812 return $this->_priority;
1818 * TEnumerable class.
1819 * TEnumerable is the base class for all enumerable types.
1820 * To define an enumerable type, extend TEnumberable and define string constants.
1821 * Each constant represents an enumerable value.
1822 * The constant name must be the same as the constant value.
1825 * class TTextAlign extends TEnumerable
1827 * const Left='Left';
1828 * const Right='Right';
1831 * Then, one can use the enumerable values such as TTextAlign::Left and
1832 * TTextAlign::Right.
1834 * @author Qiang Xue <qiang.xue@gmail.com>
1838 class TEnumerable implements Iterator
1840 private $_enums=array();
1842 public function __construct() {
1843 $reflection=new ReflectionClass($this);
1844 $this->_enums=$reflection->getConstants();
1847 public function current() {
1848 return current($this->_enums);
1851 public function key() {
1852 return key($this->_enums);
1855 public function next() {
1856 return next($this->_enums);
1859 public function rewind() {
1860 reset($this->_enums);
1863 public function valid() {
1864 return $this->current()!==false;
1869 * TPropertyValue class
1871 * TPropertyValue is a utility class that provides static methods
1872 * to convert component property values to specific types.
1874 * TPropertyValue is commonly used in component setter methods to ensure
1875 * the new property value is of specific type.
1876 * For example, a boolean-typed property setter method would be as follows,
1878 * function setPropertyName($value) {
1879 * $value=TPropertyValue::ensureBoolean($value);
1880 * // $value is now of boolean type
1884 * Properties can be of the following types with specific type conversion rules:
1885 * - string: a boolean value will be converted to 'true' or 'false'.
1886 * - boolean: string 'true' (case-insensitive) will be converted to true,
1887 * string 'false' (case-insensitive) will be converted to false.
1890 * - array: string starting with '(' and ending with ')' will be considered as
1891 * as an array expression and will be evaluated. Otherwise, an array
1892 * with the value to be ensured is returned.
1894 * - enum: enumerable type, represented by an array of strings.
1896 * @author Qiang Xue <qiang.xue@gmail.com>
1900 class TPropertyValue
1903 * Converts a value to boolean type.
1904 * Note, string 'true' (case-insensitive) will be converted to true,
1905 * string 'false' (case-insensitive) will be converted to false.
1906 * If a string represents a non-zero number, it will be treated as true.
1907 * @param mixed the value to be converted.
1910 public static function ensureBoolean($value)
1912 if (is_string($value))
1913 return strcasecmp($value,'true')==0 || $value!=0;
1915 return (boolean)$value;
1919 * Converts a value to string type.
1920 * Note, a boolean value will be converted to 'true' if it is true
1921 * and 'false' if it is false.
1922 * @param mixed the value to be converted.
1925 public static function ensureString($value)
1927 if (TJavaScript::isJsLiteral($value))
1929 if (is_bool($value))
1930 return $value?'true':'false';
1932 return (string)$value;
1936 * Converts a value to integer type.
1937 * @param mixed the value to be converted.
1940 public static function ensureInteger($value)
1942 return (integer)$value;
1946 * Converts a value to float type.
1947 * @param mixed the value to be converted.
1950 public static function ensureFloat($value)
1952 return (float)$value;
1956 * Converts a value to array type. If the value is a string and it is
1957 * in the form (a,b,c) then an array consisting of each of the elements
1958 * will be returned. If the value is a string and it is not in this form
1959 * then an array consisting of just the string will be returned. If the value
1960 * is not a string then
1961 * @param mixed the value to be converted.
1964 public static function ensureArray($value)
1966 if(is_string($value))
1968 $value = trim($value);
1969 $len = strlen($value);
1970 if ($len >= 2 && $value[0] == '(' && $value[$len-1] == ')')
1972 eval('$array=array'.$value.';');
1976 return $len>0?array($value):array();
1979 return (array)$value;
1983 * Converts a value to object type.
1984 * @param mixed the value to be converted.
1987 public static function ensureObject($value)
1989 return (object)$value;
1993 * Converts a value to enum type.
1995 * This method checks if the value is of the specified enumerable type.
1996 * A value is a valid enumerable value if it is equal to the name of a constant
1997 * in the specified enumerable type (class).
1998 * For more details about enumerable, see {@link TEnumerable}.
2000 * For backward compatibility, this method also supports sanity
2001 * check of a string value to see if it is among the given list of strings.
2002 * @param mixed the value to be converted.
2003 * @param mixed class name of the enumerable type, or array of valid enumeration values. If this is not an array,
2004 * the method considers its parameters are of variable length, and the second till the last parameters are enumeration values.
2005 * @return string the valid enumeration value
2006 * @throws TInvalidDataValueException if the original value is not in the string array.
2008 public static function ensureEnum($value,$enums)
2010 static $types=array();
2011 if(func_num_args()===2 && is_string($enums))
2013 if(!isset($types[$enums]))
2014 $types[$enums]=new ReflectionClass($enums);
2015 if($types[$enums]->hasConstant($value))
2018 throw new TInvalidDataValueException(
2019 'propertyvalue_enumvalue_invalid',$value,
2020 implode(' | ',$types[$enums]->getConstants()));
2022 else if(!is_array($enums))
2024 $enums=func_get_args();
2025 array_shift($enums);
2027 if(in_array($value,$enums,true))
2030 throw new TInvalidDataValueException('propertyvalue_enumvalue_invalid',$value,implode(' | ',$enums));
2034 * Converts the value to 'null' if the given value is empty
2035 * @param mixed value to be converted
2036 * @return mixed input or NULL if input is empty
2038 public static function ensureNullIfEmpty($value)
2040 return empty($value)?null:$value;
2045 * TEventParameter class.
2046 * TEventParameter is the base class for all event parameter classes.
2048 * @author Qiang Xue <qiang.xue@gmail.com>
2052 class TEventParameter extends TComponent
2056 class TEventResults extends TEnumerable {
2057 const EVENT_RESULT_FEED_FORWARD=1;
2058 const EVENT_RESULT_FILTER=2;
2059 const EVENT_RESULT_ALL=4;
2063 * TComponentReflection class.
2065 * TComponentReflection provides functionalities to inspect the public/protected
2066 * properties, events and methods defined in a class.
2068 * The following code displays the properties and events defined in {@link TDataGrid},
2070 * $reflection=new TComponentReflection('TDataGrid');
2071 * Prado::varDump($reflection->getProperties());
2072 * Prado::varDump($reflection->getEvents());
2075 * @author Qiang Xue <qiang.xue@gmail.com>
2079 class TComponentReflection extends TComponent
2081 private $_className;
2082 private $_properties=array();
2083 private $_events=array();
2084 private $_methods=array();
2088 * @param object|string the component instance or the class name
2089 * @throws TInvalidDataTypeException if the object is not a component
2091 public function __construct($component)
2093 if(is_string($component) && class_exists($component,false))
2094 $this->_className=$component;
2095 else if(is_object($component))
2096 $this->_className=get_class($component);
2098 throw new TInvalidDataTypeException('componentreflection_class_invalid');
2102 private function isPropertyMethod($method)
2104 $methodName=$method->getName();
2105 return $method->getNumberOfRequiredParameters()===0
2106 && strncasecmp($methodName,'get',3)===0
2107 && isset($methodName[3]);
2110 private function isEventMethod($method)
2112 $methodName=$method->getName();
2113 return strncasecmp($methodName,'on',2)===0
2114 && isset($methodName[2]);
2117 private function reflect()
2119 $class=new ReflectionClass($this->_className);
2120 $properties=array();
2123 $isComponent=is_subclass_of($this->_className,'TComponent') || strcasecmp($this->_className,'TComponent')===0;
2124 foreach($class->getMethods() as $method)
2126 if($method->isPublic() || $method->isProtected())
2128 $methodName=$method->getName();
2129 if(!$method->isStatic() && $isComponent)
2131 if($this->isPropertyMethod($method))
2132 $properties[substr($methodName,3)]=$method;
2133 else if($this->isEventMethod($method))
2136 $events[$methodName]=$method;
2139 if(strncmp($methodName,'__',2)!==0)
2140 $methods[$methodName]=$method;
2145 foreach($properties as $name=>$method)
2147 $this->_properties[$name]=array(
2148 'type'=>$this->determinePropertyType($method),
2149 'readonly'=>!$class->hasMethod('set'.$name),
2150 'protected'=>$method->isProtected(),
2151 'class'=>$method->getDeclaringClass()->getName(),
2152 'comments'=>$method->getDocComment()
2154 $reserved['get'.strtolower($name)]=1;
2155 $reserved['set'.strtolower($name)]=1;
2158 foreach($events as $name=>$method)
2160 $this->_events[$name]=array(
2161 'class'=>$method->getDeclaringClass()->getName(),
2162 'protected'=>$method->isProtected(),
2163 'comments'=>$method->getDocComment()
2165 $reserved[strtolower($name)]=1;
2168 foreach($methods as $name=>$method)
2170 if(!isset($reserved[strtolower($name)]))
2171 $this->_methods[$name]=array(
2172 'class'=>$method->getDeclaringClass()->getName(),
2173 'protected'=>$method->isProtected(),
2174 'static'=>$method->isStatic(),
2175 'comments'=>$method->getDocComment()
2181 * Determines the property type.
2182 * This method uses the doc comment to determine the property type.
2183 * @param ReflectionMethod
2184 * @return string the property type, '{unknown}' if type cannot be determined from comment
2186 protected function determinePropertyType($method)
2188 $comment=$method->getDocComment();
2189 if(preg_match('/@return\\s+(.*?)\\s+/',$comment,$matches))
2196 * @return string class name of the component
2198 public function getClassName()
2200 return $this->_className;
2204 * @return array list of component properties. Array keys are property names.
2205 * Each array element is of the following structure:
2206 * [type]=>property type,
2207 * [readonly]=>whether the property is read-only,
2208 * [protected]=>whether the method is protected or not
2209 * [class]=>the class where the property is inherited from,
2210 * [comments]=>comments associated with the property.
2212 public function getProperties()
2214 return $this->_properties;
2218 * @return array list of component events. Array keys are event names.
2219 * Each array element is of the following structure:
2220 * [protected]=>whether the event is protected or not
2221 * [class]=>the class where the event is inherited from.
2222 * [comments]=>comments associated with the event.
2224 public function getEvents()
2226 return $this->_events;
2230 * @return array list of public/protected methods. Array keys are method names.
2231 * Each array element is of the following structure:
2232 * [protected]=>whether the method is protected or not
2233 * [static]=>whether the method is static or not
2234 * [class]=>the class where the property is inherited from,
2235 * [comments]=>comments associated with the event.
2237 public function getMethods()
2239 return $this->_methods;
2244 * IBaseBehavior interface is the base behavior class from which all other
2245 * behaviors types are derived
2247 * @author Brad Anderson <javalizard@mac.com>
2252 interface IBaseBehavior {
2254 * Attaches the behavior object to the component.
2255 * @param CComponent the component that this behavior is to be attached to.
2257 public function attach($component);
2259 * Detaches the behavior object from the component.
2260 * @param CComponent the component that this behavior is to be detached from.
2262 public function detach($component);
2266 * IBehavior interfaces is implemented by instance behavior classes.
2268 * A behavior is a way to enhance a component with additional methods and
2269 * events that are defined in the behavior class and not available in the
2270 * class. Objects may signal behaviors through dynamic events.
2272 * @author Brad Anderson <javalizard@mac.com>
2277 interface IBehavior extends IBaseBehavior
2280 * @return boolean whether this behavior is enabled
2282 public function getEnabled();
2284 * @param boolean whether this behavior is enabled
2286 public function setEnabled($value);
2291 * IClassBehavior interface is implements behaviors across all instances of
2292 * a particular class
2294 * Any calls to functions not present in the original object but to behaviors
2295 * derived from this class, will have inserted as the first argument parameter
2296 * the object containing the behavior.
2300 * $objWithClassBehavior->MethodOfClassBehavior(1, 20);
2302 * will be acted within the class behavior like this:
2304 * public function MethodOfClassBehavior($object, $firstParam, $secondParam){
2305 * // $object === $objWithClassBehavior, $firstParam === 1, $secondParam === 20
2309 * This also holds for 'dy' events as well. For dynamic events, method arguments would be:
2311 * public function dyMethodOfClassBehavior($object, $firstParam, $secondParam, $callchain){
2312 * // $object === $objWithClassBehavior, $firstParam === 1, $secondParam === 20, $callchain instanceof {@link TCallChain}
2316 * @author Brad Anderson <javalizard@mac.com>
2321 interface IClassBehavior extends IBaseBehavior {
2326 * IInstanceCheck This interface allows objects to determine their own
2327 * 'instanceof' results when {@link TComponent::isa} is called. This is
2328 * important with behaviors because behaviors may want to look like
2329 * particular objects other than themselves.
2331 * @author Brad Anderson <javalizard@mac.com>
2336 interface IInstanceCheck {
2338 * The method checks $this or, if needed, the parameter $instance is of type
2339 * class. In the case of a Class Behavior, the instance to which the behavior
2340 * is attached may be important to determine if $this is an instance
2341 * of a particular class.
2342 * @param class|string the component that this behavior is checking if it is an instanceof.
2343 * @param object the object which the behavior is attached to. default: null
2344 * @return boolean|null if the this or the instance is of type class. When null, no information could be derived and
2345 * the default mechanisms take over.
2347 public function isinstanceof($class,$instance=null);
2351 * TJavaScriptLiteral class that encloses string literals that are not
2352 * supposed to be escaped by {@link TJavaScript::encode() }
2354 * Since Prado 3.2 all the data that gets sent clientside inside a javascript statement
2355 * is encoded by default to avoid any kind of injection.
2356 * Sometimes there's the need to bypass this encoding and send raw javascript code.
2357 * To ensure that a string doesn't get encoded by {@link TJavaScript::encode() },
2358 * construct a new TJavaScriptLiteral:
2360 * // a javascript test string
2361 * $js="alert('hello')";
2362 * // the string in $raw will not be encoded when sent clientside inside a javascript block
2363 * $raw=new TJavaScriptLiteral($js);
2371 class TJavaScriptLiteral
2375 public function __construct($s)
2380 public function __toString()
2382 return (string)$this->_s;
2385 public function toJavaScriptLiteral()
2387 return $this->__toString();
2392 * TJavaScriptString class is an internal class that marks strings that will be
2393 * forcibly encoded when rendered inside a javascript block
2398 class TJavaScriptString extends TJavaScriptLiteral
2400 public function toJavaScriptLiteral()
2402 return TJavaScript::jsonEncode((string)$this->_s,JSON_HEX_QUOT | JSON_HEX_APOS | JSON_HEX_TAG);