Base class for objects that allow delegation.
It is first necessary to discuss the role of delegates in PEAR. With the advent of PHP 5, a whole host of new programming techniques were introduced. Among them were class interfaces. These interfaces provide much of the benefits of multiple inheritance (with regard to methods) without adulterating the inheritance hierarchy. That is, an object can inherit from a parent class as usual, but still possess methods that qualify it as a member of another group of objects, related by certain behavior but still exclusively separate in the hierarchy. The interfaces, however, only define the protocol to which each adopting class must adhere, but they do not define the implementation. This is very useful in many instances, but for some purposes, the implementation remains viritually the same for all adopting parties. This is where delegation enters.
A delegate is a class that defines methods which are intended to be called by an adopting object as if they were members of that object. For instance,
As many delegates as necessary can be added to an object, and they can be either class objects or instance objects. Class objects have their methods called statically.
- class Foo extends PEAR_Delegator
- {
- public function _construct()
- {
- parent::_construct();
- }
- public function _destruct()
- {
- parent::_destruct();
- }
- }
- $foo = new Foo();
- $foo->bar() //This results in a runtime error,
- //Foo has no such method.
- //Now define a delegate. Note that the constructor and destructor
- //are unnecessary, as this delegate will be added statically.
- class Delegate
- {
- public function _construct()
- {
- parent::_construct();
- }
- public function _destruct()
- {
- parent::_destruct();
- }
- public function bar()
- {
- echo "bar";
- }
- }
- foo->addDelegate(Delegate); //add the delegate.
- foo->bar(); //This will be called as if it
You may wonder about the performance impact of this model. In actuality, there should be little extra overhead after the first call to a delegated method. This is due to a caching scheme: When methods are called upon a delegator, the delegator checks another associated array that contains method names as keys and the proper delegates as values. If the key (method name) is cached in this manner, then the method is immediatly invoked on the proper delegate. If it does not exist, then each delegate is searched until one that can respond is found and this relationship is cached, otherwise, a fatal error is produced. Thus no matter how many delegates a class has, all calls after the first should only have a small latency.
To call the method, PEAR_Delegator implements the __call() method. When it finds the correct delegate, it calls the method, transparently inserting a reference to the owning object ($this) as the first argument, so that the delegate can act on its owner as if it is itself. Thus, delegated methods must be defined as follows:
Note, however, that the user of the method need only consider those parameters that follow the first parameter.
- accesslevel function functionName($owner, ...);
One of the benefits of this scheme is the ability to have delegate hierarchies. That is, a delegator could have a delegate that is a delegator, and the PEAR_Delegator class recognizes this by viewing such delegates as subdelegates, treating such subdelegates as subclasses would be treated. This allows for such capabilities as pseudo-overriding:
Now, the delegate's implementation would be called as well. This of course means that you can also completely override the delegate method, and not even call it.
- //In our Foo class we could define a bar() method as follows:
- public function bar()
- {
- $args = func_get_args();
- $this->forwardMethod("bar", $args);
- echo "foobar";
- }
In truth, this mode of delegation is unorthodox. The traditional model of delegation is that an object delegates selected methods, calling its own version unless one delegate is present. This feature is, in fact, a subset of the scheme presented here. In otherwords, you can achieve the same effect by limiting the forwarding mechanism:
- //In our Foo class, we could define a bar() method as follows:
- public function bar()
- {
- if ($this->hasDelegate()) {
- $args = func_get_args();
- return $this->forwardMethod("bar", $args);
- }
- echo "foobar";
- }
- //Now, if you call:
- $foo->bar();
- //you get the functionality provided by the class,
- //but if you set the delegate:
- $foo->setDelegate(Delegate); //or instantiate the
- //delegate and set the object.
- //You can use its functionality instead.
Some might also worry about the flexibility of errors. This is not a trouble at all. In fact, the error output of this class is more direct in many cases than PHP's own error output. It will give you the file and line of the error in user code and its messages are modeled after those of PHP.
Terminology:
* owner: a delegator. * subdelegate: A delegator that is a delegate. * native delegate: A delegate that is an immediate delegate, not a delegate of a delegate. * static (class) delegate: A delegate that is simply the class. * dynamic (object) delegate: A delegate that is an instantiated class.
Located in /Delegator.php (line 197)
An associative array with delegate classnames as keys and objects as values.
This is checked by the forwarding mechanism to find which object should be considered the calling object.
An associative array with delegated methods as keys and delegate objects as values.
Constructs a delegator.
Destroys a delegator.
When a delegator is destroyed, it automatically removes all of the delegates, so it is unnessary for user code to do so.
Adds delegates to the calling object.
This method takes a list of classnames or objects. If an argument is a classname, then the method determines if it is defined. If it is, the class is added as a static delegate, otherwise a fatal error is raised. If it is an object, it is stored as a delegate; thus, there are two types of delegates: static and dynamic delegates.
Add extensions to this class.
This adds the PEAR_Delegator_Extensions delegate.
Stores the relationship between method names and delegates.
Takes a method name, searches for the delegate that can handle it, and stores the relationship in the _method_map array. This method is called when the __call() method reveives an unrecognized method. This caching of methods speeds up delegation. If the method cannot be handled by any of the adopted delegates, then an Exception is thrown.
Note: This caches the first responding delegate.
This is an internal method and should not be invoked.
Removes the unwanted entries from _method_map.
This method cleans the _method_map array when a call to removeDelegate*() is made.
This can be used by delegators for pseudo-method-overriding a method.
This is the public interface to the __call() method, and it allows for a method to be forwarded to the delegation system, so that pseudo-method-overriding can occur.
Gets the associated array of delegate classes => delegates.
Note: Cloning may not work after this.
Gets the delegate objects that are instances of the specified class.
This method returns instances of the specified classname as well as child instances of the specified classnames, including subdelegates, which are native to the caller. That is, if one of the delegates is a delegator and it contains a delegate of the specified type, it will be returned regardless of its own class type.
Gets the native delegate objects that respond to a certain method.
The method returns delegates native to the calling delegator, which can respond to the method in question, whether it be defined in the native delegate or in a delegate deeper in the hierarchy.
Determines whether or not the calling object adopts a particular delegate.
This returns the availability of a delegate, including subdelegates.
Determines if a class or instance object is of the given type.
This method is an extension of the is_aExact() method. It also handles subdelegates, so it returns true if a delegator is passed in and has a delegate of type $classname, whether or not the delegator is of type $classname.
Determines if a class or instance object is of the given type.
This method is analogous to the is_a() method of PHP. However, it handles classes too.
Determines if a class or instance object responds to a method.
This method is an extension of the method_existsExact() method. It also handles subdelegates, so it returns true if a delegator is passed in and has a delegate that can implement $method, whether or not the delegator can implement the $method.
Determines if a class or instance object responds to a method.
This method is analogous to the method_exists() method of PHP. However, it handles classes too.
Removes all delegates.
This completely cleans the calling object of any delegates.
Removes the specified delegate.
Takes a list of delegate classnames and delegate objects and removes them from the calling object.
Finds whether or not the object or one of its delegates implements a method
Finds whether or not the calling object can perform the given method name. The calling object can perform the given method if it or one of its delegates can do so. This means that it also searches delegates that are themselves delegators.
Sets the delegator's one delegate.
This method takes a classname or an object and makes it the only delegate. In actuality, it removes all of the delegates and then adds the specified delegate. This is useful for using the delegation method for the traditional delegate model.
This informs a delegator that is a delegate not to pass itself.
This informs a delegator that is a delegate not to pass itself as the calling object if it needs to forward a method to a delegate. Thus, a chain of responders is established with the initial caller as the caller.
This is an internal method and should not be invoked.
Processes unrecognized method signatures.
This checks the _method_map array for a cached relationship between the method and any delegate. If one exists, the method is immediately called and the result returned. If it does not, then it calls the cacheMethod() method to find and cache the method, after which is calls the unrecognized method on the proper delegate or kills the PHP with an error.
This is an internal method and should not be invoked.
Documentation generated on Fri, 25 Feb 2005 17:29:12 -0500 by phpDocumentor 1.3.0RC3