EventHandler and Delegate

Ezku 02.08.05 14:35

An EventHandler class that enables one to attach Delegates to events and trigger them. Delegates support lazy-loading objects by using a Handle. Scroll down for an example. PHP5.

 Tekstiversio  Arvo: 5 (7 ääntä)  Äänestä: +  -
<?php
/**
 * Implements event handling: attaching Delegates to events and triggering them
 *
 * @author            Ezku (eevert.saukkokoski@gmail.com)
 * @since              Jul 12, 2005
 */

class EventHandler
{
        /**
         * @var array   2-dimensional IDelegate map
         */

        protected $listeners = array();
       
        /**
         * @var mixed    invocation arguments
         */

        protected $arguments = array();
       
        /**
         * Attached listeners will be invoked using whatever arguments the
         * EventHandler was constructed with.
         */

        public function __construct()
        {
                $this->arguments = func_get_args();
        }
       
        /**
         * Attach a Delegate to an event
         * @param       string event
         * @param       object IDelegate
         */

        public function attach($event, IDelegate $delegate)
        {
                $this->listeners[$event][] = $delegate;
        }
       
        /**
         * Trigger event, invoking all attached listeners
         * @param       string event
         * @return      int      number of listeners invoked
         */

        public function trigger($event)
        {
                if (isset($this->listeners[$event]) && is_array($this->listeners[$event]))
                {
                        foreach($this->listeners[$event] as $delegate)
                        {
                                $delegate->invoke($this->arguments);
                        }
                        return count($this->listeners[$event]);
                }
                return 0;
        }
       
       
        public function __set($event, $delegate)
        {
                $this->attach($event, $delegate);
        }
       
        public function __call($event, $args)
        {
                $this->trigger($event);
        }
}

interface IDelegate
{
        /**
         * Invoke subordinate
         * @param       array  arguments, optional
         * @return      mixed subordinate return value
         */

        public function invoke($args = NULL);
}

/**
 * Calls a method on an object upon invocation
 *
 * @author            Ezku (dmnEe0@gmail.com)
 * @since              Jul 12, 2005
 */

class Delegate implements IDelegate
{
        protected $subordinate = NULL;
        protected $method = NULL;
       
        public function __construct($subordinate, $method)
        {
                $this->subordinate = $subordinate;
                $this->method = $method;
        }
       
        public function invoke($args = NULL)
        {
                Handle::resolve($this->subordinate);
                return call_user_func_array(array($this->subordinate, $this->method), (array) $args);
        }
}

/**
 * Calls a static method on a class upon invocation
 *
 * @author            Ezku (dmnEe0@gmail.com)
 * @since              Jul 14, 2005
 */

class StaticDelegate implements IDelegate
{
        protected $class = NULL;
        protected $method = NULL;
        protected $file = NULL;
       
        /**
         * @param       string class name
         * @param       string method name
         * @param       string file to be included before invocation, optional
         */

        public function __construct($class, $method, $file = NULL)
        {
                $this->class = $class;
                $this->method = $method;
                $this->file = $file;
        }
       
        public function invoke($args = NULL)
        {
                if (!empty($this->file))
                {
                        require_once($this->file);
                }
                return call_user_func_array(array($this->class, $this->method), (array) $args);
        }
}

/**
 * "A Handle represents an uninstantiated object that takes the place of a
 * given object and can be swapped out for the object.
 * Implements lazy loading for composing object heirarchies."
 *
 * @author            Ezku (dmnEe0@gmail.com)
 * @since              Jul 12, 2005
 * @see   http://wact.sourceforge.net/index.php/ResolveHandle
 */

class Handle
{
        /**
         * @var string   class name
         */

        protected $class = NULL;
        /**
         * @var array    class constructor arguments
         */

        protected $args = array();
        /**
         * @var string   class definition file
         */

        protected $file = NULL;
       
        /**
         * @param       string class name
         * @param       array  class constructor arguments
         * @param       string class definition filename, optional
         */

        public function __construct($class, $args, $file = NULL)
        {
                $this->class    = (string)  $class;
                $this->args          = (array)    $args;
                $this->file          = (string)   $file;
        }
       
        /**
         * Resolves a Handle; replaces a Handle instance with its identified class
         * @param       object passed by reference
         */

        static public function resolve(&$handle)
        {
                if ($handle instanceof self)
                {
                        $class = $handle->getClass();
                        $file = $handle->getFile();
                       
                        if (!class_exists($class) && !empty($file) && is_readable($file))
                        {
                                require_once($file);
                        }
                        /**
                         * newInstance seems to be misdocumented: it's actually newInstance ( [mixed args [, ...]] ).
                         */

                        $handle = call_user_func_array(array(new ReflectionClass($class), 'newInstance'), $handle->getArgs());
                }
        }
       
        public function getClass() { return $this->class; }
        public function getFile() { return $this->file; }
        public function getArgs() { return $this->args; }
}






/**
 * Example follows
 */







/**
 * A class that composes an EventHandler for attaching Observers
 */

class Observable
{
        /**
         * @var object   EventHandler
         */

        public $events = NULL;
       
        public function __construct()
        {
                $this->events = new EventHandler($this);
        }
       
        public function foo()
        {
                echo "Observable: method foo called \n<br />";
                $this->events->onFoo(); // Triggers the event "onFoo"
        }
       
        public function bar()
        {
                echo "Observable: method bar called \n<br />";
                $this->events->onBar(); // Triggers the event "onBar"
        }
}

/**
 * A matching Observer class for Observable
 */

class Observer
{
        public function __construct(Observable $observable)
        {
                /**
                 * Setting a delegate to a property of an EventHandler is the same
                 * as calling attach() - a nice short cut. Note that you aren't limited
                 * to one listener per event, there can be several.
                 */

                $observable->events->onFoo = new Delegate($this, 'callback_foo');
                $observable->events->onBar = new Delegate($this, 'callback_bar');
        }
        public function callback_foo(Observable $observable)
        {
                echo "Observer: event onFoo triggered \n<br />";
        }
       
        public function callback_bar(Observable $observable)
        {
                echo "Observer: event onBar triggered \n<br />";
        }
}

/**
 * Let's demonstrate a bit
 */


$observable = new Observable;
$observer       = new Observer($observable);

$observable->foo();
$observable->bar();

/**
 * Now, imagine you want an object to respond to an event, but you don't want
 * to include it's definition or instantiate it before you really have to. This
 * is lazy loading, just what Handle is for.
 *
 * Let's imagine there's a class named ColossalObserver that resides in
 * ColossalObserver.php and has a method named `callback` - let's create a
 * Handle and attach it.
 */


$colossal = new Handle('ColossalObserver', NULL, 'ColossalObserver.php');
$observable->events->onBar = new Delegate($colossal, 'callback');

/**
 * The rest is left as an exercise for the reader.
 */

?>
 

editoitu: 14:48 2.8.05
Ezku 14:38 2.8.05 
Tämä härdelli on siis tarkoitettu avuksi lähinnä Observer design patternin soveltamiseen, mutta miksei muuhunkin säätämiseen. Esimerkiksi Model-View-Controller-esimerkin PageControllerin rumat onAction-metodit voi korvata nätisti tällä.

Yritin pitää homman mahdollisimman siistinä, mutta parannusehdotuksia otetaan luonnollisesti vastaan. Toivottavasti tästä on jollekin apua.
Ezku 21:33 2.8.05 
Kiinnostavatko tällaiset koodipätkät ylipäätään ketään? Toistaiseksi on ainakin ollut hiljaista kommenttien kanssa, mutta kertokaa hyvät ihmiset on näitä mitään järkeä pistää tänne. Puuttuuko otsikosta mediaseksikkyyttä? :)
tsuriga 23:18 2.8.05 
Itsehän olen pelehtinyt ensin Javan kanssa ja sittemmin on tullut tutustuttua PHP:hen jo hyvän aikaa. Tajuan kyllä koodin kaikkine rakenteineen lähestulkoon kokonaan, mutta asiaan perehtymättömänä tulee mieleen, että luodaanko tässä lantaa hopeahaarukalla? "Tämä on näitä nykyajan hömpötyksiä".

Tässä vaiheessa luin linkin "Observer"-sisällön ja hienoinen käsitys skriptin tarkoituksesta alkoi muodostua. Jos tuosta saat aikaan vielä konkreettisen ja hyödyllisen esimerkin niin saatan kiinnostua lisää. Hauskaa tietysti, että näitä edistyneempiäkin pätkiä joku jaksaa kirjoitella.
eis 23:22 3.8.05 
Varmaan MVC-skriptin saamat kommentit ja arvosanat kertovat jo, että mielelläänhän ihmiset näitä katselevat. Pistä ihmeessä vaan..
editoitu: 18:50 5.8.05
T.M. 18:49 5.8.05 
Englantia suomenkielisellä ohjelmointisivustolla? Ei kiitos, eli en jaksa edes perehtyä koodiin.
theril 20:47 8.8.05 
Patterneista voisi IMHO laittaa mielummin wikiin. Varmaankin lyhyehkö selitys kertoo enemmän, tai ainakin nopeammin, patternsta ja etenkin sen käyttötarkoituksista.
Ezku 20:25 15.8.05 
Törmäsin tässä aloittelijoillekin sopivaan opukseen PHP5 Power Programming, jonka ilmainen PDF-versio löytyy täältä. Lähtee aivan alkeista, mutta sisältää myös design patterneja esimerkkisovellutuksineen. Esimerkiksi Observer löytyy sivulta 101.
Ezku 17:37 19.8.05 
Täällä oli oikein hyvä selitys skriptin hyödyllisyydestä:
-- why bother with the introduction of using delegates? The advantage of the publisg / subscribe idiom is that any number of classes can be notified when an event is raised. The subscribing classes do not need to know how the Clock works, and the Clock does not need to know what they are going to do in response to the event. Similarly a button can publish an Onclick event, and any number of unrelated objects can subscribe to that event, receiving notification when the button is clicked.

The publisher and the subscribers are decoupled by the delegate. This is highly desirable as it makes for more flexible and robust code. The clock can chnage how it detects time without breaking any of the subscribing classes. The subscribing classes can change how they respond to time changes without breaking the Clock. The two classes spin indepentdently of one another, which makes for code that is easier to maintain.
wex 15:16 29.8.05 
Tästä voisi kyllä säätää jotain omaan käyttöön. Pitää katsella kun kotiin pääseen, että taipuuko.
editoitu: 00:58 27.10.05
zer0hunt3r 00:53 27.10.05 
Miks vitus pitää pistää enkuks kaikki jutut(vissiin niin hienoo...), itteeni ainaki sekottaa tommonen, natiivi kieli paras. Ei sillä etten osais enkkua, pitkä enkku L mut selkeys äfter ooool............................................................. Tässäkään pätkässä ei mitään loogisesti nerokasta ole, mutta vaivaa on nähty