summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes3
-rw-r--r--framework/Exceptions/messages/messages.txt4
-rw-r--r--framework/TApplication.php4
-rw-r--r--framework/TComponent.php1538
-rw-r--r--framework/Util/TBehavior.php87
-rw-r--r--framework/Util/TCallChain.php147
-rw-r--r--framework/Util/TClassBehavior.php36
-rw-r--r--tests/unit/TComponentTest.php1940
8 files changed, 3654 insertions, 105 deletions
diff --git a/.gitattributes b/.gitattributes
index 6541f9fb..3102dd50 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2706,6 +2706,9 @@ framework/I18N/core/data/zh_TW.dat -text
framework/I18N/schema/mysql.sql -text
framework/I18N/schema/postgresql.sql -text
framework/I18N/schema/sqlite.sql -text
+framework/Util/TBehavior.php -text
+framework/Util/TCallChain.php -text
+framework/Util/TClassBehavior.php -text
framework/Web/Javascripts/source/.htaccess -text
framework/Web/Javascripts/source/prado/activefileupload/ActiveFileUploadBlank.html -text
framework/Web/Javascripts/source/prado/activefileupload/ActiveFileUploadComplete.png -text svneol=unset#image/png
diff --git a/framework/Exceptions/messages/messages.txt b/framework/Exceptions/messages/messages.txt
index 69fd89fc..468ea4e1 100644
--- a/framework/Exceptions/messages/messages.txt
+++ b/framework/Exceptions/messages/messages.txt
@@ -11,6 +11,10 @@ component_event_undefined = Component event '{0}.{1}' is not defined.
component_eventhandler_invalid = Component event '{0}.{1}' is attached with an invalid event handler '{2}'.
component_expression_invalid = Component '{0}' is evaluating an invalid expression '{1}' : {2}.
component_statements_invalid = Component '{0}' is evaluating invalid PHP statements '{1}' : {2}.
+component_class_behavior_defined = Component '{0}' already has a class behavior of '{1}'.
+component_not_a_behavior = Component '{0}' is being added as behavior is not a TBaseBehavior.
+component_no_tcomponent_class_behaviors = TComponent cannot have class behaviors attached due to recursion.
+component_no_class_provided_nor_late_binding = Adding or Removing Class Behaviors must have PHP feature Late Static Binding or a class provided as a parameter
propertyvalue_enumvalue_invalid = Value '{0}' is a not valid enumeration value ({1}).
diff --git a/framework/TApplication.php b/framework/TApplication.php
index 1da70ba5..be43e697 100644
--- a/framework/TApplication.php
+++ b/framework/TApplication.php
@@ -24,8 +24,8 @@ Prado::using('System.TService');
Prado::using('System.Exceptions.TErrorHandler');
Prado::using('System.Caching.TCache');
Prado::using('System.IO.TTextWriter');
-Prado::using('System.Collections.TList');
-Prado::using('System.Collections.TMap');
+Prado::using('System.Collections.TPriorityList');
+Prado::using('System.Collections.TPriorityMap');
Prado::using('System.Collections.TStack');
Prado::using('System.Xml.TXmlDocument');
Prado::using('System.Security.TAuthorizationRule');
diff --git a/framework/TComponent.php b/framework/TComponent.php
index 941f2c50..930afe23 100644
--- a/framework/TComponent.php
+++ b/framework/TComponent.php
@@ -1,10 +1,13 @@
<?php
/**
* TComponent, TPropertyValue classes
- *
* @author Qiang Xue <qiang.xue@gmail.com>
+ *
+ * Global Events, intra-object events, Class behaviors, expanded behaviors
+ * @author Brad Anderson <javalizard@mac.com>
+ *
* @link http://www.pradosoft.com/
- * @copyright Copyright &copy; 2005-2012 PradoSoft
+ * @copyright Copyright &copy; 2005-2011 PradoSoft
* @license http://www.pradosoft.com/license/
* @version $Id$
* @package System
@@ -14,7 +17,8 @@
* TComponent class
*
* TComponent is the base class for all PRADO components.
- * TComponent implements the protocol of defining, using properties and events.
+ * TComponent implements the protocol of defining, using properties, behaviors,
+ * 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.
@@ -35,6 +39,8 @@
* in the format of concatenated words, with the first letter of each word
* capitalized (e.g. DisplayMode, ItemStyle).
*
+ * Javascript Get and Set
+ *
* Since Prado 3.2 a new class of javascript-friendly properties have been introduced
* to better deal with potential security problems like cross-site scripting issues.
* All the data that gets sent clientside inside a javascript block is now encoded by default.
@@ -60,6 +66,8 @@
* statement and will not be encoded when rendered inside a javascript block.
* This special handling makes use of the {@link TJavaScriptLiteral} class.
*
+ * Events
+ *
* 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).
@@ -74,6 +82,7 @@
* To raise an event (assuming named as 'Click') of a component, use
* <code>
* $component->raiseEvent('OnClick');
+ * $component->raiseEvent('OnClick', $this, $param);
* </code>
* To attach an event handler to an event, use one of the following ways,
* <code>
@@ -81,7 +90,7 @@
* $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 event handler list {@link TPriorityList} 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
@@ -90,11 +99,187 @@
* - 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>
+ *
+ * With the addition of behaviors, a more expansive event model is needed. There
+ * are two new event types (global and dynamic events) as well as a more comprehensive
+ * behavior model that includes class wide behaviors.
+ *
+ * A global event is defined by all events whose name starts with 'fx'.
+ * The event name is potentially a method name and is thus case-insensitive. All 'fx' events
+ * are valid as the whole 'fx' event/method space is global in nature. Any object may patch into
+ * any global event by defining that event as a method. Global events have priorities
+ * just like 'on' events; so as to be able to order the event execution. Due to the
+ * nature of all events which start with 'fx' being valid, in effect, every object
+ * has every 'fx' global event. It is simply an issue of tapping into the desired
+ * global event.
+ *
+ * A global event that starts with 'fx' can be called even if the object does not
+ * implement the method of the global event. A call to a non-existing 'fx' method
+ * will, at minimal, function and return null. If a method argument list has a first
+ * parameter, it will be returned instead of null. This allows filtering and chaining.
+ * 'fx' methods do not automatically install and uninstall. To install and uninstall an
+ * object's global event listeners, call the object's {@link listen} and
+ * {@link unlisten} methods, respectively. An object may auto-install its global event
+ * during {@link __construct} by overriding {@link getAutoGlobalListen} and returning true.
+ *
+ * As of PHP version 5.3, nulled objects without code references will still continue to persist
+ * in the global event queue because {@link __destruct} is not automatically called. In the common
+ * __destruct method, if an object is listening to global events, then {@link unlisten} is called.
+ * {@link unlisten} is required to be manually called before an object is
+ * left without references if it is currently listening to any global events. This includes
+ * class wide behaviors.
+ *
+ * An object that contains a method that starts with 'fx' will have those functions
+ * automatically receive those events of the same name after {@link listen} is called on the object.
+ *
+ * An object may listen to a global event without defining an 'fx' method of the same name by
+ * adding an object method to the global event list. For example
+ * <code>
+ * $component->fxGlobalCheck=$callback; // or $component->OnClick->add($callback);
+ * $component->attachEventHandler('fxGlobalCheck',array($object, 'someMethod'));
+ * </code>
+ *
+ * Events between Objects and their behaviors, Dynamic Events
+ *
+ * An intra-object/behavior event is defined by methods that start with 'dy'. Just as with
+ * 'fx' global events, every object has every dynamic event. Any call to a method that
+ * starts with 'dy' will be handled, regardless of whether it is implemented. These
+ * events are for communicating with attached behaviors.
+ *
+ * Dynamic events can be used in a variety of ways. They can be used to tell behaviors
+ * when a non-behavior method is called. Dynamic events could be used as data filters.
+ * They could also be used to specify when a piece of code is to be run, eg. should the
+ * loop process be performed on a particular piece of data. In this way, some control
+ * is handed to the behaviors over the process and/or data.
+ *
+ * If there are no handlers for an 'fx' or 'dy' event, it will return the first
+ * parameter of the argument list. If none there are no arguments, these events
+ * will return null. If there are handlers an 'fx' method will be called directly
+ * within the object. Global 'fx' events are triggered by calling {@link raiseEvent}.
+ * For dynamic events where there are behaviors that respond to the dynamic events, a
+ * {@link TCallChain} is developed. A call chain allows the behavior dynamic event
+ * implementations to call further implementing behaviors within a chain.
+ *
+ * If an object implements {@link IDynamicMethods}, all global and object dynamic
+ * events will be sent to {@link __dycall}. In the case of global events, all
+ * global events will trigger this method. In the case of behaviors, all undefined
+ * dynamic events which are called will be passed through to this method.
+ *
+ *
+ * Behaviors
+ *
+ * There are two types of behaviors. There are individual object behaviors and
+ * there are class wide behaviors. Class behaviors depend upon object behaviors.
+ *
+ * When a new class implements {@link IBehavior} or {@link IClassBehavior} or
+ * extends {@link TBehavior} or {@link TClassBehavior}, it may be added to an
+ * object by calling the object's {@link attachBehavior}. The behaviors associated
+ * name can then be used to {@link enableBehavior} or {@link disableBehavior}
+ * the specific behavior.
+ *
+ * All behaviors may be turned on and off via {@link enableBehaviors} and
+ * {@link disableBehaviors}, respectively. To check if behaviors are on or off
+ * a call to {@link getBehaviorsEnabled} will provide the variable.
+ *
+ * Attaching and detaching whole sets of behaviors is done using
+ * {@link attachBehaviors} and {@link detachBehaviors}. {@link clearBehaviors}
+ * removes all of an object's behaviors.
+ *
+ * {@link asa} returns a behavior of a specific name. {@link isa} is the
+ * behavior inclusive function that acts as the PHP operator {@link instanceof}.
+ * A behavior could provide the functionality of a specific class thus causing
+ * the host object to act similarly to a completely different class. A behavior
+ * would then implement {@link IInstanceCheck} to provide the identity of the
+ * different class.
+ *
+ * Class behaviors are similar to object behaviors except that the class behavior
+ * is the implementation for all instances of the class. A class behavior
+ * will have the object upon which is being called be prepended to the parameter
+ * list. This way the object is known across the class behavior implementation.
+ *
+ * Class behaviors are attached using {@link attachClassBehavior} and detached
+ * using {@link detachClassBehavior}. Class behaviors are important in that
+ * they will be applied to all new instances of a particular class. In this way
+ * class behaviors become default behaviors to a new instances of a class in
+ * {@link __construct}. Detaching a class behavior will remove the behavior
+ * from the default set of behaviors created for an object when the object
+ * is instanced.
+ *
+ * Class behaviors are also added to all existing instances via the global 'fx'
+ * event mechanism. When a new class behavior is added, the event
+ * {@link fxAttachClassBehavior} is raised and all existing instances that are
+ * listening to this global event (primarily after {@link listen} is called)
+ * will have this new behavior attached. A similar process is used when
+ * detaching class behaviors. Any objects listening to the global 'fx' event
+ * {@link fxDetachClassBehavior} will have a class behavior removed.
+ *
+ * Dynamic Intra-Object Events
+ *
+ * Dynamic events start with 'dy'. This mechanism is used to allow objects
+ * to communicate with their behaviors directly. The entire 'dy' event space
+ * is valid. All attached, enabled behaviors that implement a dynamic event
+ * are called when the host object calls the dynamic event. If there is no
+ * implementation or behaviors, this returns null when no parameters are
+ * supplied and will return the first parameter when there is at least one
+ * parameter in the dynamic event.
+ * <code>
+ * null == $this->dyBehaviorEvent();
+ * 5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event
+ * </code>
+ *
+ * Dynamic events can be chained together within behaviors to allow for data
+ * filtering. Dynamic events are implemented within behaviors by defining the
+ * event as a method.
+ * <code>
+ * class TObjectBehavior extends TBehavior {
+ * public function dyBehaviorEvent($param1, $callchain) {
+ * //Do something, eg: $param1 += 13;
+ * return $callchain->dyBehaviorEvent($param1);
+ * }
+ * }
+ * </code>
+ * This implementation of a behavior and dynamic event will flow through to the
+ * next behavior implementing the dynamic event. The first parameter is always
+ * return when it is supplied. Otherwise a dynamic event returns null.
+ *
+ * In the case of a class behavior, the object is also prepended to the dynamic
+ * event.
+ * <code>
+ * class TObjectClassBehavior extends TClassBehavior {
+ * public function dyBehaviorEvent($hostobject, $param1, $callchain) {
+ * //Do something, eg: $param1 += $hostobject->getNumber();
+ * return $callchain->dyBehaviorEvent($param1);
+ * }
+ * }
+ * </code>
+ * When calling a dynamic event, only the parameters are passed. The host object
+ * and the call chain are built into the framework.
+ *
+ * Global Event and Dynamic event catching
+ *
+ * Given that all global 'fx' events and dynamic 'dy' events are valid and
+ * operational, there is a mechanism for catching events called that are not
+ * implemented (similar to the built-in PHP method {@link __call}). When
+ * a dynamic or global event is called but a behavior does not implement it,
+ * yet desires to know when an undefined dynamic event is run, the behavior
+ * implements the interface {@link IDynamicMethods} and method {@link __dycall}.
+ *
+ * In the case of dynamic events, {@link __dycall} is supplied with the method
+ * name and its parameters. When a global event is raised, via {@link raiseEvent},
+ * the method is the event name and the parameters are supplied.
+ *
+ * When implemented, this catch-all mechanism is called for event global event event
+ * when implemented outside of a behavior. Within a behavior, it will also be called
+ * when the object to which the behavior is attached calls any unimplemented dynamic
+ * event. This is the fall-back mechanism for informing a class and/or behavior
+ * of when an global and/or undefined dynamic event is executed.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
* @version $Id$
* @package System
- * @since 3.0
+ * @since 3.1.8
*/
class TComponent
{
@@ -102,6 +287,279 @@ class TComponent
* @var array event handler lists
*/
private $_e=array();
+
+ /**
+ * @var boolean if listening is enabled. Automatically turned on or off in
+ * constructor according to {@link getAutoGlobalListen}. Default false, off
+ */
+ private $_listeningenabled=false;
+
+ /**
+ * @var array static registered global event handler lists
+ */
+ private static $_ue=array();
+
+ /**
+ * @var boolean if object behaviors are on or off. default true, on
+ */
+ private $_behaviorsenabled=true;
+
+ /**
+ * @var TPriorityMap list of object behaviors
+ */
+ private $_m=null;
+
+ /**
+ * @var array static global class behaviors, these behaviors are added upon instantiation of a class
+ */
+ private static $_um=array();
+
+
+ /**
+ * @const string the name of the global {@link raiseEvent} listener
+ */
+ const GLOBAL_RAISE_EVENT_LISTENER='fxGlobalListener';
+
+
+ /**
+ * The common __construct
+ * If desired by the new object, this will auto install and listen to global event functions
+ * as defined by the object via 'fx' methods. This also attaches any predefined behaviors.
+ * This function installs all class behaviors in a class hierarchy from the deepest subclass
+ * through each parent to the top most class, TComponent.
+ */
+ public function __construct() {
+ if($this->getAutoGlobalListen())
+ $this->listen();
+
+ $classes=array_reverse($this->getClassHierarchy(true));
+ foreach($classes as $class) {
+ if(isset(self::$_um[$class]))
+ $this->attachBehaviors(self::$_um[$class]);
+ }
+ }
+
+
+ /**
+ * Tells TComponent whether or not to automatically listen to global events.
+ * Defaults to false because PHP variable cleanup is affected if this is true.
+ * When unsetting a variable that is listening to global events, {@link unlisten}
+ * must explicitly be called when cleaning variables allocation or else the global
+ * event registry will contain references to the old object. This is true for PHP 5.4
+ *
+ * Override this method by a subclass to change the setting. When set to true, this
+ * will enable {@link __construct} to call {@link listen}.
+ *
+ * @return boolean whether or not to auto listen to global events during {@link __construct}, default false
+ */
+ public function getAutoGlobalListen() {
+ return false;
+ }
+
+
+ /**
+ * The common __destruct
+ * This unlistens from the global event routines if listening
+ *
+ * PHP 5.3 does not __destruct objects when they are nulled and thus unlisten must be
+ * called must be explicitly called.
+ */
+ public function __destruct() {
+ if($this->_listeningenabled)
+ $this->unlisten();
+ }
+
+
+ /**
+ * This utility function is a private array filter method. The array values
+ * that start with 'fx' are filtered in.
+ */
+ private function filter_prado_fx($name) {
+ return strncasecmp($name,'fx',2)===0;
+ }
+
+
+ /**
+ * This returns an array of the class name and the names of all its parents. The base object first,
+ * {@link TComponent}, and the deepest subclass is last.
+ * @param boolean optional should the names be all lowercase true/false
+ * @return array array of strings being the class hierarchy of $this.
+ */
+ public function getClassHierarchy($lowercase = false)
+ {
+ $class=get_class($this);
+ $classes=array($class);
+ while($class=get_parent_class($class)){array_unshift($classes,$class);}
+ if($lowercase)
+ return array_map('strtolower',$classes);
+ return $classes;
+ }
+
+
+ /**
+ * This adds an object's fx event handlers into the global broadcaster to listen into any
+ * broadcast global events called through {@link raiseEvent}
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyListen($globalEvents[, $chain]) {
+ * $this->listen(); //eg
+ * }
+ * </code>
+ * to be executed when listen is called. All attached behaviors are notified through dyListen.
+ *
+ * @return numeric the number of global events that were registered to the global event registry
+ */
+ public function listen() {
+ if($this->_listeningenabled)
+ return;
+
+ $fx=array_filter(get_class_methods($this),array($this,'filter_prado_fx'));
+
+ foreach($fx as $func)
+ $this->attachEventHandler($func,array($this,$func));
+
+ if(is_a($this,'IDynamicMethods')) {
+ $this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER,array($this,'__dycall'));
+ array_push($fx,TComponent::GLOBAL_RAISE_EVENT_LISTENER);
+ }
+
+ $this->_listeningenabled=true;
+
+ $this->dyListen($fx);
+
+ return count($fx);
+ }
+
+ /**
+ * this removes an object's fx events from the global broadcaster
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyUnlisten($globalEvents[, $chain]) {
+ * $this->behaviorUnlisten(); //eg
+ * }
+ * </code>
+ * to be executed when listen is called. All attached behaviors are notified through dyUnlisten.
+ *
+ * @return numeric the number of global events that were unregistered from the global event registry
+ */
+ public function unlisten() {
+ if(!$this->_listeningenabled)
+ return;
+
+ $fx=array_filter(get_class_methods($this),array($this,'filter_prado_fx'));
+
+ foreach($fx as $func)
+ $this->detachEventHandler($func,array($this,$func));
+
+ if(is_a($this,'IDynamicMethods')) {
+ $this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER,array($this,'__dycall'));
+ array_push($fx,TComponent::GLOBAL_RAISE_EVENT_LISTENER);
+ }
+
+ $this->_listeningenabled=false;
+
+ $this->dyUnlisten($fx);
+
+ return count($fx);
+ }
+
+ /**
+ * Gets the state of listening to global events
+ * @return boolean is Listening to global broadcast enabled
+ */
+ public function getListeningToGlobalEvents()
+ {
+ return $this->_listeningenabled;
+ }
+
+
+ /**
+ * Calls a method.
+ * Do not call this method directly. This is a PHP magic method that we override
+ * to allow behaviors, dynamic events (intra-object/behavior events),
+ * undefined dynamic and global events, and
+ * to allow using the following syntax to call a property setter or getter.
+ * <code>
+ * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
+ * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
+ * </code>
+ *
+ * Additional object behaviors override class behaviors.
+ * dynamic and global events do not fail even if they aren't implemented.
+ * Any intra-object/behavior dynamic events that are not implemented by the behavior
+ * return the first function paramater or null when no parameters are specified.
+ *
+ * @param string method name that doesn't exist and is being called on the object
+ * @param mixed method parameters
+ * @throws TInvalidOperationException If the property is not defined or read-only or
+ * method is undefined
+ * @return mixed result of the method call, or false if 'fx' or 'dy' function but
+ * is not found in the class, otherwise it runs
+ */
+ public function __call($method, $args)
+ {
+ $getset=substr($method,0,3);
+ if(($getset=='get')||($getset=='set'))
+ {
+ $propname=substr($method,3);
+ $jsmethod=$getset.'js'.$propname;
+ if(method_exists($this,$jsmethod))
+ {
+ if(count($args)>0)
+ if($args[0]&&!($args[0] instanceof TJavaScriptString))
+ $args[0]=new TJavaScriptString($args[0]);
+ return call_user_func_array(array($this,$jsmethod),$args);
+ }
+
+ if (($getset=='set')&&method_exists($this,'getjs'.$propname))
+ throw new TInvalidOperationException('component_property_readonly',get_class($this),$method);
+ }
+
+ if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ if(strncasecmp($method,'dy',2)===0)
+ {
+ $callchain=new TCallChain($method);
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&(method_exists($behavior,$method)||($behavior instanceof IDynamicMethods)))
+ {
+ $behavior_args=$args;
+ if($behavior instanceof IClassBehavior)
+ array_unshift($behavior_args,$this);
+ $callchain->addCall(array($behavior,$method),$behavior_args);
+ }
+
+ }
+ if($callchain->getCount()>0)
+ return call_user_func_array(array($callchain,'call'),$args);
+ }
+ else
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&method_exists($behavior,$method))
+ {
+ if($behavior instanceof IClassBehavior)
+ array_unshift($args,$this);
+ return call_user_func_array(array($behavior,$method),$args);
+ }
+ }
+ }
+ }
+
+ if(strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
+ {
+ if($this instanceof IDynamicMethods)
+ return $this->__dycall($method,$args);
+ return isset($args[0])?$args[0]:null;
+ }
+
+ throw new TApplicationException('component_method_undefined',get_class($this),$method);
+ }
+
/**
* Returns a property value or an event handler list by property or event name.
@@ -115,21 +573,27 @@ class TComponent
* <code>
* $eventHandlerList=$component->EventName;
* </code>
+ * This will also return the global event handler list when specifing an 'fx'
+ * event,
+ * <code>
+ * $globalEventHandlerList=$component->fxEventName;
+ * </code>
+ * When behaviors are enabled, this will return the behavior of a specific
+ * name, a property of a behavior, or an object 'on' event defined by the behavior.
* @param string the property name or the event name
- * @return mixed the property value or the event handler list
+ * @return mixed the property value or the event handler list as {@link TPriorityList}
* @throws TInvalidOperationException if the property/event is not defined.
*/
public function __get($name)
{
- $getter='get'.$name; $jsgetter = 'getjs'.$name;
- if(method_exists($this,$getter))
+ if(method_exists($this,$getter='get'.$name))
{
// getting a property
return $this->$getter();
}
- else if(method_exists($this,$jsgetter))
+ else if(method_exists($this,$jsgetter='getjs'.$name))
{
- // getting a property
+ // getting a javascript property
return (string)$this->$jsgetter();
}
else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
@@ -137,13 +601,33 @@ class TComponent
// getting an event (handler list)
$name=strtolower($name);
if(!isset($this->_e[$name]))
- $this->_e[$name]=new TList;
+ $this->_e[$name]=new TPriorityList;
return $this->_e[$name];
}
- else
+ else if(strncasecmp($name,'fx',2)===0)
{
- throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
+ // getting a global event (handler list)
+ $name=strtolower($name);
+ if(!isset(self::$_ue[$name]))
+ self::$_ue[$name]=new TPriorityList;
+ return self::$_ue[$name];
}
+ else if($this->_behaviorsenabled)
+ {
+ // getting a behavior property/event (handler list)
+ if(isset($this->_m[$name]))
+ return $this->_m[$name];
+ else if($this->_m!==null)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&
+ (property_exists($behavior,$name)||$behavior->canGetProperty($name)||$behavior->hasEvent($name)))
+ return $behavior->$name;
+ }
+ }
+ }
+ throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
}
/**
@@ -154,30 +638,47 @@ class TComponent
* $this->PropertyName=$value;
* $this->jsPropertyName=$value; // $value will be treated as a JavaScript literal
* $this->EventName=$handler;
+ * $this->fxEventName=$handler; //global event listener
* </code>
+ * When behaviors are enabled, this will also set a behaviors properties and events.
* @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)
{
- if(method_exists($this, $setter='set'.$name))
+ if(method_exists($this,$setter='set'.$name))
{
- if (strncasecmp($name,'js',2)===0 && $value && !($value instanceof TJavaScriptLiteral))
+ if(strncasecmp($name,'js',2)===0&&$value&&!($value instanceof TJavaScriptLiteral))
$value = new TJavaScriptLiteral($value);
- $this->$setter($value);
+ return $this->$setter($value);
}
- else if(method_exists($this, $jssetter = 'setjs'.$name))
+ else if(method_exists($this,$jssetter='setjs'.$name))
{
- if ($value and !($value instanceof TJavaScriptString))
- $value = new TJavaScriptString($value);
- $this->$jssetter($value);
+ if($value&&!($value instanceof TJavaScriptString))
+ $value=new TJavaScriptString($value);
+ return $this->$jssetter($value);
}
- else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
+ else if((strncasecmp($name,'on',2)===0&&method_exists($this,$name))||strncasecmp($name,'fx',2)===0)
{
- $this->attachEventHandler($name,$value);
+ return $this->attachEventHandler($name,$value);
}
- else if(method_exists($this,'get'.$name) || method_exists($this,'getjs'.$name))
+ else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
+ {
+ $sets=0;
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&
+ (property_exists($behavior,$name)||$behavior->canSetProperty($name)||$behavior->hasEvent($name))) {
+ $behavior->$name=$value;
+ $sets++;
+ }
+ }
+ if($sets)return $value;
+
+ }
+
+ if(method_exists($this,'get'.$name)||method_exists($this,'getjs'.$name))
{
throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
}
@@ -188,37 +689,86 @@ class TComponent
}
/**
- * Calls a method.
+ * Checks if a property value is null, there are no events in the object
+ * event list or global event list registered under the name, and, if
+ * behaviors are enabled,
* Do not call this method. This is a PHP magic method that we override
- * to allow using the following syntax to call a property setter or getter.
- * <code>
- * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
- * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
- * </code>
- * @param string the getter or setter method name
- * @param mixed method call parameters
- * @throws TInvalidOperationException If the property is not defined or read-only.
+ * to allow using isset() to detect if a component property is set or not.
+ * This also works for global events. When behaviors are enabled, it
+ * will check for a behavior of the specified name, and also check
+ * the behavior for events and properties.
+ * @param string the property name or the event name
+ * @since 1.0.1
*/
- public function __call($name,$params)
+ public function __isset($name)
{
- $getset = substr($name,0,3);
- if (($getset=='get') || ($getset=='set'))
+ if(method_exists($this,$getter='get'.$name))
+ return $this->$getter()!==null;
+ else if(method_exists($this,$jsgetter='getjs'.$name))
+ return $this->$jsgetter()!==null;
+ else if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
{
- $propname = substr($name,3);
- $jsmethod = $getset.'js'.$propname;
- if (method_exists($this, $jsmethod))
+ $name=strtolower($name);
+ return isset($this->_e[$name])&&$this->_e[$name]->getCount();
+ }
+ else if(strncasecmp($name,'fx',2)===0)
+ {
+ $name=strtolower($name);
+ return isset(self::$_ue[$name])&&self::$_ue[$name]->getCount();
+ }
+ else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
+ {
+ if(isset($this->_m[$name]))
+ return true;
+ foreach($this->_m->toArray() as $behavior)
{
- if (count($params)>0)
- if ($params[0] && !($params[0] instanceof TJavaScriptString))
- $params[0] = new TJavaScriptString($params[0]);
- return call_user_func_array(array($this, $jsmethod), $params);
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled()))
+ return isset($behavior->$name);
}
-
- if (($getset=='set') and method_exists($this, 'getjs'.$propname))
- throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
+
}
+ else
+ return false;
+ }
- throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
+ /**
+ * Sets a component property to be null. Clears the object or global
+ * events. When enabled, loops through all behaviors and unsets the
+ * property or event.
+ * Do not call this method. This is a PHP magic method that we override
+ * to allow using unset() to set a component property to be null.
+ * @param string the property name or the event name
+ * @throws TInvalidOperationException if the property is read only.
+ * @since 3.1.8
+ */
+ public function __unset($name)
+ {
+ if(method_exists($this,$setter='set'.$name))
+ $this->$setter(null);
+ else if(method_exists($this,$jssetter='setjs'.$name))
+ $this->$jssetter(null);
+ else if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
+ $this->_e[strtolower($name)]->clear();
+ else if(strncasecmp($name,'fx',2)===0)
+ $this->getEventHandlers($name)->remove(array($this, $name));
+ else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
+ {
+ if(isset($this->_m[$name]))
+ $this->detachBehavior($name);
+ else {
+ $unset=0;
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())) {
+ unset($behavior->$name);
+ $unset++;
+ }
+ }
+ if(!$unset&&method_exists($this,'get'.$name))
+ throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
+ }
+ } else if(method_exists($this,'get'.$name))
+ throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
}
/**
@@ -230,34 +780,55 @@ class TComponent
*/
public function hasProperty($name)
{
- return
- method_exists($this,'get'.$name) || method_exists($this,'set'.$name) ||
- method_exists($this,'getjs'.$name) || method_exists($this,'setjs'.$name)
- ;
+ return $this->canGetProperty($name)||$this->canSetProperty($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.
+ * This also checks for getjs. When enabled, it loops through all
+ * active behaviors for the get property when undefined by the object.
* @param string the property name
* @return boolean whether the property can be read
*/
public function canGetProperty($name)
{
- return method_exists($this,'get'.$name) || method_exists($this,'getjs'.$name);
+ if(method_exists($this,'get'.$name)||method_exists($this,'getjs'.$name))
+ return true;
+ else if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->canGetProperty($name))
+ return true;
+ }
+ }
+ return false;
}
/**
* 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.
+ * This also checks for setjs. When enabled, it loops through all
+ * active behaviors for the set property when undefined by the object.
* @param string the property name
* @return boolean whether the property can be written
*/
public function canSetProperty($name)
{
- return method_exists($this,'set'.$name) || method_exists($this,'setjs'.$name);
+ if(method_exists($this,'set'.$name)||method_exists($this,'setjs'.$name))
+ return true;
+ else if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->canSetProperty($name))
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -265,6 +836,8 @@ class TComponent
* 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).
+ * When a property is not defined by an object, this also loops through all
+ * active behaviors of the object.
* @param string property path
* @return mixed the property path value
*/
@@ -281,6 +854,8 @@ class TComponent
* 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).
+ * When a property is not defined by an object, this also loops through all
+ * active behaviors of the object.
* @param string property path
* @param mixed the property path value
*/
@@ -299,41 +874,90 @@ class TComponent
/**
* 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'.
+ * An event is defined if the class has a method whose name is the event name
+ * prefixed with 'on', 'fx', or 'dy'.
+ * Every object responds to every 'fx' and 'dy' event as they are in a universally
+ * accepted event space. 'on' event must be declared by the object.
+ * When enabled, this will loop through all active behaviors for 'on' events
+ * defined by the behavior.
* 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);
+ if((strncasecmp($name,'on',2)===0&&method_exists($this,$name))||strncasecmp($name,'fx',2)===0||strncasecmp($name,'dy',2)===0)
+ return true;
+
+ else if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEvent($name))
+ return true;
+ }
+ }
+ return false;
}
/**
+ * Checks if an event has any handlers. This function also checks through all
+ * the behaviors for 'on' events when behaviors are enabled.
+ * 'dy' dynamic events are not handled by this function.
+ * @param string the event 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;
+ if(strncasecmp($name,'fx',2)===0)
+ return isset(self::$_ue[$name])&&self::$_ue[$name]->getCount()>0;
+ else
+ {
+ if(isset($this->_e[$name])&&$this->_e[$name]->getCount()>0)
+ return true;
+ else if($this->_m!==null&&$this->_behaviorsenabled) {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEventHandler($name))
+ return true;
+ }
+ }
+ }
+ return false;
}
/**
- * Returns the list of attached event handlers for an event.
- * @return TList list of attached event handlers for an event
+ * Returns the list of attached event handlers for an 'on' or 'fx' event. This function also
+ * checks through all the behaviors for 'on' event lists when behaviors are enabled.
+ * @return TPriorityList 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))
+ if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
{
$name=strtolower($name);
if(!isset($this->_e[$name]))
- $this->_e[$name]=new TList;
+ $this->_e[$name]=new TPriorityList;
return $this->_e[$name];
}
- else
- throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
+ else if(strncasecmp($name,'fx',2)===0)
+ {
+ $name=strtolower($name);
+ if(!isset(self::$_ue[$name]))
+ self::$_ue[$name]=new TPriorityList;
+ return self::$_ue[$name];
+ }
+ else if($this->_m!==null&&$this->_behaviorsenabled)
+ {
+ foreach($this->_m->toArray() as $behavior)
+ {
+ if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEvent($name))
+ return $behavior->getEventHandlers($name);
+ }
+ }
+ throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
}
/**
@@ -348,16 +972,18 @@ class TComponent
*
* The event handler must be of the following signature,
* <code>
- * function handlerName($sender,$param) {}
+ * function handlerName($sender, $param) {}
+ * function handlerName($sender, $param, $name) {}
* </code>
* where $sender represents the object that raises the event,
- * and $param is the event parameter.
+ * and $param is the event parameter. $name refers to the event name
+ * being handled.
*
* 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
+ * {@link TPriorityList} operations to append, insert or remove
* event handlers. You may also do these operations like
* getting and setting properties, e.g.,
* <code>
@@ -370,29 +996,37 @@ class TComponent
* $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
* </code>
*
+ * Due to the nature of {@link getEventHandlers}, any active behaviors defining
+ * new 'on' events, this method will pass through to the behavior transparently.
+ *
* @param string the event name
* @param callback the event handler
+ * @param numeric|null the priority of the handler, defaults to null which translates into the
+ * default priority of 10.0 within {@link TPriorityList}
* @throws TInvalidOperationException if the event does not exist
*/
- public function attachEventHandler($name,$handler)
+ public function attachEventHandler($name,$handler,$priority=null)
{
- $this->getEventHandlers($name)->add($handler);
+ $this->getEventHandlers($name)->add($handler,$priority);
}
/**
* Detaches an existing event handler.
- * This method is the opposite of {@link attachEventHandler}.
+ * This method is the opposite of {@link attachEventHandler}. It will detach
+ * any 'on' events definedb by an objects active behaviors as well.
* @param string event name
* @param callback the event handler to be removed
+ * @param numeric|false|null the priority of the handler, defaults to false which translates
+ * to an item of any priority within {@link TPriorityList}; null means the default priority
* @return boolean if the removal is successful
*/
- public function detachEventHandler($name,$handler)
+ public function detachEventHandler($name,$handler,$priority=false)
{
if($this->hasEventHandler($name))
{
try
{
- $this->getEventHandlers($name)->remove($handler);
+ $this->getEventHandlers($name)->remove($handler,$priority);
return true;
}
catch(Exception $e)
@@ -403,42 +1037,133 @@ class TComponent
}
/**
- * Raises an event.
+ * Raises an event. This raises both inter-object 'on' events and global 'fx' events.
* This method represents the happening of an event and will
- * invoke all attached event handlers for the event.
+ * invoke all attached event handlers for the event in {@link TPriorityList} order.
+ * This method does not handle intra-object/behavior dynamic 'dy' events.
+ *
+ * There are ways to handle event responses. By defailt {@link EVENT_RESULT_FILTER},
+ * all event responses are stored in an array, filtered for null responses, and returned.
+ * If {@link EVENT_RESULT_ALL} is specified, all returned results will be stored along
+ * with the sender and param in an array
+ * <code>
+ * $result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
+ * </code>
+ *
+ * If {@link EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
+ * fed forward as the parameters for the next event. This allows for events to filter data
+ * directly by affecting the event parameters
+ *
+ * If a callable function is set in the response type or the post function filter is specified then the
+ * result of each called event handler is post processed by the callable function. Used in
+ * combination with {@link EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
+ *
+ * When raising a global 'fx' event, registered handlers in the global event list for
+ * {@link GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers. In this way,
+ * these global events are always raised for every global 'fx' event. The registered handlers for global
+ * raiseEvent events have priorities. Any registered global raiseEvent event handlers with a priority less than zero
+ * are added before the main event handlers being raised and any registered global raiseEvent event handlers
+ * with a priority equal or greater than zero are added after the main event handlers being raised. In this way
+ * all {@link GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
+ *
+ * Behaviors may implement the following functions:
+ * <code>
+ * public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction[, $chain]) {
+ * return $name; //eg, the event name may be filtered/changed
+ * }
+ * public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name[, $chain]) {
+ * return true; //should this particular handler be executed? true/false
+ * }
+ * public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response[, $chain]) {
+ * //contains the per handler response
+ * }
+ * public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction[, $chain]) {
+ * return $responses;
+ * }
+ * </code>
+ * to be executed when raiseEvent is called. The 'intra' dynamic events are called per handler in
+ * the handler loop.
+ *
+ * dyPreRaiseEvent has the effect of being able to change the event being raised. This intra
+ * object/behavior event returns the name of the desired event to be raised. It will pass through
+ * if no dynamic event is specified, or if the original event name is returned.
+ * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
+ * called for a specific raised event (and associated event arguments)
+ * dyIntraRaiseEventPostHandler does not return anything. This allows behaviors to access the results
+ * of an event handler in the per handler loop.
+ * dyPostRaiseEvent returns the responses. This allows for any post processing of the event
+ * results from the sum of all event handlers
+ *
+ * When handling a catch-all {@link __dycall}, the method name is the name of the event
+ * and the parameters are the sender, the param, and then the name of the event.
+ *
* @param string the event name
* @param mixed the event sender object
* @param TEventParameter the event parameter
+ * @param numeric how the results of the event are tabulated. default: {@link EVENT_RESULT_FILTER} The default filters out
+ * null responses. optional
+ * @param function any per handler filtering of the response result needed is passed through
+ * this if not null. default: null. optional
+ * @return mixed the results of the event
* @throws TInvalidOperationException if the event is undefined
* @throws TInvalidDataValueException If an event handler is invalid
*/
- public function raiseEvent($name,$sender,$param)
+ public function raiseEvent($name,$sender,$param,$responsetype=null,$postfunction=null)
{
+ $p=$param;
+ if(is_callable($responsetype))
+ {
+ $postfunction=$responsetype;
+ $responsetype=null;
+ }
+
+ if($responsetype===null)
+ $responsetype=TEventResults::EVENT_RESULT_FILTER;
+
$name=strtolower($name);
- if(isset($this->_e[$name]))
+ $responses=array();
+
+ $name=$this->dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction);
+
+ if($this->hasEventHandler($name)||$this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER))
{
- foreach($this->_e[$name] as $handler)
+ $handlers=$this->getEventHandlers($name);
+ $handlerArray=$handlers->toArray();
+ if(strncasecmp($name,'fx',2)===0&&$this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER))
+ {
+ $globalhandlers=$this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
+ $handlerArray=array_merge($globalhandlers->toArrayBelowPriority(0),$handlerArray,$globalhandlers->toArrayAbovePriority(0));
+ }
+ $response=null;
+ foreach($handlerArray as $handler)
{
+ if($this->dyIntraRaiseEventTestHandler($handler,$sender,$param,$name)===false)
+ continue;
+
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);
+ if(method_exists($object,$method)||strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
+ {
+ if($method=='__dycall')
+ $response=$object->__dycall($name,array($sender,$param,$name));
+ else
+ $response=$object->$method($sender,$param,$name);
+ }
else
throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler);
}
else
- call_user_func($handler,$sender,$param);
+ $response=call_user_func($handler,$sender,$param,$name);
}
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);
+ if(is_string($object))
+ $response=call_user_func($handler,$sender,$param,$name);
else
{
if(($pos=strrpos($method,'.'))!==false)
@@ -446,27 +1171,66 @@ class TComponent
$object=$this->getSubProperty(substr($method,0,$pos));
$method=substr($method,$pos+1);
}
- if(method_exists($object,$method))
- $object->$method($sender,$param);
+ if(method_exists($object,$method)||strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
+ {
+ if($method=='__dycall')
+ $response=$object->__dycall($name,array($sender,$param,$name));
+ else
+ $response=$object->$method($sender,$param,$name);
+ }
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));
+
+ $this->dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response);
+
+ if($postfunction)
+ $response=call_user_func_array($postfunction,array($sender,$param,$this,$response));
+
+ if($responsetype&TEventResults::EVENT_RESULT_ALL)
+ $responses[]=array('sender'=>$sender,'param'=>$param,'response'=>$response);
+ else
+ $responses[]=$response;
+
+ if($response!==null&&($responsetype&TEventResults::EVENT_RESULT_FEED_FORWARD))
+ $param=$response;
+
}
}
- else if(!$this->hasEvent($name))
+ else if(strncasecmp($name,'on',2)===0&&!$this->hasEvent($name))
throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
+
+ if($responsetype&TEventResults::EVENT_RESULT_FILTER)
+ $responses=array_filter($responses);
+
+ $responses=$this->dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction);
+
+ return $responses;
}
/**
* Evaluates a PHP expression in the context of this control.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyEvaluateExpressionFilter($expression, $chain) {
+ * return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
+ * }
+ * </code>
+ * to be executed when evaluateExpression is called. All attached behaviors are notified through
+ * dyEvaluateExpressionFilter. The chaining is important in this function due to the filtering
+ * pass-through effect.
+ *
+ * @param string PHP expression
* @return mixed the expression result
* @throws TInvalidOperationException if the expression is invalid
*/
public function evaluateExpression($expression)
{
+ $expression=$this->dyEvaluateExpressionFilter($expression);
try
{
if(eval("\$result=$expression;")===false)
@@ -481,12 +1245,24 @@ class TComponent
/**
* Evaluates a list of PHP statements.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyEvaluateStatementsFilter($statements, $chain) {
+ * return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
+ * }
+ * </code>
+ * to be executed when evaluateStatements is called. All attached behaviors are notified through
+ * dyEvaluateStatementsFilter. The chaining is important in this function due to the filtering
+ * pass-through effect.
+ *
* @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)
{
+ $statements=$this->dyEvaluateStatementsFilter($statements);
try
{
ob_start();
@@ -508,11 +1284,22 @@ class TComponent
* The default implementation of this method will invoke
* the potential parent component's {@link addParsedObject}.
* This method can be overridden.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyCreatedOnTemplate($parent, $chain) {
+ * return $chain->dyCreatedOnTemplate($parent); //example
+ * }
+ * </code>
+ * to be executed when createdOnTemplate is called. All attached behaviors are notified through
+ * dyCreatedOnTemplate.
+ *
* @param TComponent potential parent of this control
* @see addParsedObject
*/
public function createdOnTemplate($parent)
{
+ $parent=$this->dyCreatedOnTemplate($parent);
$parent->addParsedObject($this);
}
@@ -521,11 +1308,381 @@ class TComponent
* 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.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyAddParsedObject($object[, $chain]) {
+ * }
+ * </code>
+ * to be executed when addParsedObject is called. All attached behaviors are notified through
+ * dyAddParsedObject.
+ *
* @param string|TComponent text string or component parsed and instantiated in template
* @see createdOnTemplate
*/
public function addParsedObject($object)
{
+ $this->dyAddParsedObject($object);
+ }
+
+
+ /**
+ * This is the method registered for all instanced objects should a class behavior be added after
+ * the class is instanced. Only when the class to which the behavior is being added is in this
+ * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
+ * @param $sender the application
+ * @param $param TClassBehaviorEventParameter
+ */
+ public function fxAttachClassBehavior($sender,$param) {
+ if(in_array($param->getClass(),$this->getClassHierarchy(true)))
+ return $this->attachBehavior($param->getName(),$param->getBehavior(),$param->getPriority());
+ }
+
+
+ /**
+ * This is the method registered for all instanced objects should a class behavior be removed after
+ * the class is instanced. Only when the class to which the behavior is being added is in this
+ * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
+ * @param $sender the application
+ * @param $param TClassBehaviorEventParameter
+ */
+ public function fxDetachClassBehavior($sender,$param) {
+ if(in_array($param->getClass(),$this->getClassHierarchy(true)))
+ return $this->detachBehavior($param->getName(),$param->getPriority());
+ }
+
+
+ /**
+ * This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
+ * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
+ * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
+ * This is done so class behaviors are added last first.
+ * @param string name the key of the class behavior
+ * @param object|string class behavior or name of the object behavior per instance
+ * @param string|class string of class or class on which to attach this behavior. Defaults to null which will error
+ * but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
+ * it should extend.
+ * <code>
+ * TPanel::attachClassBehavior('javascripts', (new TJsPanelBehavior())->init($this));
+ * </code>
+ * @param numeric|null priority of behavior, default: null the default priority of the {@link TPriorityList} Optional.
+ * @throws TInvalidOperationException if the class behavior is being added to a {@link TComponent}; due to recursion.
+ * @throws TInvalidOperationException if the class behavior is already defined
+ */
+ public static function attachClassBehavior($name,$behavior,$class=null,$priority=null) {
+ if(!$class&&function_exists('get_called_class'))
+ $class=get_called_class();
+ if(!$class)
+ throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
+
+ if(!is_string($name))
+ $name=get_class($name);
+ $class=strtolower($class);
+ if($class==='tcomponent')
+ throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
+ if(empty(self::$_um[$class]))
+ self::$_um[$class]=array();
+ if(isset(self::$_um[$class][$name]))
+ throw new TInvalidOperationException('component_class_behavior_defined',$class,$name);
+ $param=new TClassBehaviorEventParameter($class,$name,$behavior,$priority);
+ self::$_um[$class]=array($name=>$param)+self::$_um[$class];
+ $behaviorObject=is_string($behavior)?new $behavior:$behavior;
+ return $behaviorObject->raiseEvent('fxAttachClassBehavior',null,$param);
+ }
+
+
+ /**
+ * This will remove a behavior from a class. It unregisters it from future instances and
+ * pulls the changes from all the instances that are listening as well.
+ * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
+ * @param $name the key of the class behavior
+ * @param $class string class on which to attach this behavior. Defaults to null.
+ * @param $priority numeric|null|false priority. false is any priority, null is default
+ * {@link TPriorityList} priority, and numeric is a specific priority.
+ * @throws Exception if the the class cannot be derived from Late Static Binding and is not
+ * not supplied as a parameter.
+ */
+ public static function detachClassBehavior($name,$class=null,$priority=false) {
+ if(!$class&&function_exists('get_called_class'))
+ $class=get_called_class();
+ if(!$class)
+ throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
+
+ $class=strtolower($class);
+ if(!is_string($name))
+ $name=get_class($name);
+ if(empty(self::$_um[$class])||!isset(self::$_um[$class][$name]))
+ return false;
+ $param=self::$_um[$class][$name];
+ $behavior=$param->getBehavior();
+ unset(self::$_um[$class][$name]);
+ $behaviorObject=is_string($behavior)?new $behavior:$behavior;
+ return $behaviorObject->raiseEvent('fxDetachClassBehavior',null,$param);
+ }
+
+ /**
+ * Returns the named behavior object.
+ * The name 'asa' stands for 'as a'.
+ * @param string the behavior name
+ * @return IBehavior the behavior object, or null if the behavior does not exist
+ */
+ public function asa($behaviorname)
+ {
+ return isset($this->_m[$behaviorname])?$this->_m[$behaviorname]:null;
+ }
+
+ /**
+ * Returns whether or not the object or any of the behaviors are of a particular class.
+ * The name 'isa' stands for 'is a'. This first checks if $this is an instanceof the class.
+ * It then checks each Behavior. If a behavior implements {@link IInstanceCheck},
+ * then the behavior can determine what it is an instanceof. If this behavior function returns true,
+ * then this method returns true. If the behavior instance checking function returns false,
+ * then no further checking is performed as it is assumed to be correct.
+ *
+ * If the behavior instance check function returns nothing or null or the behavior
+ * doesn't implement the {@link IInstanceCheck} interface, then the default instanceof occurs.
+ * The default isa behavior is to check if the behavior is an instanceof the class.
+ *
+ * The behavior {@link IInstanceCheck} is to allow a behavior to have the host object
+ * act as a completely different object.
+ *
+ * @param class or string
+ * @return boolean whether or not the object or a behavior is an instance of a particular class
+ */
+ public function isa($class)
+ {
+ if($this instanceof $class)
+ return true;
+ if($this->_m!==null&&$this->_behaviorsenabled)
+ foreach($this->_m->toArray() as $behavior){
+ if(($behavior instanceof IBehavior)&&!$behavior->getEnabled())
+ continue;
+
+ $check = null;
+ if(($behavior->isa('IInstanceCheck'))&&$check=$behavior->isinstanceof($class,$this))
+ return true;
+ if($check===null&&($behavior->isa($class)))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attaches a list of behaviors to the component.
+ * Each behavior is indexed by its name and should be an instance of
+ * {@link IBehavior}, a string specifying the behavior class, or a
+ * {@link TClassBehaviorEventParameter}.
+ * @param array list of behaviors to be attached to the component
+ */
+ public function attachBehaviors($behaviors)
+ {
+ foreach($behaviors as $name=>$behavior)
+ if($behavior instanceof TClassBehaviorEventParameter)
+ $this->attachBehavior($behavior->getName(),$behavior->getBehavior(),$behavior->getPriority());
+ else
+ $this->attachBehavior($name,$behavior);
+ }
+
+ /**
+ * Detaches select behaviors from the component.
+ * Each behavior is indexed by its name and should be an instance of
+ * {@link IBehavior}, a string specifying the behavior class, or a
+ * {@link TClassBehaviorEventParameter}.
+ * @param array list of behaviors to be detached from the component
+ */
+ public function detachBehaviors($behaviors)
+ {
+ if($this->_m!==null)
+ {
+ foreach($behaviors as $name=>$behavior)
+ if($behavior instanceof TClassBehaviorEventParameter)
+ $this->detachBehavior($behavior->getName(),$behavior->getPriority());
+ else
+ $this->detachBehavior(is_string($behavior)?$behavior:$name);
+ }
+ }
+
+ /**
+ * Detaches all behaviors from the component.
+ */
+ public function clearBehaviors()
+ {
+ if($this->_m!==null)
+ {
+ foreach($this->_m->toArray() as $name=>$behavior)
+ $this->detachBehavior($name);
+ $this->_m=null;
+ }
+ }
+
+ /**
+ * Attaches a behavior to this component.
+ * This method will create the behavior object based on the given
+ * configuration. After that, the behavior object will be initialized
+ * by calling its {@link IBehavior::attach} method.
+ *
+ * Already attached behaviors may implement the function:
+ * <code>
+ * public function dyAttachBehavior($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when attachBehavior is called. All attached behaviors are notified through
+ * dyAttachBehavior.
+ *
+ * @param string the behavior's name. It should uniquely identify this behavior.
+ * @param mixed the behavior configuration. This is passed as the first
+ * parameter to {@link YiiBase::createComponent} to create the behavior object.
+ * @return IBehavior the behavior object
+ */
+ public function attachBehavior($name,$behavior,$priority=null)
+ {
+ if(is_string($behavior))
+ $behavior=Prado::createComponent($behavior);
+ if(!($behavior instanceof IBaseBehavior))
+ throw new TInvalidDataTypeException('component_not_a_behavior',get_class($behavior));
+ if($behavior instanceof IBehavior)
+ $behavior->setEnabled(true);
+ if($this->_m===null)
+ $this->_m=new TPriorityMap;
+ $behavior->attach($this);
+ $this->dyAttachBehavior($name,$behavior);
+ $this->_m->add($name,$behavior,$priority);
+ return $behavior;
+ }
+
+ /**
+ * Detaches a behavior from the component.
+ * The behavior's {@link IBehavior::detach} method will be invoked.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyDetachBehavior($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when detachBehavior is called. All attached behaviors are notified through
+ * dyDetachBehavior.
+ *
+ * @param string the behavior's name. It uniquely identifies the behavior.
+ * @param numeric the behavior's priority. This defaults to false, aka any priority.
+ * @return IBehavior the detached behavior. Null if the behavior does not exist.
+ */
+ public function detachBehavior($name,$priority=false)
+ {
+ if($this->_m!=null&&isset($this->_m[$name]))
+ {
+ $this->_m[$name]->detach($this);
+ $behavior=$this->_m->itemAt($name);
+ $this->_m->remove($name,$priority);
+ $this->dyDetachBehavior($name,$behavior);
+ return $behavior;
+ }
+ }
+
+ /**
+ * Enables all behaviors attached to this component independent of the behaviors
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyEnableBehaviors($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when enableBehaviors is called. All attached behaviors are notified through
+ * dyEnableBehaviors.
+ *
+ */
+ public function enableBehaviors()
+ {
+ if(!$this->_behaviorsenabled)
+ {
+ $this->_behaviorsenabled=true;
+ $this->dyEnableBehaviors();
+ }
+ }
+
+ /**
+ * Disables all behaviors attached to this component independent of the behaviors
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyDisableBehaviors($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when disableBehaviors is called. All attached behaviors are notified through
+ * dyDisableBehaviors.
+ *
+ */
+ public function disableBehaviors()
+ {
+ if($this->_behaviorsenabled)
+ {
+ $this->dyDisableBehaviors();
+ $this->_behaviorsenabled=false;
+ }
+ }
+
+
+ /**
+ * Returns if all the behaviors are turned on or off for the object.
+ * @return boolean whether or not all behaviors are enabled (true) or not (false)
+ */
+ public function getBehaviorsEnabled()
+ {
+ return $this->_behaviorsenabled;
+ }
+
+ /**
+ * Enables an attached object behavior. This cannot enable or disable whole class behaviors.
+ * A behavior is only effective when it is enabled.
+ * A behavior is enabled when first attached.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyEnableBehavior($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when enableBehavior is called. All attached behaviors are notified through
+ * dyEnableBehavior.
+ *
+ * @param string the behavior's name. It uniquely identifies the behavior.
+ */
+ public function enableBehavior($name)
+ {
+ if($this->_m!=null&&isset($this->_m[$name])){
+ if($this->_m[$name] instanceof IBehavior) {
+ $this->_m[$name]->setEnabled(true);
+ $this->dyEnableBehavior($name,$this->_m[$name]);
+ return true;
+ }
+ return false;
+ }
+ return null;
+ }
+
+ /**
+ * Disables an attached behavior. This cannot enable or disable whole class behaviors.
+ * A behavior is only effective when it is enabled.
+ *
+ * Behaviors may implement the function:
+ * <code>
+ * public function dyDisableBehavior($name,$behavior[, $chain]) {
+ * }
+ * </code>
+ * to be executed when disableBehavior is called. All attached behaviors are notified through
+ * dyDisableBehavior.
+ *
+ * @param string the behavior's name. It uniquely identifies the behavior.
+ */
+ public function disableBehavior($name)
+ {
+ if($this->_m!=null&&isset($this->_m[$name])){
+ if($this->_m[$name] instanceof IBehavior) {
+ $this->_m[$name]->setEnabled(false);
+ $this->dyDisableBehavior($name,$this->_m[$name]);
+ return true;
+ }
+ return false;
+ }
+ return null;
}
/**
@@ -554,6 +1711,91 @@ class TComponent
}
}
+
+/**
+ * IDynamicMethods interface.
+ * IDynamicMethods marks an object to receive undefined global or dynamic events.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IDynamicMethods
+{
+ public function __dycall($method,$args);
+}
+
+
+
+/**
+ * TClassBehaviorEventParameter class.
+ * TClassBehaviorEventParameter is the parameter sent with the class behavior changes.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+class TClassBehaviorEventParameter extends TEventParameter
+{
+ private $_class;
+ private $_name;
+ private $_behavior;
+ private $_priority;
+
+ /**
+ * Holds the parameters for the Class Behavior Events
+ * @param string $class this is the class to get the behavior
+ * @param string $name the name of the behavior
+ * @param object $behavior this is the behavior to implement the class behavior
+ */
+ public function __construct($class,$name,$behavior,$priority)
+ {
+ $this->_class=$class;
+ $this->_name=$name;
+ $this->_behavior=$behavior;
+ $this->_priority=$priority;
+ }
+
+ /**
+ * This is the class to get the behavior
+ * @return string the class to get the behavior
+ */
+ public function getClass()
+ {
+ return $this->_class;
+ }
+
+ /**
+ * name of the behavior
+ * @return string the name to get the behavior
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * This is the behavior which the class is to get
+ * @return object the behavior to implement
+ */
+ public function getBehavior()
+ {
+ return $this->_behavior;
+ }
+
+ /**
+ * This is the priority which the behavior is to get
+ * @return numeric the priority of the behavior
+ */
+ public function getPriority()
+ {
+ return $this->_priority;
+ }
+}
+
+
/**
* TEnumerable class.
* TEnumerable is the base class for all enumerable types.
@@ -578,11 +1820,11 @@ class TComponent
*/
class TEnumerable implements Iterator
{
- private $_enums = array();
+ private $_enums=array();
public function __construct() {
- $reflection = new ReflectionClass($this);
- $this->_enums = $reflection->getConstants();
+ $reflection=new ReflectionClass($this);
+ $this->_enums=$reflection->getConstants();
}
public function current() {
@@ -602,7 +1844,7 @@ class TEnumerable implements Iterator
}
public function valid() {
- return $this->current() !== false;
+ return $this->current()!==false;
}
}
@@ -779,7 +2021,7 @@ class TPropertyValue
*/
public static function ensureNullIfEmpty($value)
{
- return empty($value) ? null : $value;
+ return empty($value)?null:$value;
}
}
@@ -796,6 +2038,12 @@ class TEventParameter extends TComponent
{
}
+class TEventResults extends TEnumerable {
+ const EVENT_RESULT_FEED_FORWARD=1;
+ const EVENT_RESULT_FILTER=2;
+ const EVENT_RESULT_ALL=4;
+}
+
/**
* TComponentReflection class.
*
@@ -978,6 +2226,117 @@ class TComponentReflection extends TComponent
}
}
+
+
+
+
+/**
+ * IBaseBehavior interface is the base behavior class from which all other
+ * behaviors types are derived
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IBaseBehavior {
+ /**
+ * Attaches the behavior object to the component.
+ * @param CComponent the component that this behavior is to be attached to.
+ */
+ public function attach($component);
+ /**
+ * Detaches the behavior object from the component.
+ * @param CComponent the component that this behavior is to be detached from.
+ */
+ public function detach($component);
+}
+
+/**
+ * IBehavior interfaces is implemented by instance behavior classes.
+ *
+ * A behavior is a way to enhance a component with additional methods and
+ * events that are defined in the behavior class and not available in the
+ * class. Objects may signal behaviors through dynamic events.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IBehavior extends IBaseBehavior
+{
+ /**
+ * @return boolean whether this behavior is enabled
+ */
+ public function getEnabled();
+ /**
+ * @param boolean whether this behavior is enabled
+ */
+ public function setEnabled($value);
+}
+
+
+/**
+ * IClassBehavior interface is implements behaviors across all instances of
+ * a particular class
+ *
+ * Any calls to functions not present in the original object but to behaviors
+ * derived from this class, will have inserted as the first argument parameter
+ * the object containing the behavior.
+ *
+ * For example:
+ * <code>
+ * $objWithClassBehavior->MethodOfClassBehavior(1, 20);
+ * </code>
+ * will be acted within the class behavior like this:
+ * <code>
+ * public function MethodOfClassBehavior($object, $firstParam, $secondParam){
+ * // $object === $objWithClassBehavior, $firstParam === 1, $secondParam === 20
+ * }
+ * </code>
+ *
+ * This also holds for 'dy' events as well. For dynamic events, method arguments would be:
+ * <code>
+ * public function dyMethodOfClassBehavior($object, $firstParam, $secondParam, $callchain){
+ * // $object === $objWithClassBehavior, $firstParam === 1, $secondParam === 20, $callchain instanceof {@link TCallChain}
+ * }
+ * </code>
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IClassBehavior extends IBaseBehavior {
+}
+
+
+/**
+ * IInstanceCheck This interface allows objects to determine their own
+ * 'instanceof' results when {@link TComponent::isa} is called. This is
+ * important with behaviors because behaviors may want to look like
+ * particular objects other than themselves.
+ *
+ * @author Brad Anderson <javalizard@mac.com>
+ * @version $Id$
+ * @package System
+ * @since 3.2
+ */
+interface IInstanceCheck {
+ /**
+ * The method checks $this or, if needed, the parameter $instance is of type
+ * class. In the case of a Class Behavior, the instance to which the behavior
+ * is attached may be important to determine if $this is an instance
+ * of a particular class.
+ * @param class|string the component that this behavior is checking if it is an instanceof.
+ * @param object the object which the behavior is attached to. default: null
+ * @return boolean|null if the this or the instance is of type class. When null, no information could be derived and
+ * the default mechanisms take over.
+ */
+ public function isinstanceof($class,$instance=null);
+}
+
/**
* TJavaScriptLiteral class that encloses string literals that are not
* supposed to be escaped by {@link TJavaScript::encode() }
@@ -1035,3 +2394,4 @@ class TJavaScriptString extends TJavaScriptLiteral
return TJavaScript::jsonEncode((string)$this->_s,JSON_HEX_QUOT | JSON_HEX_APOS | JSON_HEX_TAG);
}
}
+
diff --git a/framework/Util/TBehavior.php b/framework/Util/TBehavior.php
new file mode 100644
index 00000000..465f9673
--- /dev/null
+++ b/framework/Util/TBehavior.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * CBehavior class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2009 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CBehavior is a convenient base class for behavior classes.
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: CBehavior.php 564 2009-01-21 22:07:10Z qiang.xue $
+ * @package system.base
+ * @since 1.0.2
+ */
+class TBehavior extends TComponent implements IBehavior
+{
+ private $_enabled;
+ private $_owner;
+
+ /**
+ * Declares events and the corresponding event handler methods.
+ * The events are defined by the {@link owner} component, while the handler
+ * methods by the behavior class. The handlers will be attached to the corresponding
+ * events when the behavior is attached to the {@link owner} component; and they
+ * will be detached from the events when the behavior is detached from the component.
+ * @return array events (array keys) and the corresponding event handler methods (array values).
+ */
+ public function events()
+ {
+ return array();
+ }
+
+ /**
+ * Attaches the behavior object to the component.
+ * The default implementation will set the {@link owner} property
+ * and attach event handlers as declared in {@link events}.
+ * Make sure you call the parent implementation if you override this method.
+ * @param TComponent the component that this behavior is to be attached to.
+ */
+ public function attach($owner)
+ {
+ $this->_owner=$owner;
+ foreach($this->events() as $event=>$handler)
+ $owner->attachEventHandler($event,array($this,$handler));
+ }
+
+ /**
+ * Detaches the behavior object from the component.
+ * The default implementation will unset the {@link owner} property
+ * and detach event handlers declared in {@link events}.
+ * Make sure you call the parent implementation if you override this method.
+ * @param TComponent the component that this behavior is to be detached from.
+ */
+ public function detach($owner)
+ {
+ foreach($this->events() as $event=>$handler)
+ $owner->detachEventHandler($event,array($this,$handler));
+ $this->_owner=null;
+ }
+
+ /**
+ * @return CComponent the owner component that this behavior is attached to.
+ */
+ public function getOwner()
+ {
+ return $this->_owner;
+ }
+
+ /**
+ * @return boolean whether this behavior is enabled
+ */
+ public function getEnabled()
+ {
+ return $this->_enabled;
+ }
+
+ /**
+ * @param boolean whether this behavior is enabled
+ */
+ public function setEnabled($value)
+ {
+ $this->_enabled=$value;
+ }
+} \ No newline at end of file
diff --git a/framework/Util/TCallChain.php b/framework/Util/TCallChain.php
new file mode 100644
index 00000000..ae6e02fc
--- /dev/null
+++ b/framework/Util/TCallChain.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * TCallChain class file.
+ *
+ * @author Brad Anderson <javalizard@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2008-2011 Pradosoft
+ * @license http://www.pradosoft.com/license/
+ */
+
+/**
+ * TCallChain is a recursive event calling mechanism. This class implements
+ * the {@link IDynamicMethods} class so that any 'dy' event calls can be caught
+ * and patched through to the intended recipient
+ * @author Brad Anderson <javalizard@gmail.com>
+ * @version $Id: TCallChain.php 564 2009-01-21 22:07:10Z javalizard $
+ * @package system.base
+ * @since 3.2
+ */
+class TCallChain extends TList implements IDynamicMethods
+{
+ /**
+ * @var {@link TListIterator} for moving through the chained method calls
+ */
+ private $_iterator=null;
+
+ /**
+ * @var string the method name of the call chain
+ */
+ private $_method=null;
+
+ /**
+ * This initializes the list and the name of the method to be called
+ * @param string the name of the function call
+ */
+ public function __construct($method) {
+ $this->_method=$method;
+ parent::__construct();
+ }
+
+
+ /**
+ * This initializes the list and the name of the method to be called
+ * @param string|array this is a callable function as a string or array with
+ * the object and method name as string
+ * @param array The array of arguments to the function call chain
+ */
+ public function addCall($method,$args)
+ {
+ $this->add(array($method,$args));
+ }
+
+ /**
+ * This method calls the next Callable in the list. All of the method arguments
+ * coming into this method are substituted into the original method argument of
+ * call in the chain.
+ *
+ * If the original method call has these parameters
+ * <code>
+ * $originalobject->dyExampleMethod('param1', 'param2', 'param3')
+ * </code>
+ * <code>
+ * $callchain->dyExampleMethod('alt1', 'alt2')
+ * </code>
+ * then the next call in the call chain will recieve the parameters as if this were called
+ * <code>
+ * $behavior->dyExampleMethod('alt1', 'alt2', 'param3', $callchainobject)
+ * </code>
+ *
+ * When dealing with {@link IClassBehaviors}, the first parameter of the stored argument
+ * list in 'dy' event calls is always the object containing the behavior. This modifies
+ * the parameter replacement mechanism slightly to leave the object containing the behavior
+ * alone and only replacing the other parameters in the argument list. As per {@link __call},
+ * any calls to a 'dy' event do not need the object containing the behavior as the addition of
+ * the object to the argument list as the first element is automatic for IClassBehaviors.
+ *
+ * The last parameter of the method parameter list for any callable in the call chain
+ * will be the TCallChain object itself. This is so that any behavior implementing
+ * these calls will have access to the call chain. Each callable should either call
+ * the TCallChain call method internally for direct chaining or call the method being
+ * chained (in which case the dynamic handler will pass through to this call method).
+ *
+ * If the dynamic intra object/behavior event is not called in the behavior implemented
+ * dynamic method, it will return to this method and call the following behavior
+ * implementation so as no behavior with an implementation of the dynamic event is left
+ * uncalled. This does break the call chain though and will not act as a "parameter filter".
+ *
+ * When there are no handlers or no handlers left, it returns the first parameter of the
+ * argument list.
+ *
+ */
+ public function call()
+ {
+ $args=func_get_args();
+ if($this->getCount()===0)
+ return isset($args[0])?$args[0]:null;
+
+ if(!$this->_iterator)
+ {
+ $chain_array=array_reverse($this->toArray());
+ $this->_iterator=new TListIterator($chain_array);
+ }
+ if($this->_iterator->valid())
+ do {
+ $handler=$this->_iterator->current();
+ $this->_iterator->next();
+ if(is_array($handler[0])&&$handler[0][0] instanceof IClassBehavior)
+ array_splice($handler[1],1,count($args),$args);
+ else
+ array_splice($handler[1],0,count($args),$args);
+ $handler[1][]=$this;
+ $result=call_user_func_array($handler[0],$handler[1]);
+ } while($this->_iterator->valid());
+ else
+ $result = $args[0];
+ return $result;
+ }
+
+
+ /**
+ * This catches all the unpatched dynamic events. When the method call matches the
+ * call chain method, it passes the arguments to the original __call (of the dynamic
+ * event being unspecified in TCallChain) and funnels into the method {@link call},
+ * so the next dynamic event handler can be called.
+ * If the original method call has these parameters
+ * <code>
+ * $originalobject->dyExampleMethod('param1', 'param2', 'param3')
+ * </code>
+ * and within the chained dynamic events, this can be called
+ * <code>
+ * class DyBehavior extends TBehavior {
+ * public function dyExampleMethod($param1, $param2, $param3, $callchain)
+ * $callchain->dyExampleMethod($param1, $param2, $param3)
+ * }
+ * {
+ * </code>
+ * to call the next event in the chain.
+ * @param string method name of the unspecified object method
+ * @param array arguments to the unspecified object method
+ */
+ public function __dycall($method,$args)
+ {
+ if($this->_method==$method)
+ return call_user_func_array(array($this,'call'),$args);
+ return null;
+ }
+} \ No newline at end of file
diff --git a/framework/Util/TClassBehavior.php b/framework/Util/TClassBehavior.php
new file mode 100644
index 00000000..807549b7
--- /dev/null
+++ b/framework/Util/TClassBehavior.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * TClassBehavior class file.
+ *
+ * @author Brad Anderson <javalizard@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2008-2011 Pradosoft
+ * @license http://www.pradosoft.com/license/
+ */
+
+/**
+ * TClassBehavior is a convenient base class for whole class behaviors.
+ * @author Brad Anderson <javalizard@gmail.com>
+ * @version $Id: TClassBehavior.php 564 2009-01-21 22:07:10Z javalizard $
+ * @package system.base
+ * @since 3.1.8
+ */
+class TClassBehavior extends TComponent implements IClassBehavior
+{
+
+ /**
+ * Attaches the behavior object to the component.
+ * @param TComponent the component that this behavior is to be attached to.
+ */
+ public function attach($component)
+ {
+ }
+
+ /**
+ * Detaches the behavior object from the component.
+ * @param TComponent the component that this behavior is to be detached from.
+ */
+ public function detach($component)
+ {
+ }
+} \ No newline at end of file
diff --git a/tests/unit/TComponentTest.php b/tests/unit/TComponentTest.php
index 25292c0d..7722f9d6 100644
--- a/tests/unit/TComponentTest.php
+++ b/tests/unit/TComponentTest.php
@@ -1,11 +1,21 @@
<?php
require_once dirname(__FILE__).'/phpunit.php';
+Prado::using('System.Util.*');
+Prado::using('System.Collections.TPriorityList');
+Prado::using('System.Collections.TPriorityMap');
+
class NewComponent extends TComponent {
private $_object = null;
private $_text = 'default';
private $_eventHandled = false;
+ private $_return = null;
+ private $_colorattribute = null;
+ public function getAutoGlobalListen() {
+ return true;
+ }
+
public function getText() {
return $this->_text;
}
@@ -14,6 +24,14 @@ class NewComponent extends TComponent {
$this->_text=$value;
}
+ public function getReadOnlyProperty() {
+ return 'read only';
+ }
+
+ public function getJsReadOnlyJsProperty() {
+ return 'js read only';
+ }
+
public function getObject() {
if(!$this->_object) {
$this->_object=new NewComponent;
@@ -30,10 +48,363 @@ class NewComponent extends TComponent {
$this->_eventHandled=true;
}
+ public function eventReturnValue($sender,$param) {
+ return $param->Return;
+ }
+
public function isEventHandled() {
return $this->_eventHandled;
}
+
+ public function resetEventHandled() {
+ $this->_eventHandled = false;
+ }
+ public function getReturn() {
+ return $this->_return;
+ }
+ public function setReturn($return) {
+ $this->_return = $return;
+ }
+ public function getjsColorAttribute() {
+ return $this->_colorattribute;
+ }
+ public function setjsColorAttribute($colorattribute) {
+ $this->_colorattribute = $colorattribute;
+ }
+}
+
+
+class NewComponentNoListen extends NewComponent {
+ // this object does _not_ auto install global listeners during construction
+ public function getAutoGlobalListen() {
+ return false;
+ }
+}
+
+class DynamicCatchingComponent extends NewComponentNoListen implements IDynamicMethods {
+ public function __dycall($method, $args) {
+
+ }
+}
+
+
+class GlobalRaiseComponent extends NewComponent implements IDynamicMethods {
+ private $_callorder = array();
+
+ public function getCallOrders()
+ {
+ return $this->_callorder;
+ }
+ public function __dycall($method, $args) {
+ if(strncasecmp($method,'fx',2)!==0) return;
+ $this->_callorder[] = 'fxcall';
+ }
+ public function fxGlobalListener($sender,$param,$name) {
+ $this->_callorder[] = 'fxGL';
+ }
+ public function fxPrimaryGlobalEvent($sender,$param,$name) {
+ $this->_callorder[] = 'primary';
+ }
+ public function commonRaiseEventListener($sender,$param,$name) {
+ $this->_callorder[] = 'com';
+ }
+ public function postglobalRaiseEventListener($sender,$param,$name) {
+ $this->_callorder[] = 'postgl';
+ }
+ public function preglobalRaiseEventListener($sender,$param,$name) {
+ $this->_callorder[] = 'pregl';
+ }
+}
+
+
+
+class FooClassBehavior extends TClassBehavior {
+ private $_baseObject = null;
+ public function faaEverMore($object, $laa, $sol) {
+ $this->_baseObject = $object;
+ return $laa * $sol;
+ }
+ public function getLastClassObject() {return $this->_baseObject;}
+}
+
+class FooFooClassBehavior extends FooClassBehavior {
+ public function faafaaEverMore($object, $laa, $sol) {
+
+ }
+}
+
+class BarClassBehavior extends TClassBehavior {
+ public function moreFunction($object, $laa, $sol) {
+ return true;
+ }
+}
+
+
+
+class FooBehavior extends TBehavior {
+ public function faaEverMore($laa, $sol) {
+ return true;
+ }
}
+class FooFooBehavior extends FooBehavior {
+
+ public function faafaaEverMore($laa, $sol) {
+ return sqrt($laa * $laa + $sol * $sol);
+ }
+}
+class FooBarBehavior extends TBehavior {
+ public function moreFunction($laa, $sol) {
+ return $laa * $sol * $sol;
+ }
+}
+
+class PreBarBehavior extends TBehavior {
+}
+
+class BarBehavior extends PreBarBehavior implements IInstanceCheck {
+ private $_instanceReturn = null;
+
+ public function moreFunction($laa, $sol) {
+ return pow($laa+$sol+1, 2);
+ }
+
+ public function isinstanceof($class, $instance=null) {
+ return $this->_instanceReturn;
+ }
+ public function setInstanceReturn($value) {
+ $this->_instanceReturn = $value;
+ }
+}
+class DynamicCallComponent extends NewComponent implements IDynamicMethods {
+ public function __dycall($method, $args) {
+ if($method === 'dyPowerFunction')
+ return pow($args[0], $args[1]);
+ if($method === 'dyDivisionFunction')
+ return $args[0] / $args[1];
+ if($method === 'fxPowerFunction')
+ return 2*pow($args[0], $args[1]);
+ if($method === 'fxDivisionFunction')
+ return 2*$args[0] / $args[1];
+ }
+}
+
+
+
+class BehaviorTestBehavior extends TBehavior {
+ private $excitement = 'faa';
+ public function getExcitement() {
+ return $this->excitement;
+ }
+ public function setExcitement($value) {
+ $this->excitement = $value;
+ }
+ public function getReadOnly() {
+ return true;
+ }
+
+ public function onBehaviorEvent($sender, $param,$responsetype=null,$postfunction=null) {
+ return $this->getOwner()->raiseEvent('onBehaviorEvent',$sender,$param,$responsetype,$postfunction);
+ }
+ public function fxGlobalBehaviorEvent($sender, $param) {
+ }
+}
+
+class dy1TextReplace extends TBehavior {
+ protected $_called = false;
+ public function dyTextFilter($text, $callchain) {
+ $this->_called = true;
+ return str_replace("..", '__', $callchain->dyTextFilter($text));
+ }
+ public function isCalled() {
+ return $this->_called;
+ }
+ public function dyPowerFunction($x, $y, $callchain)
+ {
+ return pow($x / $callchain->dyPowerFunction($x, $y), $y);
+ }
+}
+class dy2TextReplace extends dy1TextReplace {
+ public function dyTextFilter($text, $callchain) {
+ $this->_called = true;
+ return str_replace("++", '||', $callchain->dyTextFilter($text));
+ }
+}
+class dy3TextReplace extends dy1TextReplace {
+ public function dyTextFilter($text, $callchain) {
+ $this->_called = true;
+ return str_replace("!!", '??', $callchain->dyTextFilter($text));
+ }
+}
+
+class dy1ClassTextReplace extends TClassBehavior {
+ protected $_called = false;
+ public function dyTextFilter($hostobject, $text, $callchain) {
+ $this->_called = true;
+ return str_replace("__", '..', $callchain->dyTextFilter($text));
+ }
+ public function isCalled() {
+ return $this->_called;
+ }
+ public function dyPowerFunction($hostobject, $x, $y, $callchain)
+ {
+ return pow($x / $callchain->dyPowerFunction($x, $y), $y);
+ }
+}
+class dy2ClassTextReplace extends dy1ClassTextReplace {
+ public function dyTextFilter($hostobject, $text, $callchain) {
+ $this->_called = true;
+ return str_replace("||", '++', $callchain->dyTextFilter($text));
+ }
+}
+class dy3ClassTextReplace extends dy1ClassTextReplace {
+ public function dyTextFilter($hostobject, $text, $callchain) {
+ $this->_called = true;
+ return str_replace("??", '^_^', $callchain->dyTextFilter($text));
+ }
+}
+
+
+class IntraObjectExtenderBehavior extends TBehavior {
+
+ private $lastCall = null;
+ private $arglist = null;
+
+ public function getLastCall() {
+ $v = $this->lastCall;
+ $this->lastCall = null;
+ return $v;
+ }
+
+ public function getLastArgumentList() {
+ $v = $this->arglist;
+ $this->arglist = null;
+ return $v;
+ }
+
+
+
+ public function dyListen($fx, $chain) {
+ $this->lastCall = 1;
+ $this->arglist = func_get_args();
+
+ return $chain->dyListen($fx); // Calls the next event, within a chain
+ }
+ public function dyUnlisten($fx, $chain) {
+ $this->lastCall = 2;
+ $this->arglist = func_get_args();
+
+ return $chain->dyUnlisten($fx);
+ }
+ public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction, $chain) {
+ $this->lastCall = 3;
+ $this->arglist = func_get_args();
+
+ return $chain->dyPreRaiseEvent($name);// Calls the next event, within a chain, if parameters are left off, they are filled in with
+ // the original parameters passed to the dynamic event. Parameters can be passed if they are changed.
+ }
+ public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name, $chain) {
+ $this->lastCall += 4;
+ $this->arglist = func_get_args();
+ }
+ public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler, $chain) {
+ $this->lastCall += 5;
+ $this->arglist = func_get_args();
+ }
+ public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction, $chain) {
+ $this->lastCall += 6;
+ $this->arglist = func_get_args();
+ }
+ public function dyEvaluateExpressionFilter($expression, $chain) {
+ $this->lastCall = 7;
+ $this->arglist = func_get_args();
+ return $expression;
+ }
+ public function dyEvaluateStatementsFilter($statement, $chain) {
+ $this->lastCall = 8;
+ $this->arglist = func_get_args();
+ return $statement;
+ }
+ public function dyCreatedOnTemplate($parent, $chain) {
+ $this->lastCall = 9;
+ $this->arglist = func_get_args();
+ return $parent;
+ }
+ public function dyAddParsedObject($object, $chain) {
+ $this->lastCall = 10;
+ $this->arglist = func_get_args();
+ }
+ public function dyAttachBehavior($name,$behavior, $chain) {
+ $this->lastCall = 11;
+ $this->arglist = func_get_args();
+ }
+ public function dyDetachBehavior($name,$behavior, $chain) {
+ $this->lastCall = 12;
+ $this->arglist = func_get_args();
+ }
+ public function dyEnableBehaviors($chain) {
+ $this->lastCall += 13;
+ $this->arglist = func_get_args();
+ }
+ public function dyDisableBehaviors($chain) {
+ $this->lastCall = 14;
+ $this->arglist = func_get_args();
+ }
+ public function dyEnableBehavior($name,$behavior, $chain) {
+ $this->lastCall = 15;
+ $this->arglist = func_get_args();
+ }
+ public function dyDisableBehavior($name,$behavior, $chain) {
+ $this->lastCall = 16;
+ $this->arglist = func_get_args();
+ }
+}
+
+
+class IntraClassObjectExtenderBehavior extends TClassBehavior {
+}
+
+
+class TDynamicBehavior extends TBehavior implements IDynamicMethods {
+ private $_dyMethod = null;
+ public function getLastBehaviorDynamicMethodCalled() {
+ return $this->_dyMethod;
+ }
+ public function __dycall($method, $args) {
+ $this->_dyMethod = $method;
+ if($method == 'dyTestDynamicBehaviorMethod')
+ return $args[0] / $args[1];
+ }
+ public function dyTestIntraEvent($param1, $param2, $chain) {
+ return $chain->dyTestIntraEvent($param1*2*$param2, $param2);
+ }
+ public function TestBehaviorMethod($param1, $param2) {
+ return $param1 * $param2;
+ }
+}
+
+
+class TDynamicClassBehavior extends TClassBehavior implements IDynamicMethods {
+ private $_dyMethod = null;
+ public function getLastBehaviorDynamicMethodCalled() {
+ return $this->_dyMethod;
+ }
+ //Dynamic Calls within class behaviors contain the main object as the first parameter within the args
+ public function __dycall($method, $args) {
+ $this->_dyMethod = $method;
+ $object = array_shift($args);
+ if($method == 'dyTestDynamicClassBehaviorMethod')
+ return $args[0] / $args[1];
+ }
+ public function dyTestIntraEvent($object, $param1, $param2, $chain) {
+ return $chain->dyTestIntraEvent($param1*2*$param2, $param2);
+ }
+ public function TestBehaviorMethod($object, $param1, $param2) {
+ return $param1 * $param2;
+ }
+}
+
+
+
/**
* @package System
@@ -46,26 +417,761 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->component = new NewComponent();
}
+
public function tearDown() {
+ // PHP version 5.3.6 doesn't call the __destruct method when unsetting variables;
+ // Thus any object that listens must be explicitly call unlisten in this version of PHP.
+ if($this->component)
+ $this->component->unlisten();
$this->component = null;
}
+
+
+ public function testGetListeningToGlobalEvents() {
+ $this->assertEquals(true, $this->component->getListeningToGlobalEvents());
+ $this->component->unlisten();
+ $this->assertEquals(false, $this->component->getListeningToGlobalEvents());
+ }
+
+
+ public function testConstructorAutoListen() {
+ // the default object auto installs class behavior hooks
+ $this->assertEquals(1, $this->component->getEventHandlers('fxAttachClassBehavior')->getCount());
+ $this->assertEquals(1, $this->component->getEventHandlers('fxDetachClassBehavior')->getCount());
+ $this->assertTrue($this->component->getListeningToGlobalEvents());
+
+ // this object does not auto install class behavior hooks, thus not changing the global event structure.
+ // Creating a new instance should _not_ influence the fxAttachClassBehavior and fxDetachClassBehavior
+ // count.
+ $component_nolisten = new NewComponentNoListen();
+ $this->assertEquals(1, $this->component->getEventHandlers('fxAttachClassBehavior')->getCount());
+ $this->assertEquals(1, $this->component->getEventHandlers('fxDetachClassBehavior')->getCount());
+ $this->assertEquals(1, $component_nolisten->getEventHandlers('fxAttachClassBehavior')->getCount());
+ $this->assertEquals(1, $component_nolisten->getEventHandlers('fxDetachClassBehavior')->getCount());
+
+ // tests order of class behaviors when a parent and class have class behavior.
+ // The child should override the parent object-oriented programming style
+ $this->component->attachClassBehavior('Bar', 'BarBehavior', 'NewComponentNoListen');
+ $this->component->attachClassBehavior('FooBar', 'FooBarBehavior', 'NewComponent');
+
+ //create new object with new class behaviors built in, defined in the two lines above
+ $component = new NewComponentNoListen;
+
+ $this->assertEquals(25, $component->moreFunction(2, 2));
+
+ $this->assertEquals(25, $component->Bar->moreFunction(2, 2));
+ $this->assertEquals(8, $component->FooBar->moreFunction(2, 2));
+
+ $component->unlisten();// unwind object and class behaviors
+ $this->component->detachClassBehavior('FooBar', 'NewComponent');
+ $this->component->detachClassBehavior('Bar', 'NewComponentNoListen');
+
+ }
+
+
+ public function testListenAndUnlisten() {
+
+ $component = new NewComponentNoListen();
+
+ $this->assertEquals(false, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(1, $component->getEventHandlers('fxAttachClassBehavior')->getCount());
+
+ $this->assertEquals(2, $component->listen());
+
+ $this->assertEquals(true, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(2, $component->getEventHandlers('fxAttachClassBehavior')->getCount());
+
+ $this->assertEquals(2, $component->unlisten());
+
+ $this->assertEquals(false, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(1, $component->getEventHandlers('fxAttachClassBehavior')->getCount());
+ }
+
+
+ public function testListenAndUnlistenWithDynamicEventCatching() {
+
+ $component = new DynamicCatchingComponent();
+
+ $this->assertEquals(false, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(0, $component->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER)->getCount());
+
+ // this adds the fxAttachClassBehavior, fxDetachClassBehavior, and __dycall of the component
+ $this->assertEquals(3, $component->listen());
+
+ $this->assertEquals(true, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(1, $component->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER)->getCount());
+
+ $this->assertEquals(3, $component->unlisten());
+
+ $this->assertEquals(false, $component->getListeningToGlobalEvents());
+
+ //This is from $this->component being instanced and listening. $component is accessing the global event structure
+ $this->assertEquals(0, $component->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER)->getCount());
+ }
+
+
+
+ //Test Class behaviors
+ public function testAttachClassBehavior() {
+
+ // ensure that the class is listening
+ $this->assertEquals(1, $this->component->getEventHandlers('fxAttachClassBehavior')->getCount());
+
+ //Test that the component is not a FooClassBehavior
+ $this->assertNull($this->component->asa('FooClassBehavior'), "Component is already a FooClassBehavior and should not have this behavior");
+
+ //Add the FooClassBehavior
+ $this->component->attachClassBehavior('FooClassBehavior', new FooClassBehavior);
+
+ //Test that the existing listening component can be a FooClassBehavior
+ $this->assertNotNull($this->component->asa('FooClassBehavior'), "Component is does not have the FooClassBehavior and should have this behavior");
+
+ // test if the function modifies new instances of the object
+ $anothercomponent = new NewComponent();
+
+ //The new component should be a FooClassBehavior
+ $this->assertNotNull($anothercomponent->asa('FooClassBehavior'), "anothercomponent does not have the FooClassBehavior");
+
+ // test when overwriting an existing class behavior, it should throw an TInvalidOperationException
+ try {
+ $this->component->attachClassBehavior('FooClassBehavior', new BarClassBehavior);
+ $this->fail('TInvalidOperationException not raised when overwriting an existing behavior');
+ } catch(TInvalidOperationException $e) {
+ }
+
+
+ // test when overwriting an existing class behavior, it should throw an TInvalidOperationException
+ try {
+ $this->component->attachClassBehavior('FooBarBehavior', 'FooBarBehavior', 'TComponent');
+ $this->fail('TInvalidOperationException not raised when trying to place a behavior on the root object TComponent');
+ } catch(TInvalidOperationException $e) {
+ }
+
+
+ // test if the function does not modify any existing objects that are not listening
+ // The FooClassBehavior is already a part of the class behaviors thus the new instance gets the behavior.
+ $nolistencomponent = new NewComponentNoListen();
+
+ // test if the function modifies all existing objects that are listening
+ // Adding a behavior to the first object, the second instance should automatically get the class behavior.
+ // This is because the second object is listening to the global events of class behaviors
+ $this->component->attachClassBehavior('BarClassBehavior', new BarClassBehavior);
+ $this->assertNotNull($anothercomponent->asa('BarClassBehavior'), "anothercomponent is does not have the BarClassBehavior");
+
+ // The no listen object should not have the BarClassBehavior because it was added as a class behavior after the object was instanced
+ $this->assertNull($nolistencomponent->asa('BarClassBehavior'), "nolistencomponent has the BarClassBehavior and should not");
+
+ // But the no listen object should have the FooClassBehavior because the class behavior was installed before the object was instanced
+ $this->assertNotNull($nolistencomponent->asa('FooClassBehavior'), "nolistencomponent is does not have the FooClassBehavior");
+
+ //Clear out what was done during this test
+ $anothercomponent->unlisten();
+ $this->component->detachClassBehavior('FooClassBehavior');
+ $this->component->detachClassBehavior('BarClassBehavior');
+
+ // Test attaching of single object behaviors as class-wide behaviors
+ $this->component->attachClassBehavior('BarBehaviorObject', 'BarBehavior');
+ $this->assertTrue($this->component->asa('BarBehaviorObject') instanceof BarBehavior);
+ $this->assertEquals($this->component->BarBehaviorObject->Owner, $this->component);
+ $this->component->detachClassBehavior('BarBehaviorObject');
+ }
+
+
+
+
+
+ public function testDetachClassBehavior() {
+ // ensure that the component is listening
+ $this->assertEquals(1, $this->component->getEventHandlers('fxDetachClassBehavior')->getCount());
+
+ $prenolistencomponent = new NewComponentNoListen();
+
+ //Attach a class behavior
+ $this->component->attachClassBehavior('FooClassBehavior', new FooClassBehavior);
+
+ //Create new components that listen and don't listen to global events
+ $anothercomponent = new NewComponent();
+ $postnolistencomponent = new NewComponentNoListen();
+
+ //ensures that all the Components are properly initialized
+ $this->assertEquals(2, $this->component->getEventHandlers('fxDetachClassBehavior')->getCount());
+ $this->assertNotNull($this->component->asa('FooClassBehavior'), "Component does not have the FooClassBehavior and should have this behavior");
+ $this->assertNull($prenolistencomponent->asa('FooClassBehavior'), "Component has the FooClassBehavior and should _not_ have this behavior");
+ $this->assertNotNull($anothercomponent->asa('FooClassBehavior'), "Component does not have the FooClassBehavior and should have this behavior");
+ $this->assertNotNull($postnolistencomponent->asa('FooClassBehavior'), "Component does not have the FooClassBehavior and should have this behavior");
+
+
+ $this->component->detachClassBehavior('FooClassBehavior');
+
+ $this->assertNull($this->component->asa('FooClassBehavior'), "Component has the FooClassBehavior and should _not_ have this behavior");
+ $this->assertNull($prenolistencomponent->asa('FooClassBehavior'), "Component has the FooClassBehavior and should _not_ have this behavior");
+ $this->assertNull($anothercomponent->asa('FooClassBehavior'), "Component has the FooClassBehavior and should _not_ have this behavior");
+ $this->assertNotNull($postnolistencomponent->asa('FooClassBehavior'), "Component does not have the FooClassBehavior and should have this behavior");
+
+
+ //tear down function variables
+ $anothercomponent->unlisten();
+ }
+
+ public function testGetClassHierarchy() {
+ $component = new DynamicCatchingComponent;
+ $this->assertEquals(array('TComponent', 'NewComponent', 'NewComponentNoListen', 'DynamicCatchingComponent'), $component->getClassHierarchy());
+ $this->assertEquals(array('TComponent', 'NewComponent', 'NewComponentNoListen', 'DynamicCatchingComponent'), $component->getClassHierarchy(false));
+ $this->assertEquals(array('tcomponent', 'newcomponent', 'newcomponentnolisten', 'dynamiccatchingcomponent'), $component->getClassHierarchy(true));
+ }
+
+
+ public function testAsA() {
+ $anothercomponent = new NewComponent();
+
+ // ensure the component does not have the FooClassBehavior
+ $this->assertNull($this->component->asa('FooClassBehavior'));
+ $this->assertNull($this->component->asa('FooFooClassBehavior'));
+ $this->assertNull($this->component->asa('BarClassBehavior'));
+ $this->assertNull($this->component->asa('NonExistantClassBehavior'));
+
+ $this->assertNull($anothercomponent->asa('FooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('BarClassBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantClassBehavior'));
+
+ // add the class behavior
+ $this->component->attachClassBehavior('FooClassBehavior', new FooClassBehavior);
+
+ //Check that the component has only the class behavior assigned
+ $this->assertNotNull($this->component->asa('FooClassBehavior'));
+ $this->assertNull($this->component->asa('FooFooClassBehavior'));
+ $this->assertNull($this->component->asa('BarClassBehavior'));
+ $this->assertNull($this->component->asa('NonExistantClassBehavior'));
+
+ //Check that the component has only the class behavior assigned
+ $this->assertNotNull($anothercomponent->asa('FooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('BarClassBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantClassBehavior'));
+
+ // remove the class behavior
+ $this->component->detachClassBehavior('FooClassBehavior');
+
+ // Check the function doesn't have the behavior any more
+ $this->assertNull($this->component->asa('FooClassBehavior'));
+ $this->assertNull($this->component->asa('FooFooClassBehavior'));
+ $this->assertNull($this->component->asa('BarClassBehavior'));
+ $this->assertNull($this->component->asa('NonExistantClassBehavior'));
+
+ $this->assertNull($anothercomponent->asa('FooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooClassBehavior'));
+ $this->assertNull($anothercomponent->asa('BarClassBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantClassBehavior'));
+
+
+
+
+ $this->component->attachBehavior('BarBehavior', new BarBehavior);
+
+ //Check that the component has only the object behavior assigned
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+ $this->assertNotNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('NonExistantBehavior'));
+
+ //Check that the component has the behavior assigned
+ $this->assertNull($anothercomponent->asa('FooBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooBehavior'));
+ $this->assertNull($anothercomponent->asa('BarBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantBehavior'));
+
+ $this->component->detachBehavior('BarBehavior');
+
+ //Check that the component has no object behaviors assigned
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('NonExistantBehavior'));
+
+ //Check that the component has no behavior assigned
+ $this->assertNull($anothercomponent->asa('FooBehavior'));
+ $this->assertNull($anothercomponent->asa('FooFooBehavior'));
+ $this->assertNull($anothercomponent->asa('BarBehavior'));
+ $this->assertNull($anothercomponent->asa('NonExistantBehavior'));
+
+ $anothercomponent->unlisten();
+ }
+
+ public function testIsA() {
+ //This doesn't check the IInstanceCheck functionality, separate function
+
+ $this->assertTrue($this->component->isa('TComponent'));
+ $this->assertTrue($this->component->isa('NewComponent'));
+ $this->assertFalse($this->component->isa(new FooBehavior));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+
+ //Ensure there is no BarBehavior
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+
+ $this->component->attachBehavior('FooFooBehavior', new FooFooBehavior);
+
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertTrue($this->component->isa('FooFooBehavior'));
+
+ $this->component->disableBehaviors();
+ // It still has the behavior
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+
+ // But it is not expressed
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+
+ $this->component->enableBehaviors();
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+
+ $this->assertTrue($this->component->isa('FooFooBehavior'));
+
+
+
+ $this->component->attachBehavior('FooBarBehavior', new FooBarBehavior);
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertTrue($this->component->isa('FooBarBehavior'));
+
+ $this->component->disableBehavior('FooBarBehavior');
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+
+ $this->component->enableBehavior('FooBarBehavior');
+ $this->component->disableBehavior('FooFooBehavior');
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertTrue($this->component->isa('FooBarBehavior'));
+
+ $this->component->disableBehavior('FooBarBehavior');
+ $this->component->disableBehavior('FooFooBehavior');
+
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+
+ $this->component->enableBehavior('FooBarBehavior');
+ $this->component->enableBehavior('FooFooBehavior');
+
+ $this->assertTrue($this->component->isa('FooFooBehavior'));
+ $this->assertTrue($this->component->isa('FooBarBehavior'));
+
+
+ $this->component->detachBehavior('FooFooBehavior');
+ $this->component->detachBehavior('FooBarBehavior');
+
+ $this->assertFalse($this->component->isa(new FooBehavior));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa(new FooFooBehavior));
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertFalse($this->component->isa(new FooBarBehavior));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+
+ }
+
+ public function testIsA_with_IInstanceCheck() {
+
+ $this->assertTrue($this->component->isa('NewComponent'));
+ $this->assertFalse($this->component->isa('PreBarBehavior'));
+
+ $this->component->attachBehavior('BarBehavior', $behavior = new BarBehavior);
+
+ $behavior->setInstanceReturn(null);
+
+ $this->assertTrue($this->component->isa('NewComponent'));
+ $this->assertTrue($this->component->isa('PreBarBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+
+ // This forces the iso on the BarBehavior to respond to any class with false
+ $behavior->setInstanceReturn(false);
+ $this->assertFalse($this->component->isa('PreBarBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+
+ //This forces the isa on the BarBehavior to respond to any class with true
+ $behavior->setInstanceReturn(true);
+ $this->assertTrue($this->component->isa('FooBehavior'));
+
+
+ }
+
+ public function testAttachDetachBehavior() {
+
+ try {
+ $this->component->faaEverMore(true, true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+
+ try {
+ $this->component->attachBehavior('FooBehavior', new TComponent);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TInvalidDataTypeException $e) {}
+
+ $this->component->attachBehavior('FooBehavior', new FooBehavior);
+
+ $this->assertNotNull($this->component->asa('FooBehavior'));
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+
+ try {
+ $this->assertTrue($this->component->faaEverMore(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+ try {
+ $this->component->noMethodHere(true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->assertTrue($this->component->disableBehavior('FooBehavior'));
+
+ //BarBehavior is not a behavior at this time
+ $this->assertNull($this->component->disableBehavior('BarBehavior'));
+
+ try {
+ $this->component->faaEverMore(true, true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->assertTrue($this->component->enableBehavior('FooBehavior'));
+
+ //BarBehavior is not a behavior at this time
+ $this->assertNull($this->component->enableBehavior('BarBehavior'));
+
+ try {
+ $this->assertTrue($this->component->faaEverMore(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+ $this->component->detachBehavior('FooBehavior');
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+
+ }
+
+ public function testAttachDetachBehaviors() {
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNull($this->component->asa('PreBarBehavior'));
+
+ $this->component->attachBehaviors(array('FooFooBehavior' => new FooFooBehavior, 'BarBehavior' => new BarBehavior, 'PreBarBehavior' => new PreBarBehavior));
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+ $this->assertNotNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNotNull($this->component->asa('PreBarBehavior'));
+
+ $this->assertTrue($this->component->isa('FooFooBehavior'));
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertTrue($this->component->isa('BarBehavior'));
+ $this->assertTrue($this->component->isa('PreBarBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+
+ $this->component->detachBehaviors(array('FooFooBehavior' => new FooFooBehavior, 'BarBehavior' => new BarBehavior));
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNotNull($this->component->asa('PreBarBehavior'));
+
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+ $this->assertTrue($this->component->isa('PreBarBehavior'));
+
+
+
+ // testing if we can detachBehaviors just by the name of the behavior instead of an array of the behavior
+ $this->component->attachBehaviors(array('FooFooBehavior' => new FooFooBehavior, 'BarBehavior' => new BarBehavior));
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ $this->assertTrue($this->component->isa('BarBehavior'));
+
+ $this->component->detachBehaviors(array('FooFooBehavior', 'BarBehavior'));
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('FooFooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+
+ $this->assertFalse($this->component->isa('FooFooBehavior'));
+ $this->assertFalse($this->component->isa('FooBehavior'));
+ $this->assertFalse($this->component->isa('BarBehavior'));
+ $this->assertFalse($this->component->isa('FooBarBehavior'));
+ }
+
+
+ public function testClearBehaviors() {
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNull($this->component->asa('PreBarBehavior'));
+
+ $this->component->attachBehaviors(array('FooFooBehavior' => new FooFooBehavior, 'BarBehavior' => new BarBehavior, 'PreBarBehavior' => new PreBarBehavior));
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNotNull($this->component->asa('FooFooBehavior'));
+ $this->assertNotNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNotNull($this->component->asa('PreBarBehavior'));
+
+ $this->component->clearBehaviors();
+
+ $this->assertNull($this->component->asa('FooBehavior'));
+ $this->assertNull($this->component->asa('BarBehavior'));
+ $this->assertNull($this->component->asa('FooBarBehavior'));
+ $this->assertNull($this->component->asa('PreBarBehavior'));
+ }
+
+ public function testEnableDisableBehavior() {
+
+ $this->assertNull($this->component->enableBehavior('FooBehavior'));
+ $this->assertNull($this->component->disableBehavior('FooBehavior'));
+
+ try {
+ $this->component->faaEverMore(true, true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->component->attachBehavior('FooBehavior', new FooBehavior);
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ try {
+ $this->assertTrue($this->component->faaEverMore(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+ $this->assertTrue($this->component->disableBehavior('FooBehavior'));
+
+ $this->assertFalse($this->component->isa('FooBehavior'));
+
+ try {
+ $this->component->faaEverMore(true, true);
+ $this->fail('TApplicationException not raised trying to execute a undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->assertTrue($this->component->enableBehavior('FooBehavior'));
+
+ $this->assertTrue($this->component->isa('FooBehavior'));
+ try {
+ $this->assertTrue($this->component->faaEverMore(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+
+
+ $this->assertNull($this->component->enableBehavior('BarClassBehavior'));
+ $this->assertNull($this->component->disableBehavior('BarClassBehavior'));
+
+ try {
+ $this->component->moreFunction(true, true);
+ $this->fail('TApplicationException not raised trying to execute an undefined class method');
+ } catch(TApplicationException $e) {}
+
+ $this->component->attachClassBehavior('BarClassBehavior', new BarClassBehavior);
+
+ $this->assertFalse($this->component->enableBehavior('BarClassBehavior'));
+ $this->assertFalse($this->component->disableBehavior('BarClassBehavior'));
+
+ try {
+ $this->assertTrue($this->component->moreFunction(true, true));
+ } catch(TApplicationException $e) {
+ $this->fail('TApplicationException raised while trying to execute a behavior class method');
+ }
+
+ $this->component->detachClassBehavior('BarClassBehavior');
+ }
+
+
+ public function testBehaviorFunctionCalls() {
+
+ $this->component->attachBehavior('FooBarBehavior', $behavior = new FooBarBehavior);
+ $this->component->attachClassBehavior('FooClassBehavior', $classbehavior = new FooClassBehavior);
+
+ // Test the Class Methods
+ $this->assertEquals(12, $this->component->faaEverMore(3, 4));
+
+ // Check that the called object is shifted in front of the array of a class behavior call
+ $this->assertEquals($this->component, $this->component->getLastClassObject());
+
+
+ //Test the FooBarBehavior
+ $this->assertEquals(27, $this->component->moreFunction(3, 3));
+
+ $this->assertTrue($this->component->disableBehavior('FooBarBehavior'));
+ try {
+ $this->assertNull($this->component->moreFunction(3, 4));
+ $this->fail('TApplicationException not raised trying to execute a disabled behavior');
+ } catch(TApplicationException $e) {}
+ $this->assertTrue($this->component->enableBehavior('FooBarBehavior'));
+
+ // Test the global event space, this should work and return false because no function implements these methods
+ $this->assertNull($this->component->fxSomeUndefinedGlobalEvent());
+ $this->assertNull($this->component->dySomeUndefinedIntraObjectEvent());
+
+ $this->component->detachClassBehavior('FooClassBehavior');
+
+
+
+ // test object instance behaviors implemented through class-wide behaviors
+ $this->component->attachClassBehavior('FooFooBehaviorAsClass', 'FooFooBehavior');
+
+ $component = new NewComponent;
+
+ $this->assertEquals(5, $this->component->faafaaEverMore(3, 4));
+ $this->assertEquals(10, $component->faafaaEverMore(6, 8));
+
+ $this->component->detachClassBehavior('FooFooBehaviorAsClass');
+ $component->unlisten();
+ $component = null;
+
+ try {
+ $this->component->faafaaEverMore(3, 4);
+ $this->fail('TApplicationException not raised trying to execute a disabled behavior');
+ } catch(TApplicationException $e) {}
+
+
+
+ // make a call to an unpatched fx and dy call so that it's passed through to the __dycall function
+ $dynamicComponent = new DynamicCallComponent;
+
+ $this->assertNull($dynamicComponent->fxUndefinedEvent());
+ $this->assertNull($dynamicComponent->dyUndefinedEvent());
+
+ //This tests the dynamic __dycall function
+ $this->assertEquals(1024, $dynamicComponent->dyPowerFunction(2, 10));
+ $this->assertEquals(5, $dynamicComponent->dyDivisionFunction(10, 2));
+
+ $this->assertEquals(2048, $dynamicComponent->fxPowerFunction(2, 10));
+ $this->assertEquals(10, $dynamicComponent->fxDivisionFunction(10, 2));
+
+ $dynamicComponent->unlisten();
+
+ }
+
public function testHasProperty() {
$this->assertTrue($this->component->hasProperty('Text'), "Component hasn't property Text");
$this->assertTrue($this->component->hasProperty('text'), "Component hasn't property text");
- $this->assertFalse($this->component->hasProperty('Caption'), "Component as property Caption");
+ $this->assertFalse($this->component->hasProperty('Caption'), "Component has property Caption");
+
+ $this->assertTrue($this->component->hasProperty('ColorAttribute'), "Component hasn't property JsColorAttribute");
+ $this->assertTrue($this->component->hasProperty('colorattribute'), "Component hasn't property JsColorAttribute");
+ $this->assertFalse($this->component->canGetProperty('PastelAttribute'), "Component has property JsPastelAttribute");
+
+ $this->assertTrue($this->component->hasProperty('JSColorAttribute'), "Component hasn't property JsColorAttribute");
+ $this->assertTrue($this->component->hasProperty('jscolorattribute'), "Component hasn't property JsColorAttribute");
+ $this->assertFalse($this->component->hasProperty('jsPastelAttribute'), "Component has property JsPastelAttribute");
+
+ $this->assertFalse($this->component->hasProperty('Excitement'), "Component has property Excitement");
+ $this->component->attachBehavior('ExcitementPropBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->hasProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehaviors();
+ $this->assertFalse($this->component->hasProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehaviors();
+ $this->assertTrue($this->component->hasProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehavior('ExcitementPropBehavior');
+ $this->assertFalse($this->component->hasProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehavior('ExcitementPropBehavior');
+ $this->assertTrue($this->component->hasProperty('Excitement'), "Component hasn't property Excitement");
+
+ $this->component->detachBehavior('ExcitementPropBehavior');
+
+ $this->assertFalse($this->component->hasProperty('Excitement'), "Component has property Excitement");
+
}
public function testCanGetProperty() {
$this->assertTrue($this->component->canGetProperty('Text'));
$this->assertTrue($this->component->canGetProperty('text'));
$this->assertFalse($this->component->canGetProperty('Caption'));
+
+ $this->assertTrue($this->component->canGetProperty('ColorAttribute'));
+ $this->assertTrue($this->component->canGetProperty('colorattribute'));
+ $this->assertFalse($this->component->canGetProperty('PastelAttribute'));
+
+ $this->assertTrue($this->component->canGetProperty('JSColorAttribute'));
+ $this->assertTrue($this->component->canGetProperty('jscolorattribute'));
+ $this->assertFalse($this->component->canGetProperty('jsPastelAttribute'));
+
+
+ $this->assertFalse($this->component->canGetProperty('Excitement'), "Component has property Excitement");
+ $this->component->attachBehavior('ExcitementPropBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->canGetProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehaviors();
+ $this->assertFalse($this->component->canGetProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehaviors();
+ $this->assertTrue($this->component->canGetProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehavior('ExcitementPropBehavior');
+ $this->assertFalse($this->component->canGetProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehavior('ExcitementPropBehavior');
+ $this->assertTrue($this->component->canGetProperty('Excitement'), "Component hasn't property Excitement");
+
+ $this->component->detachBehavior('ExcitementPropBehavior');
+
+ $this->assertFalse($this->component->canGetProperty('Excitement'), "Component has property Excitement");
}
public function testCanSetProperty() {
$this->assertTrue($this->component->canSetProperty('Text'));
$this->assertTrue($this->component->canSetProperty('text'));
$this->assertFalse($this->component->canSetProperty('Caption'));
+
+ $this->assertTrue($this->component->canSetProperty('ColorAttribute'));
+ $this->assertTrue($this->component->canSetProperty('colorattribute'));
+ $this->assertFalse($this->component->canSetProperty('PastelAttribute'));
+
+ $this->assertTrue($this->component->canSetProperty('JSColorAttribute'));
+ $this->assertTrue($this->component->canSetProperty('jscolorattribute'));
+ $this->assertFalse($this->component->canSetProperty('jsPastelAttribute'));
+
+ $this->assertFalse($this->component->canSetProperty('Excitement'), "Component has property Excitement");
+ $this->component->attachBehavior('ExcitementPropBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->canSetProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehaviors();
+ $this->assertFalse($this->component->canSetProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehaviors();
+ $this->assertTrue($this->component->canSetProperty('Excitement'), "Component hasn't property Excitement");
+ $this->component->disableBehavior('ExcitementPropBehavior');
+ $this->assertFalse($this->component->canSetProperty('Excitement'), "Component has property Excitement");
+ $this->component->enableBehavior('ExcitementPropBehavior');
+ $this->assertTrue($this->component->canSetProperty('Excitement'), "Component hasn't property Excitement");
+
+ $this->component->detachBehavior('ExcitementPropBehavior');
}
public function testGetProperty() {
@@ -75,6 +1181,67 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->fail('exception not raised when getting undefined property');
} catch(TInvalidOperationException $e) {
}
+
+ $this->assertTrue($this->component->OnMyEvent instanceof TPriorityList);
+ try {
+ $value2=$this->component->onUndefinedEvent;
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ //Without the function parenthesis, the function is _not_ called but the __get
+ // method is called and the global events (list) are accessed
+ $this->assertTrue($this->component->fxAttachClassBehavior instanceof TPriorityList);
+ $this->assertTrue($this->component->fxDetachClassBehavior instanceof TPriorityList);
+
+ // even undefined global events have a list as every object is able to access every event
+ $this->assertTrue($this->component->fxUndefinedEvent instanceof TPriorityList);
+
+
+ // Test the behaviors within the __get function
+ $this->component->enableBehaviors();
+
+ try {
+ $value2=$this->component->Excitement;
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->component->attachBehavior('BehaviorTestBehavior', $behavior = new BehaviorTestBehavior);
+ $this->assertEquals('faa', $this->component->Excitement);
+
+ $this->component->disableBehaviors();
+
+ try {
+ $this->assertEquals('faa', $this->component->Excitement);
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->component->enableBehaviors();
+ $this->assertEquals('faa', $this->component->getExcitement());
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+
+ $this->assertEquals($behavior, $this->component->BehaviorTestBehavior);
+ try {
+ $behavior = $this->component->BehaviorTestBehavior2;
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ try {
+ $this->assertEquals('faa', $this->component->Excitement);
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertEquals('faa', $this->component->getExcitement());
+
+
+ // behaviors allow on and fx events to be passed through.
+ $this->assertTrue($this->component->onBehaviorEvent instanceof TPriorityList);
+
}
public function testSetProperty() {
@@ -87,6 +1254,203 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->fail('exception not raised when setting undefined property');
} catch(TInvalidOperationException $e) {
}
+
+ // Test get only properties is a set function
+ try {
+ $this->component->ReadOnlyProperty = 'setting read only';
+ $this->fail('a property without a set function was set to a new value without error');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ try {
+ $this->component->ReadOnlyJsProperty = 'jssetting read only';
+ $this->fail('a js property without a set function was set to a new value without error');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ try {
+ $this->component->JsReadOnlyJsProperty = 'jssetting read only';
+ $this->fail('a js property without a set function was set to a new value without error');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->assertEquals(0, $this->component->getEventHandlers('onMyEvent')->getCount());
+ $this->component->onMyEvent = array($this->component,'myEventHandler');
+ $this->assertEquals(1, $this->component->getEventHandlers('onMyEvent')->getCount());
+ $this->component->onMyEvent[] = array($this->component,'Object.myEventHandler');
+ $this->assertEquals(2, $this->component->getEventHandlers('onMyEvent')->getCount());
+
+ $this->component->getEventHandlers('onMyEvent')->clear();
+
+ // Test the behaviors within the __get function
+ $this->component->enableBehaviors();
+
+ try {
+ $this->component->Excitement = 'laa';
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->component->attachBehavior('BehaviorTestBehavior', $behavior1 = new BehaviorTestBehavior);
+ $this->component->Excitement = 'laa';
+ $this->assertEquals('laa', $this->component->Excitement);
+ $this->assertEquals('sol', $this->component->Excitement = 'sol');
+
+
+ $this->component->disableBehaviors();
+
+ try {
+ $this->component->Excitement = false;
+ $this->assertEquals(false, $this->component->Excitement);
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+
+ $this->component->enableBehaviors();
+ $this->component->Excitement = 'faa';
+ $this->assertEquals('faa', $this->component->getExcitement());
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+
+ try {
+ $this->component->Excitement = false;
+ $this->assertEquals(false, $this->component->Excitement);
+ $this->fail('exception not raised when getting undefined property');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->component->Excitement = 'sol';
+ $this->assertEquals('sol', $this->component->Excitement);
+
+
+ $this->component->attachBehavior('BehaviorTestBehavior2', $behavior2 = new BehaviorTestBehavior);
+
+ $this->assertEquals('sol', $this->component->Excitement);
+ $this->assertEquals('faa', $behavior2->Excitement);
+
+ // this sets Excitement for both because they are not uniquely named
+ $this->component->Excitement = 'befaad';
+
+ $this->assertEquals('befaad', $this->component->Excitement);
+ $this->assertEquals('befaad', $behavior1->Excitement);
+ $this->assertEquals('befaad', $behavior2->Excitement);
+
+
+ $this->component->detachBehavior('BehaviorTestBehavior2');
+
+ // behaviors allow on and fx events to be passed through.
+ $this->assertTrue($this->component->BehaviorTestBehavior->onBehaviorEvent instanceof TPriorityList);
+
+ $this->assertEquals(0, $this->component->BehaviorTestBehavior->getEventHandlers('onBehaviorEvent')->getCount());
+ $this->component->onBehaviorEvent = array($this->component,'myEventHandler');
+ $this->assertEquals(1, $this->component->BehaviorTestBehavior->getEventHandlers('onBehaviorEvent')->getCount());
+ $this->component->onBehaviorEvent[] = array($this->component,'Object.myEventHandler');
+ $this->assertEquals(2, $this->component->BehaviorTestBehavior->getEventHandlers('onBehaviorEvent')->getCount());
+
+ $this->component->BehaviorTestBehavior->getEventHandlers('onBehaviorEvent')->clear();
+ }
+
+
+ public function testIsSetFunction() {
+ $this->assertTrue(isset($this->component->fxAttachClassBehavior));
+ $this->component->unlisten();
+
+ $this->assertFalse(isset($this->component->onMyEvent));
+ $this->assertFalse(isset($this->component->undefinedEvent));
+ $this->assertFalse(isset($this->component->fxAttachClassBehavior));
+
+ $this->assertFalse(isset($this->component->BehaviorTestBehavior));
+ $this->assertFalse(isset($this->component->onBehaviorEvent));
+
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+
+ $this->assertTrue(isset($this->component->BehaviorTestBehavior));
+ $this->assertFalse(isset($this->component->onBehaviorEvent));
+
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->assertTrue(isset($this->component->onBehaviorEvent));
+
+ $this->component->attachEventHandler('onMyEvent','foo');
+ $this->assertTrue(isset($this->component->onMyEvent));
+
+ $this->assertTrue(isset($this->component->Excitement));
+ $this->component->Excitement = null;
+ $this->assertFalse(isset($this->component->Excitement));
+ $this->assertFalse(isset($this->component->UndefinedBehaviorProperty));
+
+
+ }
+
+
+ public function testUnsetFunction() {
+
+ $this->assertEquals('default', $this->component->getText());
+ unset($this->component->Text);
+ $this->assertNull($this->component->getText());
+
+ unset($this->component->UndefinedProperty);
+
+ // object events
+ $this->assertEquals(0, $this->component->onMyEvent->Count);
+ $this->component->attachEventHandler('onMyEvent','foo');
+ $this->assertEquals(1, $this->component->onMyEvent->Count);
+ unset($this->component->onMyEvent);
+ $this->assertEquals(0, $this->component->onMyEvent->Count);
+
+ //global events
+ $this->assertEquals(1, $this->component->fxAttachClassBehavior->Count);
+ $component = new NewComponent();
+ $this->assertEquals(2, $this->component->fxAttachClassBehavior->Count);
+ unset($this->component->fxAttachClassBehavior);
+ // retain the other object event
+ $this->assertEquals(1, $this->component->fxAttachClassBehavior->Count);
+ $component->unlisten();
+
+ try {
+ unset($this->component->Object);
+ $this->fail('TInvalidOperationException not raised when unsetting get only property');
+ } catch(TInvalidOperationException $e) {}
+
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->asa('BehaviorTestBehavior') instanceof BehaviorTestBehavior);
+ $this->assertFalse($this->component->asa('BehaviorTestBehavior2') instanceof BehaviorTestBehavior);
+
+ $this->assertEquals('faa', $this->component->Excitement);
+ unset($this->component->Excitement);
+ $this->assertNull($this->component->Excitement);
+ $this->component->Excitement = 'sol';
+ $this->assertEquals('sol', $this->component->Excitement);
+
+ // Test the disabling of unset within behaviors
+ $this->component->disableBehaviors();
+ unset($this->component->Excitement);
+ $this->component->enableBehaviors();
+ // This should still be 'sol' because the unset happened inside behaviors being disabled
+ $this->assertEquals('sol', $this->component->Excitement);
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ unset($this->component->Excitement);
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertEquals('sol', $this->component->Excitement);
+
+ unset($this->component->Excitement);
+ $this->assertNull($this->component->Excitement);
+
+ try {
+ unset($this->component->ReadOnly);
+ $this->fail('TInvalidOperationException not raised when unsetting get only property');
+ } catch(TInvalidOperationException $e) {}
+
+ $this->component->onBehaviorEvent = 'foo';
+ $this->assertEquals(1, count($this->component->onBehaviorEvent));
+ $this->assertEquals(1, count($this->component->BehaviorTestBehavior->onBehaviorEvent));
+ unset($this->component->onBehaviorEvent);
+ $this->assertEquals(0, count($this->component->onBehaviorEvent));
+ $this->assertEquals(0, count($this->component->BehaviorTestBehavior->onBehaviorEvent));
+
+ // Remove behavior via unset
+ unset($this->component->BehaviorTestBehavior);
+ $this->assertFalse($this->component->asa('BehaviorTestBehavior') instanceof BehaviorTestBehavior);
+
}
public function testGetSubProperty() {
@@ -102,46 +1466,225 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->assertTrue($this->component->hasEvent('OnMyEvent'));
$this->assertTrue($this->component->hasEvent('onmyevent'));
$this->assertFalse($this->component->hasEvent('onYourEvent'));
+
+ // fx won't throw an error if any of these fx function are called on an object.
+ // It is a special prefix event designation that every object responds to all events.
+ $this->assertTrue($this->component->hasEvent('fxAttachClassBehavior'));
+ $this->assertTrue($this->component->hasEvent('fxattachclassbehavior'));
+
+ $this->assertTrue($this->component->hasEvent('fxNonExistantGlobalEvent'));
+ $this->assertTrue($this->component->hasEvent('fxnonexistantglobalevent'));
+
+ $this->assertTrue($this->component->hasEvent('dyNonExistantLocalEvent'));
+ $this->assertTrue($this->component->hasEvent('fxnonexistantlocalevent'));
+
+
+ //Test behavior events
+ $this->assertFalse($this->component->hasEvent('onBehaviorEvent'));
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+ $this->assertTrue($this->component->hasEvent('onBehaviorEvent'));
+ $this->assertTrue($this->component->BehaviorTestBehavior->hasEvent('onBehaviorEvent'));
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ $this->assertFalse($this->component->hasEvent('onBehaviorEvent'));
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertTrue($this->component->hasEvent('onBehaviorEvent'));
}
public function testHasEventHandler() {
$this->assertFalse($this->component->hasEventHandler('OnMyEvent'));
$this->component->attachEventHandler('OnMyEvent','foo');
$this->assertTrue($this->component->hasEventHandler('OnMyEvent'));
+
+ $this->assertFalse($this->component->hasEventHandler('fxNonExistantGlobalEvent'));
+ $this->component->attachEventHandler('fxNonExistantGlobalEvent','foo');
+ $this->assertTrue($this->component->hasEventHandler('fxNonExistantGlobalEvent'));
+
+ //Test behavior events
+ $this->assertFalse($this->component->hasEventHandler('onBehaviorEvent'));
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+ $this->assertFalse($this->component->hasEventHandler('onBehaviorEvent'));
+ $this->assertFalse($this->component->BehaviorTestBehavior->hasEventHandler('onBehaviorEvent'));
+
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->assertTrue($this->component->hasEventHandler('onBehaviorEvent'));
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ $this->assertFalse($this->component->hasEvent('onBehaviorEvent'));
+ $this->assertFalse($this->component->hasEventHandler('onBehaviorEvent'));
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertTrue($this->component->hasEvent('onBehaviorEvent'));
+ $this->assertTrue($this->component->hasEventHandler('onBehaviorEvent'));
}
public function testGetEventHandlers() {
$list=$this->component->getEventHandlers('OnMyEvent');
- $this->assertTrue(($list instanceof TList) && ($list->getCount()===0));
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===0));
$this->component->attachEventHandler('OnMyEvent','foo');
- $this->assertTrue(($list instanceof TList) && ($list->getCount()===1));
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===1));
try {
$list=$this->component->getEventHandlers('YourEvent');
$this->fail('exception not raised when getting event handlers for undefined event');
} catch(TInvalidOperationException $e) {
}
+
+ $list=$this->component->getEventHandlers('fxRandomEvent');
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===0));
+ $this->component->attachEventHandler('fxRandomEvent','foo');
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===1));
+ try {
+ $list=$this->component->getEventHandlers('fxSomeUndefinedGlobalEvent');
+ } catch(TInvalidOperationException $e) {
+ $this->fail('exception raised when getting event handlers for universal global event');
+ }
+
+
+
+ //Test behavior events
+ try {
+ $list=$this->component->getEventHandlers('onBehaviorEvent');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->assertFalse($this->component->hasEventHandler('onBehaviorEvent'));
+
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+ $list=$this->component->getEventHandlers('onBehaviorEvent');
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===0));
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->assertTrue(($list instanceof TPriorityList) && ($list->getCount()===1));
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ try {
+ $list=$this->component->getEventHandlers('onBehaviorEvent');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->enableBehavior('BehaviorTestBehavior');
+ $this->assertTrue(($this->component->getEventHandlers('onBehaviorEvent') instanceof TPriorityList) && ($list->getCount()===1));
+
}
public function testAttachEventHandler() {
+
$this->component->attachEventHandler('OnMyEvent','foo');
- $this->assertTrue($this->component->getEventHandlers('OnMyEvent')->getCount()===1);
+ $this->assertEquals(1, $this->component->getEventHandlers('OnMyEvent')->getCount());
try {
$this->component->attachEventHandler('YourEvent','foo');
$this->fail('exception not raised when attaching event handlers for undefined event');
} catch(TInvalidOperationException $e) {
}
- /*$this->component->MyEvent[]='foo2';
- $this->assertTrue($this->component->getEventHandlers('MyEvent')->getCount()===2);
- $this->component->getEventHandlers('MyEvent')->add('foo3');
- $this->assertTrue($this->component->getEventHandlers('MyEvent')->getCount()===3);
- $this->component->MyEvent[0]='foo4';
- $this->assertTrue($this->component->getEventHandlers('MyEvent')->getCount()===3);
- $this->component->getEventHandlers('MyEvent')->insert(0,'foo5');
- $this->assertTrue($this->component->MyEvent->Count===4 && $this->component->MyEvent[0]==='foo5');
- $this->component->MyEvent='foo6';
- $this->assertTrue($this->component->MyEvent->Count===5 && $this->component->MyEvent[4]==='foo6');*/
+
+ //Testing the priorities of attaching events
+ $this->component->attachEventHandler('OnMyEvent','foopre', 5);
+ $this->component->attachEventHandler('OnMyEvent','foopost', 15);
+ $this->component->attachEventHandler('OnMyEvent','foobar', 10);
+ $this->assertEquals(4, $this->component->getEventHandlers('OnMyEvent')->getCount());
+ $list = $this->component->getEventHandlers('OnMyEvent');
+ $this->assertEquals('foopre', $list[0]);
+ $this->assertEquals('foo', $list[1]);
+ $this->assertEquals('foobar', $list[2]);
+ $this->assertEquals('foopost', $list[3]);
+
+
+ //Test attaching behavior events
+ try {
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+
+ //Testing the priorities of attaching behavior events
+ $this->component->attachEventHandler('onBehaviorEvent','foopre', 5);
+ $this->component->attachEventHandler('onBehaviorEvent','foopost', 15);
+ $this->component->attachEventHandler('onBehaviorEvent','foobar', 10);
+ $this->component->attachEventHandler('onBehaviorEvent','foobarfoobar', 10);
+ $this->assertEquals(5, $this->component->getEventHandlers('onBehaviorEvent')->getCount());
+ $list = $this->component->getEventHandlers('onBehaviorEvent');
+ $this->assertEquals('foopre', $list[0]);
+ $this->assertEquals('foo', $list[1]);
+ $this->assertEquals('foobar', $list[2]);
+ $this->assertEquals('foobarfoobar', $list[3]);
+ $this->assertEquals('foopost', $list[4]);
+
+ $this->component->disableBehavior('BehaviorTestBehavior');
+ try {
+ $this->component->attachEventHandler('onBehaviorEvent','bar');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->enableBehavior('BehaviorTestBehavior');
+
}
+ public function testDetachEventHandler() {
+
+ $this->component->attachEventHandler('OnMyEvent','foo');
+ $this->assertEquals(1, $this->component->getEventHandlers('OnMyEvent')->getCount());
+
+ $this->component->attachEventHandler('OnMyEvent','foopre', 5);
+ $this->component->attachEventHandler('OnMyEvent','foopost', 15);
+ $this->component->attachEventHandler('OnMyEvent','foobar', 10);
+ $this->component->attachEventHandler('OnMyEvent','foobarfoobar', 10);
+
+
+
+ $this->component->detachEventHandler('OnMyEvent','foo');
+ $list = $this->component->getEventHandlers('OnMyEvent');
+ $this->assertEquals(4, $list->getCount());
+
+ $this->assertEquals('foopre', $list[0]);
+ $this->assertEquals('foobar', $list[1]);
+ $this->assertEquals('foobarfoobar', $list[2]);
+ $this->assertEquals('foopost', $list[3]);
+
+ $this->component->detachEventHandler('OnMyEvent','foopre', null);
+ $this->assertEquals(4, $list->getCount());
+
+ $this->component->detachEventHandler('OnMyEvent','foopre', 5);
+ $this->assertEquals(3, $list->getCount());
+
+
+ // Now do detaching of behavior on events
+ try {
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {
+ }
+ $this->component->attachBehavior('BehaviorTestBehavior', new BehaviorTestBehavior);
+
+ $this->component->attachEventHandler('onBehaviorEvent','foo');
+ $this->assertEquals(1, $this->component->getEventHandlers('onBehaviorEvent')->getCount());
+
+ $this->component->attachEventHandler('onBehaviorEvent','foopre', 5);
+ $this->component->attachEventHandler('onBehaviorEvent','foopost', 15);
+ $this->component->attachEventHandler('onBehaviorEvent','foobar', 10);
+ $this->component->attachEventHandler('onBehaviorEvent','foobarfoobar', 10);
+
+
+
+ $this->component->detachEventHandler('onBehaviorEvent','foo');
+ $list = $this->component->getEventHandlers('onBehaviorEvent');
+ $this->assertEquals(4, $list->getCount());
+
+ $this->assertEquals('foopre', $list[0]);
+ $this->assertEquals('foobar', $list[1]);
+ $this->assertEquals('foobarfoobar', $list[2]);
+ $this->assertEquals('foopost', $list[3]);
+
+ $this->component->detachEventHandler('onBehaviorEvent','foopre', null);
+ $this->assertEquals(4, $list->getCount());
+
+ $this->component->detachEventHandler('onBehaviorEvent','foopre', 5);
+ $this->assertEquals(3, $list->getCount());
+ }
+
+
+
+
public function testRaiseEvent() {
$this->component->attachEventHandler('OnMyEvent',array($this->component,'myEventHandler'));
$this->assertFalse($this->component->isEventHandled());
@@ -151,7 +1694,210 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
$this->assertFalse($this->component->Object->isEventHandled());
$this->component->raiseEvent('OnMyEvent',$this,null);
$this->assertTrue($this->component->Object->isEventHandled());
+
+ $this->component->resetEventHandled();
+ $this->component->Object->resetEventHandled();
+
+
+ // Test a behavior on event
+ $this->component->attachBehavior('test', new BehaviorTestBehavior);
+
+ $this->component->attachEventHandler('onBehaviorEvent',array($this->component,'myEventHandler'));
+ $this->assertFalse($this->component->isEventHandled());
+ $this->component->raiseEvent('onBehaviorEvent',$this,null);
+ $this->assertTrue($this->component->isEventHandled());
+ $this->component->attachEventHandler('onBehaviorEvent',array($this->component,'Object.myEventHandler'));
+ $this->assertFalse($this->component->Object->isEventHandled());
+ $this->component->raiseEvent('onBehaviorEvent',$this,null);
+ $this->assertTrue($this->component->Object->isEventHandled());
+
+ //test behavior enabled/disabled events
+ $this->component->disableBehavior('test');
+
+ $this->component->resetEventHandled();
+ $this->component->Object->resetEventHandled();
+
+ try {
+ $this->component->attachEventHandler('onBehaviorEvent',array($this->component,'myEventHandler'));
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {}
+ $this->assertFalse($this->component->isEventHandled());
+ try {
+ $this->component->raiseEvent('onBehaviorEvent',$this,null);
+ $this->fail('exception not raised when getting event handlers for undefined event');
+ } catch(TInvalidOperationException $e) {}
+ $this->assertFalse($this->component->isEventHandled());
+
+ $this->component->enableBehavior('test');
+
+
+
+ //Test the return types of this function
+
+ $this->assertFalse($this->component->isEventHandled());
+ $this->assertFalse($this->component->Object->isEventHandled());
+ $this->assertEquals(array(), $this->component->onBehaviorEvent($this,$this->component));
+ $this->assertTrue($this->component->isEventHandled());
+ $this->assertTrue($this->component->Object->isEventHandled());
+
+ // This accumulates all the responses from each of the events
+ $arr=$this->component->onBehaviorEvent($this, $this->component, TEventResults::EVENT_RESULT_ALL);
+ $this->assertEquals($this, $arr[0]['sender']);
+ $this->assertEquals($this->component, $arr[0]['param']);
+ $this->assertTrue(null === $arr[0]['response']);
+
+ $this->assertEquals($this, $arr[1]['sender']);
+ $this->assertEquals($this->component, $arr[1]['param']);
+ $this->assertTrue(null === $arr[1]['response']);
+
+ $this->assertEquals(2, count($arr));
+
+ // This tests without the default filtering-out of null
+ $arr=$this->component->onBehaviorEvent($this, $this->component, false);
+ $this->assertEquals(array(null, null), $arr);
+
+
+ unset($this->component->onBehaviorEvent);
+ $this->assertEquals(0, $this->component->onBehaviorEvent->Count);
+
+ $this->component->onBehaviorEvent = array($this, 'returnValue4');
+ $this->component->onBehaviorEvent = array($this, 'returnValue1');
+
+ // Test the per event post processing function
+ $arr=$this->component->onBehaviorEvent($this, $this->component, array($this, 'postEventFunction'));
+ $this->assertEquals(array(exp(4), exp(1)), $arr);
+ $arr=$this->component->onBehaviorEvent($this, $this->component, array($this, 'postEventFunction2'));
+ $this->assertEquals(array(sin(4), sin(1)), $arr);
+
+
+ //Testing Feed-forward functionality
+ unset($this->component->onBehaviorEvent);
+
+ $this->component->onBehaviorEvent = array($this, 'ffValue4');
+ $this->component->onBehaviorEvent = array($this, 'ffValue2');
+ $arr=$this->component->onBehaviorEvent($this, 5, TEventResults::EVENT_RESULT_FEED_FORWARD);
+ $this->assertEquals(array(20, 40), $arr);
+
+
+ unset($this->component->onBehaviorEvent);
+
+ //Order of these events affects the response order in feed forward
+ $this->component->onBehaviorEvent = array($this, 'ffValue2');
+ $this->component->onBehaviorEvent = array($this, 'ffValue4');
+ $arr=$this->component->onBehaviorEvent($this, 5, TEventResults::EVENT_RESULT_FEED_FORWARD);
+ $this->assertEquals(array(10, 40), $arr);
+ }
+
+ public function returnValue1(){return 1;}
+ public function returnValue4(){return 4;}
+ public function postEventFunction($sender, $param, $caller, $response){return exp($response);}
+ public function postEventFunction2($sender, $param, $caller, $response){return sin($response);}
+ public function ffValue2($sender, $param){return $param*2;}
+ public function ffValue4($sender, $param){return $param*4;}
+
+
+ public function testGlobalEventListenerInRaiseEvent() {
+ //TODO Test the Global Event Listener
}
+
+
+ public function testIDynamicMethodsOnBehavior() {
+
+ //Add Behavior with dynamic call
+ $this->component->attachBehavior('TDynamicBehavior', new TDynamicBehavior);
+
+ //Check that the behavior is working as it should
+ $this->assertTrue($this->component->isa('TDynamicBehavior'));
+ $this->assertNull($this->component->getLastBehaviorDynamicMethodCalled());
+
+ // call basic behavior implemented method from object (containing behavior)
+ $this->assertEquals(42, $this->component->TestBehaviorMethod(6, 7));
+
+ //Test out undefined behavior/host object method
+ try {
+ $this->component->objectAndBehaviorUndefinedMethod();
+ $this->fail('exception not raised when evaluating an undefined method by the object and behavior');
+ } catch(TApplicationException $e) {
+ }
+
+ // calling undefined dynamic method, caught by the __dycall method in the behavior and implemented
+ // this behavior catches undefined dynamic event and divides param1 by param 2
+ $this->assertEquals(22, $this->component->dyTestDynamicBehaviorMethod(242, 11));
+ $this->assertEquals('dyTestDynamicBehaviorMethod', $this->component->getLastBehaviorDynamicMethodCalled());
+
+ // calling undefined dynamic method, caught by the __dycall in the behavior and ignored
+ $this->assertNull($this->component->dyUndefinedIntraEvent(242, 11));
+ $this->assertEquals('dyUndefinedIntraEvent', $this->component->getLastBehaviorDynamicMethodCalled());
+
+ //call behavior defined dynamic event
+ // param1 * 2 * param2
+ $this->assertEquals(2420, $this->component->dyTestIntraEvent(121, 10));
+
+ $this->component->detachBehavior('TDynamicBehavior');
+ $this->assertFalse($this->component->isa('TDynamicBehavior'));
+
+
+
+ //Add Class Behavior with dynamic call
+ $this->component->attachBehavior('TDynamicClassBehavior', new TDynamicClassBehavior);
+
+ //Check that the behavior is working as it should
+ $this->assertTrue($this->component->isa('TDynamicClassBehavior'));
+ $this->assertNull($this->component->getLastBehaviorDynamicMethodCalled());
+
+ // call basic behavior implemented method from object (containing behavior)
+ $this->assertEquals(42, $this->component->TestBehaviorMethod(6, 7));
+
+ //Test out undefined behavior/host object method
+ try {
+ $this->component->objectAndBehaviorUndefinedMethod();
+ $this->fail('exception not raised when evaluating an undefined method by the object and behavior');
+ } catch(TApplicationException $e) {
+ }
+
+ // calling undefined dynamic method, caught by the __dycall method in the behavior and implemented
+ // this behavior catches undefined dynamic event and divides param1 by param 2
+ $this->assertEquals(22, $this->component->dyTestDynamicClassBehaviorMethod(242, 11));
+ $this->assertEquals('dyTestDynamicClassBehaviorMethod', $this->component->getLastBehaviorDynamicMethodCalled());
+
+ // calling undefined dynamic method, caught by the __dycall in the behavior and ignored
+ $this->assertNull($this->component->dyUndefinedIntraEvent(242, 11));
+ $this->assertEquals('dyUndefinedIntraEvent', $this->component->getLastBehaviorDynamicMethodCalled());
+
+ //call behavior defined dynamic event
+ // param1 * 2 * param2
+ $this->assertEquals(2420, $this->component->dyTestIntraEvent(121, 10));
+
+ $this->component->detachBehavior('TDynamicClassBehavior');
+ $this->assertFalse($this->component->isa('TDynamicClassBehavior'));
+
+
+ }
+
+ // This also tests the priority of the common global raiseEvent events
+ public function testIDynamicMethodsOnBehaviorGlobalEvents() {
+ $component = new GlobalRaiseComponent();
+
+ // common function has a default priority of 10
+ $component->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, array($component, 'commonRaiseEventListener'));
+ $component->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, array($component, 'postglobalRaiseEventListener'), 1);
+ $component->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, array($component, 'preglobalRaiseEventListener'), -1);
+
+ $this->assertEquals(5, $this->component->fxGlobalListener->getCount());
+ $this->assertEquals(1, $this->component->fxPrimaryGlobalEvent->getCount());
+ $this->assertEquals(1, $this->component->fxPrimaryGlobalEvent->getCount(), 'fxPrimaryGlobalEvent is not installed on test object');
+
+ // call the global event on a different object than the test object
+ $res = $this->component->raiseEvent('fxPrimaryGlobalEvent', $this, null, TEventResults::EVENT_RESULT_ALL);
+
+ $this->assertEquals(6, count($res));
+ $this->assertEquals(array('pregl', 'primary', 'postgl', 'fxGL', 'fxcall', 'com'), $component->getCallOrders());
+
+ $component->unlisten();
+ }
+
+
+
public function testEvaluateExpression() {
$expression="1+2";
@@ -163,6 +1909,9 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
}
}
+
+
+
public function testEvaluateStatements() {
$statements='$a="test string"; echo $a;';
$this->assertEquals('test string',$this->component->evaluateStatements($statements));
@@ -173,6 +1922,169 @@ class TComponentTest extends PHPUnit_Framework_TestCase {
} catch(Exception $e) {
}
}
+
+
+ public function testDynamicFunctionCall() {
+
+ $this->assertEquals(' aa bb cc __ .. ++ || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+
+ $this->component->attachBehavior('dy1', new dy1TextReplace);
+ $this->assertFalse($this->component->dy1->isCalled());
+ $this->assertEquals(' aa bb cc __ __ ++ || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy1->isCalled());
+
+ $this->component->attachBehavior('dy2', new dy2TextReplace);
+ $this->assertFalse($this->component->dy2->isCalled());
+ $this->assertEquals(' aa bb cc __ __ || || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy2->isCalled());
+
+ $this->component->attachBehavior('dy3', new dy3TextReplace);
+ $this->assertFalse($this->component->dy3->isCalled());
+ $this->assertEquals(' aa bb cc __ __ || || ?? ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy3->isCalled());
+
+ $this->assertEquals(' aa bb cc __ .. ++ || !! ?? ', $this->component->dyUndefinedEvent(' aa bb cc __ .. ++ || !! ?? '));
+
+ $this->assertEquals(0.25, $this->component->dyPowerFunction(2,2));
+
+
+ $this->component->detachBehavior('dy1');
+ $this->component->detachBehavior('dy2');
+ $this->component->detachBehavior('dy3');
+
+ //test class behaviors of dynamic events and the argument list order
+
+ $this->assertEquals(' aa bb cc __ .. ++ || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+
+ $this->component->attachBehavior('dy1', new dy1ClassTextReplace);
+ $this->assertFalse($this->component->dy1->isCalled());
+ $this->assertEquals(' aa bb cc .. .. ++ || !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy1->isCalled());
+
+ $this->component->attachBehavior('dy2', new dy2ClassTextReplace);
+ $this->assertFalse($this->component->dy2->isCalled());
+ $this->assertEquals(' aa bb cc .. .. ++ ++ !! ?? ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy2->isCalled());
+
+ $this->component->attachBehavior('dy3', new dy3ClassTextReplace);
+ $this->assertFalse($this->component->dy3->isCalled());
+ $this->assertEquals(' aa bb cc .. .. ++ ++ !! ^_^ ', $this->component->dyTextFilter(' aa bb cc __ .. ++ || !! ?? '));
+ $this->assertTrue($this->component->dy3->isCalled());
+
+ $this->assertEquals(' aa bb cc __ .. ++ || !! ?? ', $this->component->dyUndefinedEvent(' aa bb cc __ .. ++ || !! ?? '));
+
+ $this->assertEquals(0.25, $this->component->dyPowerFunction(2,2));
+
+
+ }
+
+
+
+
+ public function testDynamicIntraObjectEvents() {
+
+ $this->component->attachBehavior('IntraEvents', new IntraObjectExtenderBehavior);
+
+ $this->assertNull($this->component->IntraEvents->LastCall);
+
+ //unlisten first, this object listens upon instantiation.
+ $this->component->unlisten();
+ $this->assertEquals(2, $this->component->IntraEvents->LastCall);
+
+ // ensures that IntraEvents nulls the last call variable when calling this getter
+ $this->assertNull($this->component->IntraEvents->LastCall);
+
+ //listen next to undo the unlisten
+ $this->component->listen();
+ $this->assertEquals(1, $this->component->IntraEvents->LastCall);
+
+
+ $this->assertEquals(3, $this->component->evaluateExpression('1+2'));
+ $this->assertEquals(7, $this->component->IntraEvents->LastCall);
+
+ $statements='$a="test string"; echo $a;';
+ $this->assertEquals('test string', $this->component->evaluateStatements($statements));
+ $this->assertEquals(8, $this->component->IntraEvents->LastCall);
+
+ $component2 = new NewComponentNoListen();
+ $this->assertNull($this->component->createdOnTemplate($component2));
+ $this->assertEquals(9, $this->component->IntraEvents->LastCall);
+
+ $this->assertNull($this->component->addParsedObject($component2));
+ $this->assertEquals(10, $this->component->IntraEvents->LastCall);
+
+
+
+ $behavior = new BarBehavior;
+ $this->assertEquals($behavior, $this->component->attachBehavior('BarBehavior', $behavior));
+ $this->assertEquals(11, $this->component->IntraEvents->LastCall);
+
+ $this->assertNull($this->component->disableBehaviors());
+ $this->assertNull($this->component->enableBehaviors());
+ $this->assertEquals(27, $this->component->IntraEvents->LastCall);
+
+ $this->assertTrue($this->component->disableBehavior('BarBehavior'));
+ $this->assertEquals(16, $this->component->IntraEvents->LastCall);
+
+ $this->assertTrue($this->component->enableBehavior('BarBehavior'));
+ $this->assertEquals(15, $this->component->IntraEvents->LastCall);
+
+ $this->assertEquals($behavior, $this->component->detachBehavior('BarBehavior'));
+ $this->assertEquals(12, $this->component->IntraEvents->LastCall);
+
+
+ $this->component->attachEventHandler('OnMyEvent',array($this->component,'myEventHandler'));
+ $this->component->raiseEvent('OnMyEvent',$this,null);
+
+ //3 + 4 + 5 + 6 = 18 (the behavior adds these together when each raiseEvent dynamic intra event is called)
+ $this->assertEquals(18, $this->component->IntraEvents->LastCall);
+ }
+
+
+
+ public function testJavascriptGetterSetter() {
+
+ $this->assertFalse(isset($this->component->ColorAttribute));
+ $this->assertFalse(isset($this->component->JsColorAttribute));
+
+ $this->component->ColorAttribute = "('#556677', '#abcdef', 503987)";
+ $this->assertEquals("('#556677', '#abcdef', 503987)", $this->component->ColorAttribute);
+
+ $this->assertTrue(isset($this->component->ColorAttribute));
+ $this->assertTrue(isset($this->component->JsColorAttribute));
+
+ $this->component->ColorAttribute = "new Array(1, 2, 3, 4, 5)";
+ $this->assertEquals("new Array(1, 2, 3, 4, 5)", $this->component->JsColorAttribute);
+
+ $this->component->JsColorAttribute = "['#112233', '#fedcba', 22009837]";
+ $this->assertEquals("['#112233', '#fedcba', 22009837]", $this->component->ColorAttribute);
+ }
+
+
+ public function testJavascriptIssetUnset() {
+
+ $this->component->JsColorAttribute = "['#112233', '#fedcba', 22009837]";
+ $this->assertEquals("['#112233', '#fedcba', 22009837]", $this->component->ColorAttribute);
+
+ unset($this->component->ColorAttribute);
+
+ $this->assertFalse(isset($this->component->ColorAttribute));
+ $this->assertFalse(isset($this->component->JsColorAttribute));
+
+ $this->component->JsColorAttribute = "['#112233', '#fedcba', 22009837]";
+
+ $this->assertTrue(isset($this->component->ColorAttribute));
+ $this->assertTrue(isset($this->component->JsColorAttribute));
+
+ unset($this->component->JsColorAttribute);
+
+ $this->assertFalse(isset($this->component->ColorAttribute));
+ $this->assertFalse(isset($this->component->JsColorAttribute));
+
+ }
+
+
+
}
?> \ No newline at end of file