<?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());
                }
                
                
$delegateClass = $delegate;
            } else {
                
$delegateClass = get_class($delegate);
            }
            
            
$this->_delegates[$delegateClass] = $delegate;
        }
    }
    
    
/**
     * 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;
                    
                    continue;
                }
            }
        }
                    
        if (
$result) {
            return (
count($args) > 1) ? $result : $result[$classname1];
        } else {
            return
null;
        }
    }
    
    
/**
     * 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;
                }
            }
        }
        
        if (
count($result)) {
            return (
count($args) > 1) ? $result : $result[$method1];
        }
        else {
            return
null;
        }
    }
    
    
/**
     * 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;
                }
                
                if (
is_object($delegate) && ($delegate instanceof PEAR_Delegator && $delegate->hasDelegate($specifier))) {
                    return
true;
                }
            }
        } else {
            foreach (
$this->_delegates as $delegate) {
                if (
$delegate === $specifier) {
                    return
true;
                }
            }
        }
        
        return
false;
    }
    
    
/**
     * 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);
        
        
$this->_method_map  = array();
        
$this->_delegates   = array();
    }
    
    
/**
     * 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();
        
        
$method_map_keys    = array_keys($this->_method_map);
        
$method_map_values  = array_values($this->_method_map);
        
        for (
$i = 0, $count = count($method_map_values); $i < $count; $i++) {
            
$delegate = $method_map_values[$i];
            
            if (
$delegate === $filterDelegate) {
                continue;
            }
            
            
$result[$method_map_keys[$i]] = $delegate;
        }
        
        return
$result;
    }

   
/**
     * 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();
        
        
$delegates = call_user_func_array(array($this, 'getDelegate'), $args);
        
        foreach (
$delegates as $delegateArray) {
            foreach (
$delegateArray as $delegate) {
                unset(
$this->_delegates[is_string($delegate) ? $delegate : get_class($delegate)]);
                
$this->_method_map = $this->filterMethodMapWithDelegate($delegate);
            }
        }
    }
    
    
/**
     * 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);
                    
        if (
$delegates && ($delegate = $delegates[0])) {
            
$this->_method_map[$method] = $delegate;
            return;
        }
        
        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);
        }
        
        if (!
array_key_exists($method, $this->_method_map)) {
            try {
                
$this->cacheMethod($method);
            } catch(
PEAR_Delegator_ExceptionMethodUndefined $exception) {
                die(
'<b>Fatal error</b>: ' . $exception->getMessage());
            }
        }
        
        
$delegate = $this->_method_map[$method];
        
        if (
$delegate instanceof PEAR_Delegator) {
            
$delegate->setForwardingMethod($method);
            
$result = call_user_func_array(array($delegate, $method), $args);
            
$delegate->setForwardingMethod(null);
            
            return
$result;
        }
        
        return
call_user_func_array(array($delegate, $method), $args);
    }
}
?>