Class PEAR_Delegator

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.

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:

accesslevel function functionName($owner, ...);

Note, however, that the user of the method need only consider those parameters that follow the first parameter.

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 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:

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.

When a delegator is destroyed, it automatically removes all of the delegates, so it is unnessary for user code to do so.

access: public

void
__destruct
()

addDelegate (line 264)

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.

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.

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.

mixed$specifier: This specifies a delegate classname or object. If $delegate is a string, then it adheres to the tests of getDelegate(). If $delegate is an object, the _delegates array is searched for the object. If $specifier is null, then this returns whether or not the caller has any delegates.

is_a (line 603)

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.

mixed$specifier: Specifies the delegate with either a class or instantiated object.

class$classname: The classname type to check against.

method_exists (line 655)

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.

mixed$specifier,...: Specifies the delegate, whose information is is to be removed. If it is a string, then it adheres to the tests of getDelegate(). If it is an object, then it searches the for that delegate to remove.

respondsToMethod (line 684)

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.

string$method: The method name that is to be searched for availability.

setDelegate (line 308)

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 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.

access: protected

void
setForwardingMethod
(string$method)

string$method: The method that is being forwarded. This must be a lowercase string.

__call (line 771)

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.