<?php /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/** * This file defines PEAR_Delegator, which provides delegation capabilities. * * PHP version 5 * * @package PEAR_Delegator * @author Michael Witten <lingwitt@yahoo.com> * @copyright 2004-2005 Michael Witten * @license http://www.php.net/license/3_0.txt PHP License 3.0 * @link http://pear.php.net/package/PEAR_Delegator */
// /* vim: set expandtab tabstop=4 shiftwidth=4: */ // +----------------------------------------------------------------------+ // | PHP Version 5 | // +----------------------------------------------------------------------+ // | Copyright (c) 1997-2004 The PHP Group | // +----------------------------------------------------------------------+ // | This source file is subject to version 3.0 of the PHP license, | // | that is bundled with this package in the file LICENSE, and is | // | available through the world-wide-web at the following url: | // | http://www.php.net/license/3_0.txt. | // | If you did not receive a copy of the PHP license and are unable to | // | obtain it through the world-wide-web, please send a note to | // | license@php.net so we can mail you a copy immediately. | // +----------------------------------------------------------------------+ // | Authors: Michael Witten 4100 85 <LingWitt@yahoo.com> | // | Martin Jansen <mj@php.net> for overwhelming encouragement | // +----------------------------------------------------------------------+ // 4100 85 // $Id$
/** * This includes the exceptions thrown. */ require_once 'Delegator/DelegatorExceptions.php';
/** * 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, * <code> * 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 * //were a member of Foo. * </code> * 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: * <code> * accesslevel function functionName($owner, ...); * </code> * 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: * <code> * //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'; * } *</code> * 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: * <code> * //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. * // * </code> * * 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: * <pre> * * 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. * </pre> * * @since PHP 5.0.0 * @author Michael Witten <LingWitt@yahoo.com> 4100 85 * @see http://pear.php.net/manual/ * @package PEAR_Delegator */
class PEAR_Delegator { /** * An associative array with delegate classnames as keys * and objects as values. * @var array */ public $_delegates = array(); /** * An associative array with delegated methods as keys * and delegate objects as values. * @var array */ public $_method_map = array();
/** * This is checked by the forwarding mechanism to find * which object should be considered the calling object. * @var string */ public $_forwardingMethod;
/** * Constructs a delegator. */
public function __construct() { }
/** * 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. */ public function __destruct() { $this->removeAllDelegates(); }
/** * Add extensions to this class. * * This adds the PEAR_Delegator_Extensions delegate. */ public function addExtensions() { if (!class_exists(PEAR_Delegator_Extensions)) { require_once 'Delegator/DelegatorExtensions.php'; $this->addDelegate(new PEAR_Delegator_Extensions); } }
/** * 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. * * @see PEAR_Delegator::setDelegate() * * @param mixed $delegate,... This specifies either a classname or an object. */ public function addDelegate($delegate) { $args = array_values(func_get_args());
foreach ($args as $delegate) { if (is_string($delegate)) { if (!class_exists($delegate)) { $exception = new PEAR_Delegator_ExceptionDelegateUndefined($delegate); die('<b>Fatal error</b>: ' . $exception->getMessage()); }
/** * 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. * * @see PEAR_Delegator::addDelegate() * @uses PEAR_Delegator::removeAllDelegates(), PEAR_Delegator::addDelegate() * @param mixed $delegate This specifies either a classname or an object. */
public function setDelegate($delegate) { $this->removeAllDelegates(); if ($delegate) { $this->addDelegate($delegate); } }
/** * Gets the associated array of delegate classes => delegates. * * Note: Cloning may not work after this. * @see PEAR_Delegator::getAllDelegates(), PEAR_Delegator::getDelegate(), * PEAR_Delegator_Extensions::getDelegateExact(), PEAR_Delegator_Extensions::getDelegateRecursive(), * PEAR_Delegator_Extensions::getDelegateRecursiveExact() * @return array A reference to the _delegates array. */ public function &getAllDelegates() { return $this->_delegates; }
/** * 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. * * @see PEAR_Delegator::getAllDelegates(), PEAR_Delegator::getDelegate(), * PEAR_Delegator_Extensions::getDelegateExact(), PEAR_Delegator_Extensions::getDelegateRecursive(), * PEAR_Delegator_Extensions::getDelegateRecursiveExact() * @param class $classname1 This specifies a delegate classname. Any * number of arguments after this is acceptable. * @return array <pre>The result is an * associative array of the form: * Array * ( * [classname1] = Array * ( * delegate11 * delegate12 * ... * ) * [classnamei] = Array * ( * delegate1i * delegate1i * ... * ) * ... * ) * Note: If a single classname is passed, a traditional array with numbered * elements is returned. * </pre> */ public function getDelegate($classname1) { $args = func_get_args();
$result = null;
foreach ($args as $classname) { foreach ($this->_delegates as $delegate) { if (PEAR_Delegator::is_a($delegate, $classname)) { $result[$classname][] = $delegate;
/** * 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. * * @see PEAR_Delegator_Extensions::getDelegateForMethodRecursive(), PEAR_Delegator_Extensions::getDelegateForMethodRecursiveExact(), * PEAR_Delegator_Extensions::getDelegateForMethodFirst(), PEAR_Delegator::method_exists() * @uses PEAR_Delegator::method_exists() * @param string $method1,... This specifies the method for whose responder is searched. * @return array <pre>The result is an * associative array of the form: * Array * ( * [method1] = Array * ( * delegate11 * delegate12 * ... * ) * [methodi] = Array * ( * delegate1i * delegate1i * ... * ) * ... * ) * Note: If a single classname is passed, a traditional array with numbered * elements is returned. * </pre> */ public function getDelegateForMethod($method1) { $result = array();
$args = func_get_args();
foreach ($args as $method) { foreach ($this->_delegates as $delegate) { if (PEAR_Delegator::method_exists($delegate, $method)) { $result[$method][] = $delegate; } } }
/** * Determines whether or not the calling object adopts a particular delegate. * * This returns the availability of a delegate, including subdelegates. * * @see PEAR_Delegator_Extensions::hasDelegateExact(), PEAR_Delegator::is_a(); * @param 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. * @return bool If the calling object has adopted the specifed class name */ public function hasDelegate($specifier = null) { if ($specifier == null) { return (count($this->_delegates)) ? true : false; } elseif (is_string($specifier)) { $specifier = $specifier;
if (array_key_exists($specifier, $this->_delegates)) { return true; }
foreach ($this->_delegates as $delegate) { if (PEAR_Delegator::is_aExact($delegate, $specifier)) { return true; }
/** * Removes all delegates. * * This completely cleans the calling object of any delegates. * * @see PEAR_Delegator::removeDelegate(), PEAR_Delegator_Extensions::removeDelegateRecursiveExact() */ public function removeAllDelegates() { unset($this->_method_map); unset($this->_delegates);
/** * Removes the unwanted entries from _method_map. * * This method cleans the _method_map array when a call to removeDelegate*() * is made. * * @param object $filterDelegate Specifies the delegate instance, whose information is * is to be removed. * @return array The method map without the $filterdelegate */ public function filterMethodMapWithDelegate($filterDelegate) { $result = array();
/** * Removes the specified delegate. * * Takes a list of delegate classnames and delegate objects and removes them * from the calling object. * * @param 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. * @see PEAR_Delegator::removeAllDelegates(), PEAR_Delegator_Extensions::removeDelegateRecursiveExact() * @uses PEAR_Delegator::getDelegate(), PEAR_Delegator::filterMethodMapWithDelegate() */ public function removeDelegate($specifier) { $args = func_get_args();
/** * 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. * * @see PEAR_Delegator::is_a() * @param mixed $specifier Specifies the delegate with either * a class or instantiated object. * @param class $classname The classname type to check against. * @return bool true if it is, false if it is not. */ public function is_aExact($specifier, $classname) { if (is_string($specifier)) { if ((new $specifier) instanceof $classname) { return true; } } elseif ($specifier instanceof $classname) { return true; }
return false; }
/** * 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. * * @uses PEAR_Delegator::hasDelegate() * @param mixed $specifier Specifies the delegate with either * a class or instantiated object. * @param class $classname The classname type to check against. * @return bool true if it is, false if it is not. */ public function is_a($specifier, $classname) { if (PEAR_Delegator::is_aExact($specifier, $classname)) { return true; } elseif (is_object($specifier) && (($specifier instanceof PEAR_Delegator) && $specifier->hasDelegate($classname))) { return true; }
return false; }
/** * 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. * * @see PEAR_Delegator::method_exists() * @param mixed $specifier Specifies the delegate with either * a class or instantiated object. * @param class $classname The method to look for. * @return bool true if it is, false if it is not. */
public function method_existsExact($specifier, $method) { if (is_string($specifier)) { if (method_exists(new $specifier, $method)) { return true; } } elseif (method_exists($specifier, $method)) { return true; }
return false; }
/** * 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. * * @see PEAR_Delegator::method_existsExact() * @param mixed $specifier Specifies the delegate with either * a class or instantiated object. * @param class $classname The method to look for. * @return bool true if it is, false if it is not. */ public function method_exists($specifier, $method) { if (PEAR_Delegator::method_existsExact($specifier, $method)) { return true; } elseif (is_object($specifier) && ($specifier instanceof PEAR_Delegator)) { foreach ($specifier->_delegates as $delegate) { if (PEAR_Delegator::method_exists($delegate, $method)) { return true; } } }
return false; }
/** * 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. * * @see PEAR_Delegator::method_exists() * @uses PEAR_Delegator::method_exists() * @param string $method The method name that is to be searched for availability. * @return bool true if it does, false if it does not. */
public function respondsToMethod($method) { return PEAR_Delegator::method_exists($this, $method); }
/** * 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. * * @param string $method The method name that is to be cached. This must * be a lowercase string. * @throws PEAR_Delegator_ExceptionNoDelegateForMethod If the method */ protected function cacheMethod($method) { $delegates = $this->getDelegateForMethod($method);
throw new PEAR_Delegator_ExceptionMethodUndefined($method); }
/** * 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. * * @param string $method The method that is being forwarded. This must * be a lowercase string. */ protected function setForwardingMethod($method) { $this->_forwardingMethod = $method; }
/** * 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. * * @see PEAR_Delegator::__call() * @param string $method See the PHP documentation. * @param string $args See the PHP documentation */ public function forwardMethod($method, $args) { return $this->__call($method, $args); }
/** * 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. * * @see PEAR_Delegator::forwardMethod(), PEAR_Delegator::cacheMethod() * @param string $method See the PHP documentation. * @param string $args See the PHP documentation. * @return mixed the result of the called method. * @uses PEAR_Delegator::cacheMethod() */ public function __call($method, $args) { //It is necessary to convert the string to lowercase since PHP doesn't //differentiate between case when calling methods. This distills the //situation to one case. $method = strtolower($method);
if ($method != $this->_forwardingMethod) { $args = array_merge(array($this), $args); }