PEAR_DelegateOwner

From: Date: Sun, 09 Nov 2003 05:57:00 +0000
Subject: PEAR_DelegateOwner
Groups: php.pear.dev 
Request: Send a blank email to pear-dev+get-23369@lists.php.net to get a copy of this message
I worry that the original did not make it through, so I've sent it again: I admit, your comments point out some blaring errors. I should have gone over it more carefully first. The new code is posted at the bottom of this page, though it still may be buggy or otherwise inefficient. I've added some comments, so keep reading... On 7 Nov 2003, at 9:13 AM, Xavier Noguer wrote:
                 if (!($delegate instanceof PEAR_DelegateInterface))
                     continue;
Should this fail silently?
No, it probably should not. I'll have to go back and add error detection. Should I use PHP 5 stuff, or stick with the pear errors? I've only recently come across pear, so I'm not sure how I should use the latter or what level of warning that should cause.
                 $delegate->setOwner($this);
                 $this->_delegates[get_class($delegate)] = $delegate;
             }
         }
         //Classnames
         public function getDelegate($variableRequired)
Could this better be named getDelegates, since it returns an array?
That's the way I initially had it, but since it can return one result, I ruled against it (plus there is already another method with the name getDelegates, which returns the _delegates array). I then considered having it return the delegate itself for one argument, but I figured that kind of policy just adds another test for the user to handle. I suppose it can be thought of as getDelegate for each classname you pass in.
         //Classnames
         public function removeDelegate($variableRequired)
         {
             $args = func_get_args();
             foreach ($args as $arg)
             {
                 if ($delegates = $this->getDelegate($arg))
                 {
                     foreach ($delegates as $delegate)
                     {
                         unset($this->_delegates[get_class($delegate)]);
                         $this->_delegates =
$this->filterMethodMapWithDelegate($delegate);
I really goofed up here. That should read $this->_method_map.
                     }
                 }
             }
Can't you make a loop on $this->_delegates here, since the call to getDelegate() is going to make that anyway?
Well, its not really like its going through the entire loop again, but I suppose bypassing getDelegate() would be more efficient, so it has been changed.
         }
         protected function cacheMethod($method)
         {
             foreach ($this->_delegates as $delegate)
             {
                 if (method_exists($delegate, $method))
                 {
                     $this->_method_map[$method] = $delegate;
                     return;
                 }
I don't know much about delegation, so this might be just ignorance on my part. But how do you solve name conflicts, as in two delegates implementing the "same" method?
Chances are that delegate methods are not going to clash since it would be silly for two delegates to implement the same method, and moreover, it is unlikely that one object will ever have enough delegates for such a problem to occur. Nevertheless, I have no right to speculate on such things, so something must be done. Perhaps we could perform a little name mangling such that when name clashes are detected, the method mapping stores methods as classname_method, so that the user must qualify the method. That makes for messiness, but what else is there to do? This is not a liability, but a consequence of bad design on the part of the delegate makers. On 7 Nov 2003, at 9:53 AM, Bertrand Mansion wrote:
Personally, I am against the idea of having more than one delegate per object, it is going to be a real mess to debug. And I don't really see where this could be needed. IMO, you only need setDelegate() and getDelegate(). SetDelegate(null) will remove the delegate.
The traditional concept of delegates does indeed hold that there is one delegate to an object, but that pedantism can still be achieved with this model, because one is a specialized version of the other. It is helpful to have more then one delegate, because some objects may share concepts with multiple groups. This calls for multiple inheritance, but that is not possible or wanted--as it is often regarded as bad design and a bastardization of the inheritance hierarchy. Even so, interfaces are provided to make up for the lack of multiple inheritance, yet they are only helpful in that they define the similar behavior but not the implementation. Thus, every object that needs to implement the interface must actually implement the interface, making it downright stupid when the implementations are all the same. Take for instance this scenario (which may be no good): There is an Element class for the general HTML element. You can create any element with it, even those that are not defined, as such: $elem = new Element("BLAHBLAH"); You can add any kind of attribute and any kind of sub element to it. Now, lets specialize it with the Document class and the Table class, which both extend it as subclasses. The Document class implements the <HTML> tag, so it has methods for taking care of the il8n attributes. Likewise, the Table class implements the <TABLE> tag, and it also has methods for taking care of the il8n attributes. They could either inherit from an il8n superclass, or they could both implement the same methods or both have one delegate that implements them. For simplicity, lets have them inherit from the same class. But wait! The Table element must also deal with the coreattrs and events attributes. To take care of those, the Table class can just implement them itself. That's not all though, because we now have the the Link class the implements the <A> tag, and it too handles the coreattrs, events, and il8n attributes. Thus, the Table and the Link classes should either both inherit from the same class, they should have one delegate that implements them all, or they should implement the same methods. If they inherit from the same class, then there is still waste, because the Document class implements the il8n attributes, which is a waste, and if Document inherits from the same superclass, then it will have methods it doesn't need. If the Table and Link classes have just one delegate, then their is still waste with the Document. If they implement all the methods individually... well... its just more waste. One good solution would be to have delegates that handle those attributes specifically, so that common classes can remain in the proper inheritance hierarchy, but still have similar methods as other objects. Delegation, in this sense, is like having implemented interfaces that are copied over and over for you, so you don't have to do it. That way, it does nothing but improve the efficiency of development.
Also, delegates shouldn't care about their owners.
I wholeheartedly agree: that requirement is now gone and the burden is placed on the developer in that the delegates' methods must all accept $this as their first parameter, so that the same effect is achieved without the setOwner() nonsense.
Sidenote: Objects that have delegate methods should declare them somewhere so we know which methods can be called. This should also be documented in the class API. A new @delegate tag will probably be needed in phpDocumentor...
I originally had a method that did this, but it just means more overhead. Either the developer has to do some name mangling to specify the delegated methods, or the list has to be maintained, both of which are unsatisfactory and tedious. Instead, the methods are just cached as they are used. Also, the scheme produced here does not allow for the traditional idea of delegates, whereby the owning class calls its own generic implementation if there is no delegate. Nevertheless, the same thing can be achieved by checking if a delegate implements the same method and then calling that one instead (which is how Cocoa does it).
Bertrand Mansion Mamasam
<?php
    /* vim: set expandtab tabstop=4 shiftwidth=4: */
    // +----------------------------------------------------------------------+
    // | PHP version 4                                                        |
    // +----------------------------------------------------------------------+
    // | Copyright (c) 1997-2003 The PHP Group                                |
    // +----------------------------------------------------------------------+
    // | This source file is subject to version 2.0 of the PHP license,       |
    // | that is bundled with this package in the file LICENSE, and is        |
    // | available through the world-wide-web at                              |
    // | http://www.php.net/license/2_02.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: Herr Witten <LingWitt@yahoo.com>                            |
    // |                                                                      |
    // +----------------------------------------------------------------------+
    //
    // $Id$
    require_once "PEAR.php";
    class PEAR_DelegateOwner extends PEAR
    {
        var $_delegates     = array();
        var $_method_map    = array();
        public function addDelegate($variableRequired)
        {
            $args = array_values(func_get_args());
            foreach ($args as $delegate)
            {
                if (is_string($delegate) && class_exists($delegate))
                    $delegate = new $delegate;
                $this->_delegates[get_class($delegate)] = $delegate;
            }
        }
        public function &getDelegates()
        {
            return $this->_delegates;
        }
        //Classnames
        public function getDelegate($variableRequired)
        {
            $args = func_get_args();
            foreach ($args as $arg)
            {
                foreach ($this->_delegates as $delegate)
                {
                    if ($delegate instanceof $arg)
                    {
                        $results[$arg] = $delegate;
                    }
                }
            }
            return $results;
        }
        //Classnames
        public function getDelegateExact($classname)
        {
            $classname = strtolower($classname);
            if (array_key_exists($classname, $this->_delegates))
            {
                return $this->_delegates[$classname];
            }
            else
            {
                return null;
            }
        }
        public function hasDelegate($delegate)
        {
            if (is_string($delegate) && $this->getDelegate($delegate))
            {
                return true;
            }
            else if (is_object($delegate))
            {
                foreach ($this->_delegates as $delegateObject)
                {
                    if ($delegateObject === $delegate)
                        return true;
                }
            }
            return false;
        }
        //Classnames
        public function hasDelegateExact($classname)
        {
            if ($this->getDelegateExact($classname))
            {
                return true;
            }
            return false;
        }
        public function removeDelegates()
        {
            unset($this->_method_map);
            unset($this->_delegates);
        }
        private 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;
        }
public function removeDelegate($variableRequired) {
	    $args = func_get_args();
	    foreach ($args as $arg)
	    {
foreach ($this->_delegates as $delegate) {
		    if ($delegate instanceof $arg)
		    {
unset($this->_delegates[get_class($delegate)]); $this->_method_map = $this->filterMethodMapWithDelegate($delegate);
		    }
}
	    }
}
        //Classnames
        public function removeDelegateExact($variableRequired)
        {
            $args = func_get_args();
            foreach ($args as $arg)
            {
                if ($delegate = $this->getDelegateExact($arg))
                {
                    unset($this->_delegates[get_class($delegate)]);
                    $this->_method_map = $this->filterMethodMapWithDelegate($delegate);
                }
            }
        }
        protected function cacheMethod($method)
        {
            foreach ($this->_delegates as $delegate)
            {
                if (method_exists($delegate, $method))
                {
                    $this->_method_map[$method] = $delegate;
                    return;
                }
            }
            throw new Exception("No Such Method: $method()");
        }
        public function __call($method, $args)
        {
            $method = strtolower($method);
	    $args = array_merge(array($this), $args);
            if (!array_key_exists($method, $this->_method_map))
            {
                try {$this->cacheMethod($method);}
                    catch(Exception $exception)
                    {
                        die ("Fatal: " . $exception->getMessage());
                    }
            }
            return call_user_func_array(array($this->_method_map[$method], $method), $args);
        }
    }
?>

« previous php.pear.dev (#23369) next »