Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** TComponent, TPropertyValue classes** @author Qiang Xue <qiang.xue@gmail.com>* @link http://www.pradosoft.com/* @copyright Copyright © 2005-2008 PradoSoft* @license http://www.pradosoft.com/license/* @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System*//*** TComponent class** TComponent is the base class for all PRADO components.* TComponent implements the protocol of defining, using properties and events.** A property is defined by a getter method, and/or a setter method.* Properties can be accessed in the way like accessing normal object members.* Reading or writing a property will cause the invocation of the corresponding* getter or setter method, e.g.,* <code>* $a=$this->Text; // equivalent to $a=$this->getText();* $this->Text='abc'; // equivalent to $this->setText('abc');* </code>* The signatures of getter and setter methods are as follows,* <code>* // getter, defines a readable property 'Text'* function getText() { ... }* // setter, defines a writable property 'Text', with $value being the value to be set to the property* function setText($value) { ... }* </code>* Property names are case-insensitive. It is recommended that they are written* in the format of concatenated words, with the first letter of each word* capitalized (e.g. DisplayMode, ItemStyle).** An event is defined by the presence of a method whose name starts with 'on'.* The event name is the method name and is thus case-insensitive.* An event can be attached with one or several methods (called event handlers).* An event can be raised by calling {@link raiseEvent} method, upon which* the attached event handlers will be invoked automatically in the order they* are attached to the event. Event handlers must have the following signature,* <code>* function eventHandlerFuncName($sender,$param) { ... }* </code>* where $sender refers to the object who is responsible for the raising of the event,* and $param refers to a structure that may contain event-specific information.* To raise an event (assuming named as 'Click') of a component, use* <code>* $component->raiseEvent('OnClick');* </code>* To attach an event handler to an event, use one of the following ways,* <code>* $component->OnClick=$callback; // or $component->OnClick->add($callback);* $$component->attachEventHandler('OnClick',$callback);* </code>* The first two ways make use of the fact that $component->OnClick refers to* the event handler list {@link TList} for the 'OnClick' event.* The variable $callback contains the definition of the event handler that can* be either a string referring to a global function name, or an array whose* first element refers to an object and second element a method name/path that* is reachable by the object, e.g.* - 'buttonClicked' : buttonClicked($sender,$param);* - array($object,'buttonClicked') : $object->buttonClicked($sender,$param);* - array($object,'MainContent.SubmitButton.buttonClicked') :* $object->MainContent->SubmitButton->buttonClicked($sender,$param);** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System* @since 3.0*/class TComponent{/*** @var array event handler lists*/private $_e=array();/*** Returns a property value or an event handler list by property or event name.* Do not call this method. This is a PHP magic method that we override* to allow using the following syntax to read a property:* <code>* $value=$component->PropertyName;* </code>* and to obtain the event handler list for an event,* <code>* $eventHandlerList=$component->EventName;* </code>* @param string the property name or the event name* @return mixed the property value or the event handler list* @throws TInvalidOperationException if the property/event is not defined.*/public function __get($name){$getter='get'.$name;if(method_exists($this,$getter)){// getting a propertyreturn $this->$getter();}else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)){// getting an event (handler list)$name=strtolower($name);if(!isset($this->_e[$name]))$this->_e[$name]=new TList;return $this->_e[$name];}else{throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);}}/*** Sets value of a component property.* Do not call this method. This is a PHP magic method that we override* to allow using the following syntax to set a property or attach an event handler.* <code>* $this->PropertyName=$value;* $this->EventName=$handler;* </code>* @param string the property name or event name* @param mixed the property value or event handler* @throws TInvalidOperationException If the property is not defined or read-only.*/public function __set($name,$value){$setter='set'.$name;if(method_exists($this,$setter)){$this->$setter($value);}else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)){$this->attachEventHandler($name,$value);}else if(method_exists($this,'get'.$name)){throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);}else{throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);}}/*** Determines whether a property is defined.* A property is defined if there is a getter or setter method* defined in the class. Note, property names are case-insensitive.* @param string the property name* @return boolean whether the property is defined*/public function hasProperty($name){return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);}/*** Determines whether a property can be read.* A property can be read if the class has a getter method* for the property name. Note, property name is case-insensitive.* @param string the property name* @return boolean whether the property can be read*/public function canGetProperty($name){return method_exists($this,'get'.$name);}/*** Determines whether a property can be set.* A property can be written if the class has a setter method* for the property name. Note, property name is case-insensitive.* @param string the property name* @return boolean whether the property can be written*/public function canSetProperty($name){return method_exists($this,'set'.$name);}/*** Evaluates a property path.* A property path is a sequence of property names concatenated by '.' character.* For example, 'Parent.Page' refers to the 'Page' property of the component's* 'Parent' property value (which should be a component also).* @param string property path* @return mixed the property path value*/public function getSubProperty($path){$object=$this;foreach(explode('.',$path) as $property)$object=$object->$property;return $object;}/*** Sets a value to a property path.* A property path is a sequence of property names concatenated by '.' character.* For example, 'Parent.Page' refers to the 'Page' property of the component's* 'Parent' property value (which should be a component also).* @param string property path* @param mixed the property path value*/public function setSubProperty($path,$value){$object=$this;if(($pos=strrpos($path,'.'))===false)$property=$path;else{$object=$this->getSubProperty(substr($path,0,$pos));$property=substr($path,$pos+1);}$object->$property=$value;}/*** Determines whether an event is defined.* An event is defined if the class has a method whose name is the event name prefixed with 'on'.* Note, event name is case-insensitive.* @param string the event name* @return boolean*/public function hasEvent($name){return strncasecmp($name,'on',2)===0 && method_exists($this,$name);}/*** @return boolean whether an event has been attached one or several handlers*/public function hasEventHandler($name){$name=strtolower($name);return isset($this->_e[$name]) && $this->_e[$name]->getCount()>0;}/*** Returns the list of attached event handlers for an event.* @return TList list of attached event handlers for an event* @throws TInvalidOperationException if the event is not defined*/public function getEventHandlers($name){if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)){$name=strtolower($name);if(!isset($this->_e[$name]))$this->_e[$name]=new TList;return $this->_e[$name];}elsethrow new TInvalidOperationException('component_event_undefined',get_class($this),$name);}/*** Attaches an event handler to an event.** The handler must be a valid PHP callback, i.e., a string referring to* a global function name, or an array containing two elements with* the first element being an object and the second element a method name* of the object. In Prado, you can also use method path to refer to* an event handler. For example, array($object,'Parent.buttonClicked')* uses a method path that refers to the method $object->Parent->buttonClicked(...).** The event handler must be of the following signature,* <code>* function handlerName($sender,$param) {}* </code>* where $sender represents the object that raises the event,* and $param is the event parameter.** This is a convenient method to add an event handler.* It is equivalent to {@link getEventHandlers}($name)->add($handler).* For complete management of event handlers, use {@link getEventHandlers}* to get the event handler list first, and then do various* {@link TList} operations to append, insert or remove* event handlers. You may also do these operations like* getting and setting properties, e.g.,* <code>* $component->OnClick[]=array($object,'buttonClicked');* $component->OnClick->insertAt(0,array($object,'buttonClicked'));* </code>* which are equivalent to the following* <code>* $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));* $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));* </code>** @param string the event name* @param callback the event handler* @throws TInvalidOperationException if the event does not exist*/public function attachEventHandler($name,$handler){$this->getEventHandlers($name)->add($handler);}/*** Detaches an existing event handler.* This method is the opposite of {@link attachEventHandler}.* @param string event name* @param callback the event handler to be removed* @return boolean if the removal is successful*/public function detachEventHandler($name,$handler){if($this->hasEventHandler($name)){try{$this->getEventHandlers($name)->remove($handler);return true;}catch(Exception $e){}}return false;}/*** Raises an event.* This method represents the happening of an event and will* invoke all attached event handlers for the event.* @param string the event name* @param mixed the event sender object* @param TEventParameter the event parameter* @throws TInvalidOperationException if the event is undefined* @throws TInvalidDataValueException If an event handler is invalid*/public function raiseEvent($name,$sender,$param){$name=strtolower($name);if(isset($this->_e[$name])){foreach($this->_e[$name] as $handler){if(is_string($handler)){if(($pos=strrpos($handler,'.'))!==false){$object=$this->getSubProperty(substr($handler,0,$pos));$method=substr($handler,$pos+1);if(method_exists($object,$method))$object->$method($sender,$param);elsethrow new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler);}elsecall_user_func($handler,$sender,$param);}else if(is_callable($handler,true)){// an array: 0 - object, 1 - method name/pathlist($object,$method)=$handler;if(is_string($object)) // static method callcall_user_func($handler,$sender,$param);else{if(($pos=strrpos($method,'.'))!==false){$object=$this->getSubProperty(substr($method,0,$pos));$method=substr($method,$pos+1);}if(method_exists($object,$method))$object->$method($sender,$param);elsethrow new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler[1]);}}elsethrow new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,gettype($handler));}}else if(!$this->hasEvent($name))throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);}/*** Evaluates a PHP expression in the context of this control.* @return mixed the expression result* @throws TInvalidOperationException if the expression is invalid*/public function evaluateExpression($expression){try{if(eval("\$result=$expression;")===false)throw new Exception('');return $result;}catch(Exception $e){throw new TInvalidOperationException('component_expression_invalid',get_class($this),$expression,$e->getMessage());}}/*** Evaluates a list of PHP statements.* @param string PHP statements* @return string content echoed or printed by the PHP statements* @throws TInvalidOperationException if the statements are invalid*/public function evaluateStatements($statements){try{ob_start();if(eval($statements)===false)throw new Exception('');$content=ob_get_contents();ob_end_clean();return $content;}catch(Exception $e){throw new TInvalidOperationException('component_statements_invalid',get_class($this),$statements,$e->getMessage());}}/*** This method is invoked after the component is instantiated by a template.* When this method is invoked, the component's properties have been initialized.* The default implementation of this method will invoke* the potential parent component's {@link addParsedObject}.* This method can be overridden.* @param TComponent potential parent of this control* @see addParsedObject*/public function createdOnTemplate($parent){$parent->addParsedObject($this);}/*** Processes an object that is created during parsing template.* The object can be either a component or a static text string.* This method can be overridden to customize the handling of newly created objects in template.* Only framework developers and control developers should use this method.* @param string|TComponent text string or component parsed and instantiated in template* @see createdOnTemplate*/public function addParsedObject($object){}}/*** TEnumerable class.* TEnumerable is the base class for all enumerable types.* To define an enumerable type, extend TEnumberable and define string constants.* Each constant represents an enumerable value.* The constant name must be the same as the constant value.* For example,* <code>* class TTextAlign extends TEnumerable* {* const Left='Left';* const Right='Right';* }* </code>* Then, one can use the enumerable values such as TTextAlign::Left and* TTextAlign::Right.** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System* @since 3.0*/class TEnumerable{}/*** TPropertyValue class** TPropertyValue is a utility class that provides static methods* to convert component property values to specific types.** TPropertyValue is commonly used in component setter methods to ensure* the new property value is of specific type.* For example, a boolean-typed property setter method would be as follows,* <code>* function setPropertyName($value) {* $value=TPropertyValue::ensureBoolean($value);* // $value is now of boolean type* }* </code>** Properties can be of the following types with specific type conversion rules:* - string: a boolean value will be converted to 'true' or 'false'.* - boolean: string 'true' (case-insensitive) will be converted to true,* string 'false' (case-insensitive) will be converted to false.* - integer* - float* - array: string starting with '(' and ending with ')' will be considered as* as an array expression and will be evaluated. Otherwise, an array* with the value to be ensured is returned.* - object* - enum: enumerable type, represented by an array of strings.** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System* @since 3.0*/class TPropertyValue{/*** Converts a value to boolean type.* Note, string 'true' (case-insensitive) will be converted to true,* string 'false' (case-insensitive) will be converted to false.* If a string represents a non-zero number, it will be treated as true.* @param mixed the value to be converted.* @return boolean*/public static function ensureBoolean($value){if (is_string($value))return strcasecmp($value,'true')==0 || $value!=0;elsereturn (boolean)$value;}/*** Converts a value to string type.* Note, a boolean value will be converted to 'true' if it is true* and 'false' if it is false.* @param mixed the value to be converted.* @return string*/public static function ensureString($value){if (is_bool($value))return $value?'true':'false';elsereturn (string)$value;}/*** Converts a value to integer type.* @param mixed the value to be converted.* @return integer*/public static function ensureInteger($value){return (integer)$value;}/*** Converts a value to float type.* @param mixed the value to be converted.* @return float*/public static function ensureFloat($value){return (float)$value;}/*** Converts a value to array type. If the value is a string and it is* in the form (a,b,c) then an array consisting of each of the elements* will be returned. If the value is a string and it is not in this form* then an array consisting of just the string will be returned. If the value* is not a string then* @param mixed the value to be converted.* @return array*/public static function ensureArray($value){if(is_string($value)){$value = trim($value);$len = strlen($value);if ($len >= 2 && $value[0] == '(' && $value[$len-1] == ')'){eval('$array=array'.$value.';');return $array;}elsereturn $len>0?array($value):array();}elsereturn (array)$value;}/*** Converts a value to object type.* @param mixed the value to be converted.* @return object*/public static function ensureObject($value){return (object)$value;}/*** Converts a value to enum type.** This method checks if the value is of the specified enumerable type.* A value is a valid enumerable value if it is equal to the name of a constant* in the specified enumerable type (class).* For more details about enumerable, see {@link TEnumerable}.** For backward compatibility, this method also supports sanity* check of a string value to see if it is among the given list of strings.* @param mixed the value to be converted.* @param mixed class name of the enumerable type, or array of valid enumeration values. If this is not an array,* the method considers its parameters are of variable length, and the second till the last parameters are enumeration values.* @return string the valid enumeration value* @throws TInvalidDataValueException if the original value is not in the string array.*/public static function ensureEnum($value,$enums){static $types=array();if(func_num_args()===2 && is_string($enums)){if(!isset($types[$enums]))$types[$enums]=new ReflectionClass($enums);if($types[$enums]->hasConstant($value))return $value;elsethrow new TInvalidDataValueException('propertyvalue_enumvalue_invalid',$value,implode(' | ',$types[$enums]->getConstants()));}else if(!is_array($enums)){$enums=func_get_args();array_shift($enums);}if(in_array($value,$enums,true))return $value;elsethrow new TInvalidDataValueException('propertyvalue_enumvalue_invalid',$value,implode(' | ',$enums));}}/*** TEventParameter class.* TEventParameter is the base class for all event parameter classes.** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System* @since 3.0*/class TEventParameter extends TComponent{}/*** TComponentReflection class.** TComponentReflection provides functionalities to inspect the public/protected* properties, events and methods defined in a class.** The following code displays the properties and events defined in {@link TDataGrid},* <code>* $reflection=new TComponentReflection('TDataGrid');* Prado::varDump($reflection->getProperties());* Prado::varDump($reflection->getEvents());* </code>** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TComponent.php 2541 2008-10-21 15:05:13Z qiang.xue $* @package System* @since 3.0*/class TComponentReflection extends TComponent{private $_className;private $_properties=array();private $_events=array();private $_methods=array();/*** Constructor.* @param object|string the component instance or the class name* @throws TInvalidDataTypeException if the object is not a component*/public function __construct($component){if(is_string($component) && class_exists($component,false))$this->_className=$component;else if(is_object($component))$this->_className=get_class($component);elsethrow new TInvalidDataTypeException('componentreflection_class_invalid');$this->reflect();}private function isPropertyMethod($method){$methodName=$method->getName();return $method->getNumberOfRequiredParameters()===0&& strncasecmp($methodName,'get',3)===0&& isset($methodName[3]);}private function isEventMethod($method){$methodName=$method->getName();return strncasecmp($methodName,'on',2)===0&& isset($methodName[2]);}private function reflect(){$class=new TReflectionClass($this->_className);$properties=array();$events=array();$methods=array();$isComponent=is_subclass_of($this->_className,'TComponent') || strcasecmp($this->_className,'TComponent')===0;foreach($class->getMethods() as $method){if($method->isPublic() || $method->isProtected()){$methodName=$method->getName();if(!$method->isStatic() && $isComponent){if($this->isPropertyMethod($method))$properties[substr($methodName,3)]=$method;else if($this->isEventMethod($method)){$methodName[0]='O';$events[$methodName]=$method;}}if(strncmp($methodName,'__',2)!==0)$methods[$methodName]=$method;}}$reserved=array();ksort($properties);foreach($properties as $name=>$method){$this->_properties[$name]=array('type'=>$this->determinePropertyType($method),'readonly'=>!$class->hasMethod('set'.$name),'protected'=>$method->isProtected(),'class'=>$method->getDeclaringClass()->getName(),'comments'=>$method->getDocComment());$reserved['get'.strtolower($name)]=1;$reserved['set'.strtolower($name)]=1;}ksort($events);foreach($events as $name=>$method){$this->_events[$name]=array('class'=>$method->getDeclaringClass()->getName(),'protected'=>$method->isProtected(),'comments'=>$method->getDocComment());$reserved[strtolower($name)]=1;}ksort($methods);foreach($methods as $name=>$method){if(!isset($reserved[strtolower($name)]))$this->_methods[$name]=array('class'=>$method->getDeclaringClass()->getName(),'protected'=>$method->isProtected(),'static'=>$method->isStatic(),'comments'=>$method->getDocComment());}}/*** Determines the property type.* This method uses the doc comment to determine the property type.* @param ReflectionMethod* @return string the property type, '{unknown}' if type cannot be determined from comment*/protected function determinePropertyType($method){$comment=$method->getDocComment();if(preg_match('/@return\\s+(.*?)\\s+/',$comment,$matches))return $matches[1];elsereturn '{unknown}';}/*** @return string class name of the component*/public function getClassName(){return $this->_className;}/*** @return array list of component properties. Array keys are property names.* Each array element is of the following structure:* [type]=>property type,* [readonly]=>whether the property is read-only,* [protected]=>whether the method is protected or not* [class]=>the class where the property is inherited from,* [comments]=>comments associated with the property.*/public function getProperties(){return $this->_properties;}/*** @return array list of component events. Array keys are event names.* Each array element is of the following structure:* [protected]=>whether the event is protected or not* [class]=>the class where the event is inherited from.* [comments]=>comments associated with the event.*/public function getEvents(){return $this->_events;}/*** @return array list of public/protected methods. Array keys are method names.* Each array element is of the following structure:* [protected]=>whether the method is protected or not* [static]=>whether the method is static or not* [class]=>the class where the property is inherited from,* [comments]=>comments associated with the event.*/public function getMethods(){return $this->_methods;}}