Page 1 of 1

Implementing hooks for modular 'add ons'

Posted: Thu Nov 11, 2004 5:32 pm
by jl
Hi All,
For my pet project, an IRC bot in PHP, I want to be able to allow addons to be made for it, and I want the addons to be able to hook into events that occur in the bot.

The basic structure I'm considering now is this:

1. A module or add on will hook into the main app something like this:

function addon(&$bot) { ... addon functionality here ... }

$hook['eventtype']=EVENT1;
$hook['functionname']='addon' ;

$bot->AddHook($hook) ;

2. $bot will keep lists of all functions that should be called for each event type. E.g. $EVENT1[]='addon' ;

3. When something occurs that could trigger a hook (e.g. a message is received), hooks will be called via arrays of event types.

e.g.
if (EVENTX has occured)
foreach ($EVENT1 as $function) {
$function($this) ;
}


Is this method of variable functions with a by-reference copy of the entire bot the best way to call the external addons?

Also wondering whether I should include the ability for the addons to define the user levels of users that they want to be called for in the $hook[] definition, or let them determine that in their own code (since they'll be able to access $bot->current_user_level or something similar) ?

Any other input on structures for implementing hooks or examples of existing code appreciated.

Posted: Fri Nov 12, 2004 2:40 pm
by timvw
I think you'll end up with some events and event handlers.
this way you only have to plugin eventlisteners (which implement the listener interface).

Code: Select all

interface CEListener 
{
 function isConnect($connected)
}

class someCEListener implements CEListener
{
    function isConnect($connected)
    {
          // do something
    }
}

class Generator 
{
    private CEListeners;

    function addCEListener(CEListener &$listener)
    {
          $this->CEListeners[] =& $listener;
    }

    function main(....)
    {
         // if CE rises
         if (....)
         {
              foreach($CEListeners as $listener)
              {
                  $listener->isConnect(...);
              }
          }
     }
}

In GUI libraries like MFC and QT they use "signals (and slots)". you can read more on that at for example at http://doc.trolltech.com/3.3/signalsandslots.html

Posted: Sat Nov 20, 2004 12:58 pm
by BDKR
From Tim's code, you may want to be careful with this.

Code: Select all

<?php
    function main(....)
    {
         // if CE rises
         if (....)
         {
              foreach($CEListeners as $listener)
              {
                  $listener->isConnect(...);
              }
          }
     }
?>
Based on how this is coded, I can see that Tim is thinking in a PHP5 way, but in PHP4 this foreach construct won't work if the listener is an object. Use this instead...

Code: Select all

<?php
    function main(....)
    {
         // if CE rises
         if (....)
         {
              foreach($CEListeners as $listener => $listenerObject)
              {
                  $CEListeners[$listener]->isConnect(...);
              }
          }
     }

?>
The reason? In P5, foreach returns objects by reference. In P4, it's by copy.

Just some FYI from someone with fresh wounds. :wink:

Posted: Sat Nov 20, 2004 3:55 pm
by McGruff
The Observer pattern would be worth a look (there's also a lot of other pattern info to be found in each of the following links).

http://www.horde.org/papers/kongress200 ... _patterns/
http://www.phppatterns.com/index.php/article/archive/1/
http://www.javaworld.com/channel_conten ... ndex.shtml

With Observer, rather than storing a list of functions, the observable (Bot) stores a list of objects all having the same "listen" interface:

Code: Select all

class Bot
{
    function registerObserver(&$observer)
    {
        $this->_observers[] =& $observer;
    }
    function notify()
    {
        foreach(array_keys($this->_observers) as $key)
        {
            $this->_observers[$key]->listen($this->event);
        }
    }
    etc..
}
class Foo // an observer
{
    function listen(&$event)
    {
        // do something with $event
    }
    etc..
}

// Or, instead, load them by factory:

class Bot
{
    function Bot()
    {
        $this->_observers[] =& $this->_foo();
        etc..
    }
    function &_foo()
    {
        include(..path to Foo class..);
        return new Foo;
    }
    etc..
}
If your "hooks" are objects you have better encapsulation. With hooks as functions, Bot may have to know some details about how to call them (unless each hook corresponds to a single, simple function). As objects, Bot can simply pass on the event and doesn't need to know anything about them.

If only one handler should ever be active, passing $event along a Chain of Responsibility is a better fit. That might be more what you are looking for. Create a single event handler class for each event type which encapsulates all the functions for that type - and possibly subdivide into factory objects, if needed.

http://www.javaworld.com/javaworld/jw-0 ... terns.html http://www.javaworld.com/javaworld/jw-0 ... chain.html