/TComponent.php
PHP | 841 lines | 389 code | 38 blank | 414 comment | 66 complexity | 0cb7d87983008ccae1056ba61d2e72d6 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0
- <?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 property
- return $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];
- }
- else
- throw 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);
- else
- throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler);
- }
- else
- call_user_func($handler,$sender,$param);
- }
- else if(is_callable($handler,true))
- {
- // an array: 0 - object, 1 - method name/path
- list($object,$method)=$handler;
- if(is_string($object)) // static method call
- call_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);
- else
- throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler[1]);
- }
- }
- else
- throw 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;
- else
- return (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';
- else
- return (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;
- }
- else
- return $len>0?array($value):array();
- }
- else
- return (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;
- else
- throw 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;
- else
- throw 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);
- else
- throw 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];
- else
- return '{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;
- }
- }