SitePoint Sponsor

User Tag List

Results 1 to 16 of 16
  1. #1
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,194
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)

    PHP Event Handling

    I built the below to achieve some very basic event handling functionality. However, I would like to review some other as well. So if you would like to share your own interpretation and/or solution for handling events in PHP it would be appreciated. Pretty much just looking for some insight and discussion regarding handling events and classes designed to achieve such a task in PHP. I'm not really looking for anything just thrown together but solutions that have been clearly thought through for a particular requirement or application.

    thanks

    PHP Code:
    <?php 
    /*
    * Manages events 
    */
    class EventHandler {
        
        private
        
        
    /*
        * Stack of object hashes with event keys 
        * and associated handlers to each event.
        */
        
    $_arrEvents;
        
        public function 
    __construct() {
            
    $this->_init();
        }
        
        private function 
    _init() {
            
    $this->_arrEvents = array();
        }
        
        
    /*
        * Get objects unique foorprint for reference in events array 
        * 
        * @param obj object to get unique foot print for
        * @return str unique object foot print
        */
        
    private function _getFootprint($obj) {
            return 
    spl_object_hash($obj);
        }
        
        
    /*
        * Subscribe handler to event for object
        * 
        * @param obj listen to this object
        * @param str for this event
        * @param arr [obj,method] call this handler on event
        */
        
    public function subscribe($objDispatcher,$strEvt,$arrHandler) {
            
    $strFootprint $this->_getFootprint($objDispatcher);
            
    $this->_arrEvents[$strFootprint][$strEvt][] = $arrHandler
        }
        
        
    /*
        * Fire event
        * 
        * @param obj target
        * @param str event name
        */
        
    public function fire($objTarget,$strEvt) {
            
            
    $strTarget $this->_getFootprint($objTarget);
            
            if(isset(
    $this->_arrEvents[$strTarget],$this->_arrEvents[$strTarget][$strEvt])) {
                
                
    /*
                * Call event listeners 
                */
                
    foreach($this->_arrEvents[$strTarget][$strEvt] as $arrHandler) {
                    
    array_shift($arrHandler)->{array_shift($arrHandler)}(array('target'=>$objTarget,'event'=>$strEvt));
                }
                
                
    /*
                * Bubble event 
                */
                
    foreach($this->_arrEvents[$strTarget][$strEvt] as $arrHandler) {
                    
    $this->fire(array_shift($arrHandler),$strEvt);
                }
            }
            
        }
        
    }
    ?>

  2. #2
    SitePoint Guru
    Join Date
    Jun 2006
    Posts
    638
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It's late, but this sounds allot like Observer pattern

  3. #3
    Twitter: @AnthonySterling silver trophy AnthonySterling's Avatar
    Join Date
    Apr 2008
    Location
    North-East, UK.
    Posts
    6,111
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    IMO, there isn't a better Event Handler implementation out there that beats Symfony's.

    You can quickly browse the source over at Git.

    As an aside, I'd be interested to know why you chose to code this segment the way you have...
    PHP Code:
        $_arrEvents;
        
        public function 
    __construct() {
            
    $this->_init();
        }
        
        private function 
    _init() {
            
    $this->_arrEvents = array();
        } 
    Why not assign the property directly in the constructor?
    @AnthonySterling: I'm a PHP developer, a consultant for oopnorth.com and the organiser of @phpne, a PHP User Group covering the North-East of England.

  4. #4
    SitePoint Addict SirAdrian's Avatar
    Join Date
    Jul 2005
    Location
    Kelowna, BC
    Posts
    289
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    +1 for Symfony's offering if you want something really robust with lots of support.
    Adrian Schneider - Web Developer

  5. #5
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    eZ Components SignalSlot

    http://www.ezcomponents.org/docs/api...ignalSlot.html

    Also if you use a SplObjectStorage instead of an array, you can do away with the spl_object_hash() and use an object as an index directly.

  6. #6
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,194
    Mentioned
    17 Post(s)
    Tagged
    4 Thread(s)
    Quote Originally Posted by AnthonySterling
    IMO, there isn't a better Event Handler implementation out there that beats Symfony's.

    You can quickly browse the source over at Git.
    I have yet to use symphony or even glance at the code, but I'll take a look.

    Quote Originally Posted by AnthonySterling
    Why not assign the property directly in the constructor?
    To form a separation between internal and external configuration of an object the constructor assigns parameters to properties and initiation method(s) configure internal components.

    Quote Originally Posted by Vali
    It's late, but this sounds allot like Observer pattern
    I would like to keep this functionality isolated and decoupled from the rest of the application code which is the reason for not using the observer pattern.

    Quote Originally Posted by Ren
    Also if you use a SplObjectStorage instead of an array, you can do away with the spl_object_hash() and use an object as an index directly.
    Nothing below 5.3 implements array access with SplObjectStorage which makes it impractical given the environment I'm currently working on.

    This is interesting and bares many similarities with what I was doing. The signatures are similar to what I had envisioned on my own, although I like the grammar more.

  7. #7
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    Nothing below 5.3 implements array access with SplObjectStorage which makes it impractical given the environment I'm currently working on.
    Ah didn't realise it was added later.

    Quote Originally Posted by oddz View Post
    This is interesting and bares many similarities with what I was doing. The signatures are similar to what I had envisioned on my own, although I like the grammar more.
    It was inspired from Qt Signal Slot...

    http://doc.trolltech.com/4.6/signalsandslots.html

  8. #8
    . shoooo... silver trophy logic_earth's Avatar
    Join Date
    Oct 2005
    Location
    CA
    Posts
    9,013
    Mentioned
    8 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    To form a separation between internal and external configuration of an object the constructor assigns parameters to properties and initiation method(s) configure internal components.
    That makes absolutely no sense. The fact is in this implementation both the constructor and init are both useless methods. You can simply drop both methods and just have: private $event = array();

    You can also join the "Call event listeners" and "Bubble event" foreach together.
    Logic without the fatal effects.
    All code snippets are licensed under WTFPL.


  9. #9
    . shoooo... silver trophy logic_earth's Avatar
    Join Date
    Oct 2005
    Location
    CA
    Posts
    9,013
    Mentioned
    8 Post(s)
    Tagged
    0 Thread(s)
    Was bored and had nothing to do during the trip to class. So in my own style felt like writing out this event handler in my own way.

    Code php:
    <?php
     
    class EventHandler
    {
      # $events {
      #   string dispatcher {
      #     string event {
      #       [#] {
      #         [0] object handler,
      #         [1] string method
      #       }
      #     }
      #   }
      # }
      protected $events = array();
     
      #-------------------------------------------------------------------------------
      # Public Interface
    
      # EventHandler ( object $dispatcher, string $event, array $handler )
      # throws Exception
      public function subscribe ( $dispatcher, $event, array $handler )
      {
        if ( !is_object( $handler[0] ) && !method_exists( $handler[0], $handler[1] ) )
          throw new Exception( "Invalid handler assigned." );
     
        $sig = $this->signature( $dispatcher );
        $this->events[ $sig ][ $event ][] = $handler;
     
        return $this;
      }
     
      # EventHandler ( object $target, string $event )
      public function fire ( $target, $event )
      {
        $sig = $this->signature( $target );
     
        if ( isset( $this->events[ $sig ][ $event ] ) ) {
          foreach ( $this->events[ $sig ][ $event ] as $handler ) {
            $handler[0]->{ $handler[1] }( array(
              'target' => $target, 'event'  => $event ) );
     
            $this->fire( $handler[0], $event );
          }
        }
     
        return $this;
      }
     
      #-------------------------------------------------------------------------------
      # Internal Interface
    
      # string ( object $object )
      protected function signature ( $object ) {
        return spl_object_hash( $object );
      }
    }
    Logic without the fatal effects.
    All code snippets are licensed under WTFPL.


  10. #10
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    996
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    This isn't quite what you're after but it may help... this is something I sometimes use for debugging... it works but it's rather (totally) hacky (see: eval, static methods...)

    It won't work on php 5.2 because it uses namespaces but you could easily rework it to use class prefixes instead.

    The reason this is good is it's COMPLETLEY decoupled from implementation code.


    PHP Code:
    <?
    class Wrapper {
        public 
    $object;
        private 
    $eventHandler;
        
        public function 
    __call($func$args) {
            if (
    EventListener::triggerEvent($thisEventListener::BEFORE$func$args) !== false) {
                
    $return call_user_func_array(array($this->object$func), $args);
                
    EventListener::triggerEvent($thisEventListener::AFTER$func$args);
                return 
    $return;
            }
        }
        
        public function 
    __set($name$value) {
            if (
    EventListener::triggerEvent($thisEventListener::BEFOREEventListener::WRITE_PROPERTYfunc_get_args()) !== false) {
                
    $this->object->$name $value;
                
    EventListener::triggerEvent($thisEventListener::AFTEREventListener::WRITE_PROPERTYfunc_get_args());
            }        
        }
        
        public function 
    __get($name) {
            if (
    EventListener::triggerEvent($thisEventListener::BEFOREEventListener::READ_PROPERTYfunc_get_args()) !== false) {
                
    $return $this->object->$name;
                
    EventListener::triggerEvent($thisEventListener::BEFOREEventListener::READ_PROPERTYfunc_get_args());
                return 
    $return;
            }
        }


    class 
    EventListener {
        const 
    READ_PROPERTY '__get';
        const 
    WRITE_PROPERTY '__set';
        
        const 
    BEFORE 1;
        const 
    AFTER 2;
        
        public static 
    $eventHandlers = array();
        
        public function 
    listenTo($eventHandler$object) {
            
    $hash spl_object_hash($object);
            if (!isset(
    self::$eventHandlers[$hash])) self::$eventHandlers[$hash] = array();
            
    self::$eventHandlers[$hash][] = $eventHandler;        
        }
        
        public static function 
    triggerEvent($object$when$event$args) {
            
    $hash spl_object_hash($object);
            foreach (
    self::$eventHandlers[$hash] as $eventHandler) {
                if (
    $eventHandler->handleEvent($event$when$args) === false){
                    break;
                    return 
    false;
                }
            }
        }    
    }



    function 
    __autoload($className) {
        
    $classDefinition str_replace(array('<?php''?>'), ''file_get_contents($className '.php'));
        
        eval(
    'namespace Implementation;
             ' 
    $classDefinition);
        
        eval(
    'class ' $className ' extends Wrapper {
        public function __construct() {
            $reflect = new ReflectionClass(\'\\Implementation\\' 
    $className '\');
            $this->object = $reflect->newInstanceArgs(func_get_args());
        }
    }'
    );
        
    }

    interface 
    EventHandler {
        public function 
    handleEvent($event$when$args);    
    }
    ?>
    Implementation code:

    First define a class we're going to watch. Must be in its own file.

    foo.php:

    PHP Code:
    <?php 
    class Foo {
        public function 
    __construct() {
            
        }
        
        public function 
    bar() {
            
        }
        
        public function 
    test() {
            
    $this->bar();
        }    
    }
    ?>

    PHP Code:
    class FooEventHandler implements EventHandler {
        public function 
    handleEvent($event$when$args) {
            switch (
    $when) {
                case 
    EventListener::BEFORE$whenText 'before'; break;
                case 
    EventListener::AFTER$whenText 'after'; break;
            }
            echo 
    'event trigered: ' $whenText ' ' $event ' with args: ';
            
    var_dump($args);
            echo 
    '<br />';
        }
    }

    $eventListener = new EventListener;
    $eventHandler = new FooEventHandler;

    $foo = new Foo;
    $eventListener->listenTo($eventHandler$foo);

    $foo->bar(123);
    $foo->test(); 
    Which prints:

    Code:
    event trigered: before bar with args: array(3) { [0]=>  int(1) [1]=>  int(2) [2]=>  int(3) }
    event trigered: after bar with args: array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) }
    event trigered: before test with args: array(0) { }
    event trigered: after test with args: array(0) { }
    There is no knowledge of the event handler in the class definition of Foo, which means any class can be easily watched.

    As I said, totally hacky. Along with probably shockingly bad performance but it does work... and allows you to catch reading/writing of properties.

    What it doesn't catch is events fired within the object using $this, although doing a str_replace() on the class definition could fix that (and make it even more hacky!!)

  11. #11
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    @TomB, There is a bug in PHP 5.3.1 with references & __call(), http://bugs.php.net/bug.php?id=50394

  12. #12
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    996
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Then it's good php 5.3.2 was released yesterday

  13. #13
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think I'd use a DI style container to deal with the decorating of objects, rather than __autoload.

    PHP Code:
    interface Event
    {
    }

    class 
    Events
    {
        protected 
    $events;

        function 
    __construct()
        {
            
    $this->events = array();
        }

        function 
    fire(Event $event)
        {
            
    $class get_class($event);
            if (isset(
    $this->events[$class]))
                foreach(
    $this->events[$class] as $fn)
                    
    $fn($event);
        }

        function 
    connect($eventClosure $fn)
        {
            if (isset(
    $this->events[$event]))
                
    $this->events[$event][] = $fn;
            else
                
    $this->events[$event] = array($fn);
        }

        function 
    connectInstance($event$instance$method)
        {
            
    $this->connect($event,
                function(
    Event $event) use ($instance$method)
                {
                    return 
    $instance->$method($event);
                }
            );
        }

        function 
    connectIndirect($eventClosure $getInstance$method)
        {
            
    $this->connect($event,
                function(
    Event $event) use ($getInstance$method)
                {
                    return 
    $getInstance()->$method($event);
                }
            );
        }
    }

    class 
    EventA implements Event
    {
    }

    interface 
    A
    {
        function 
    onA(EventA $event);
    }

    class 
    HelloWorld implements A
    {
        function 
    onA(EventA $event)
        {
            echo 
    'Hello World!'"\n";
        }
    }

    $container = new Container();
    $events = new Events();

    $container->registerShared('A''HelloWorld');    // wire container

    $events->connectIndirect('EventA'$container->getClosure('A'), 'onA');

    $events->fire(new EventA()); 
    Container::getClosure($interface) returns a closure taking no parameters and returns an object implementing interface $interface.

  14. #14
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    996
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Ok, I don't fully understand that, but that won't track events for the entire life of the object will it? They have to be fired by $events? Unless i'm missing something...

  15. #15
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by TomB View Post
    Ok, I don't fully understand that, but that won't track events for the entire life of the object will it? They have to be fired by $events? Unless i'm missing something...
    Better example?

    PHP Code:
    class DataChangedEvent implements Event
    {
        public 
    $key;

        function 
    __construct($key)
        {
            
    $this->key $key;
        }
    }

    interface 
    Cache
    {
        function 
    delete(DataChangedEvent $event);
    }

    class 
    ApcCache implements Cache
    {
        function 
    delete(DataChangedEvent $event) { apc_delete($event->key); }
    }

    class 
    Data
    {
        protected 
    $events;

        function 
    __construct()
        {
            
    $this->events = new Events();
        }

        function 
    getEvents() { return $this->events; }

        function 
    change($key$value)
        {
            
    $this->events->fire(new DataChangedEvent($key));
        }
    }

    $container = new Container();

    $container->registerShared('Cache''ApcCache');

    $data = new Data();
    $data->getEvents()->connectIndirect('DataChangedEvent'$container->getClosure('Cache'), 'delete');

    $data->change('a'1); 

  16. #16
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    996
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    That makes more sense, but it introduces the Events dependency in the Data object, which is what oddz was trying to avoid.

    My method, while messy, allows you to add a hook to an arbitrary method in an arbitrary object, without changing the class definition.

    Often it is better to have a list of pre-defined hookable events... however... that introduces the dependency which isn't ideal imho, for example my method would allow for an incredibly powerful plugin system.

    edit: My method would be a lot nicer if you could move classes between namespaces (or renamed) at runtime... allowing for classes which have been hooked to be replaced in the global namespace with the wrapper class and moved on the fly to the other namespace.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •