Pages

Categories

Writing a Pluggable System in PHP

Not so long back I decided to write a Mailing Library for PHP4 and 5, similar to phpMailer, except that mine would interface a little differently and would be fully pluggable (therefore appealingly flexible).

So then it struck me, just how do would you go about handling plugins in such a manner that both the main library and the plugins have a good understanding of each other and can relfect changes in each other without hard-coding a limited number of specific entry points?

The things I was looking to acheive were:

  1. Support for multiple plugins
  2. Each plugin can communicate with the parent object equally
  3. The parent object can trigger events and set actions off in the plugins
  4. The plugin can choose to implement an event handler or not

Which brought me to thoughts of JavaScript and it’s event handlers such as onmouseover, onkeyup etc…

If I could have my plugins support some predefined events I’d be halfway there. So I, before ever thinking of it’s implementation, jotted down all of the event handlers I could imagine a plugin ever needing in the context of this library.

Now came the time to suss out how to implement them. What you’re about to see is almost like the observer pattern although handled a little more loosely.

Let’s take a really simple system that accepts a word, and then print’s a it to the page. Pointless ok, but easy to follow.

Well, there can’t be many event handlers we could possibly use here surely?

onLoad
onUnload
onSpeak
onBeforeSpeak

Sound fair? Ok here’s how we load in the plugins.

<?php

interface iPlugin
{
   /* needs public $name; */
    public function loadBaseObject(&$base);
    /* Optional methods to implement
     public function onLoad();
     public function onUnload();
     public function onSpeak();
     public function onBeforeSpeak();
     */

}

class myPlugin implements iPlugin
{
    private $base;
    public $id = ‘myPlugin’;
   
    public function loadBaseObject(&$base)
    {
        $this->base =& $base;
    }

    public function onLoad()
    {
        echo "myPlugin now loaded…<br />";
    }

    public function onBeforeSpeak()
    {
        echo "Base was about to say $this->base->word<br />";
        $this->base->word = "myPlugin rocks!";
        echo "But I’ve just changed it… I’ll let Base carry on now…<br />";
    }

    public function onSpeak()
    {
        echo "Did someone just say something?<br />";
    }

    public function onUnload()
    {
        echo "myPlugin is now closing.  Bye!<br />";
    }
}

class baseClass
{
    public $word;
    private $plugins = array();
   
    public function __construct()
    {
        //
    }

    private function triggerEvent($eventHandler)
    {
        //If we explicitly tell it which object do this in
        if (is_array($eventHandler))
        {
            $event = $eventHandler[0];
            $obj =& $eventHandler[1];
            if (method_exists($obj, $event)) $obj->$event();
        }
        else //Otherwise just run it in all loaded plugins
        {
            foreach ($this->plugins as $name => $obj)
            {
                if (method_exists($obj, $eventHandler)) $this->plugins[$name]->$eventHandler();
            }
        }
    }

    public function loadPlugin(iPlugin &$plugin)
    {
        $this->plugins[$plugin->id] =& $plugin;
        $plugin->loadBaseObject($this);
        $this->triggerEvent(array(‘onLoad’, &$plugin));
    }

    public function removePlugin($name)
    {
        $this->tiggerEvent(array(‘onUnload’, &$this->plugins[$name]));
        unset($this->plugins[$name]);
    }

    public function speak($word)
    {
        $this->word = $word;
        $this->tiggerEvent(‘onBeforeSpeak’);
        echo "$this->word<br />";
        $this->triggerEvent(‘onSpeak’);
    }
}
?>

It looks a little hectic but if we break it down we’ll see that really it’s not.

Our plugin adhere’s to a hideously small API, that API enforces it to have a methods to hold an instance of it’s parent. The parent object incidentally holds an instance of itself so we have a cyclic link here - which is actually pretty useful.

Now, the plugin also contains an id which the parent object uses to identify it in the plugin container (the array). The rest of the plugin itself is purely optional.

The parent class “baseClass” contains a method for loading in that plugin, it also contains one for removing it… pretty standard stuff.

Now we said that the system was a simple system that prints given words to the page, so we add our speak() method to do this job.

Hoefully that much makes sense :)

All we have left to do now is to look at how the plugin responds to events. It should be pretty clear from looking at the code but in any case I’ll explain.

The parent class “baseClass” contains a vital method which I’ve called “triggerEvent()”. This, oddly enough, triggers events in our plugins. The first thing it does is checks to see if we have passed it a plugin object to use, otherwsie it enters a loop and acts upon all of our plugins. Next it checks to see if the method even exists, and if it does it executes it.. simple!

Let’s see this in action here:

$base = new baseClass;
$base->loadPlugin(new myPlugin);
$base->speak(‘Hello World!’);
$base->removePlugin(‘myPlugin’);
$base->speak(‘Hello Moon!’);

/* Outputs:

myPlugin now loaded…
Base was about to say Hello World!
But I’ve just changed it… I’ll let Base carry on now…
myPlugin rocks!
Did someone just say something?
myPlugin is now closing.  Bye!
Hello Moon!

*/

Pretty neat :)

Of course there are a few things to consider with a design like this.

Your plugin event handlers need access to items in the parent object. This is fine because we have a cyclic link between the two. All that you need to do is to push important variables into public properties before actually doing anything with them. This gives your plugins the opportunity to interact with them, just as I push $word into a public propert in the example above.

Have a play around with it. If you’re writing a highly pluggable system this method certainly gives you plenty of flexibility.

This entry was posted on Sunday, May 21st, 2006 at 10:26 pm and is filed under PHP. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

5 Responses to “Writing a Pluggable System in PHP”

  • d11wtq [ 29May06]

    If this article interested you, you may also want to read a discussion about it on SitePoint. Some other members have had some interesting ideas.

    http://www.sitepoint.com/forums/showthread.php?t=379440

  • Aaron Saray [ 20Jun07]

    I like your very slim example: but I’d have appreciated an example in a “real life” example. How do you implement these hooks?

    No worries, I’m having issues demonstrating these myself too! haha

  • suzyquq [ 30Sep07]
  • d11wtq [ 10Oct07]

    I really need to write a much more elegant solution to the plugin problem in PHP. There are far better ways to do this ;)

  • Pendol [ 19Feb08]

    Just what I needed.

    I’ve been writing a cms in php5, and today I was thinking how to write a plugin system. I’ve made a few thing, but with this, definitely I’m going to finish soon.

    Thanks for sharing this code.

Leave a Reply







XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>