RSS Feed

Peter Goodman's blog about PHP, Parsing Theory, C++, Functional Programming, Applications,

Simple Hooks System

So I decided I would make a simple hooks system, and although there is a lot of code to it, it is still VERY simple. At first, the goal was to just be able to tell the class to hook functions onto specific hook ids. This is a stupidly simple task so I was on the fence about posting about it. Then Geoffrey said I should make a function to apply a function to all of the hooks, or so I thought...

Well I went on this thought and made exactly what I thought Geoffrey wanted (which wasn't the case, oh well) and made a global hook register function. This is also very simple, although I had to make it really redundant because I wanted to maintain the order of all hooks added.

<?php

//-------------------------------------------------
// Class that deals with applying hooks to anywhere.
// (c) Copyright 2006 Peter Goodman, All Rights Reserved
//
// Syntax:
//
// Plug::apply('func1', 'func2', ...)->to('hook1', 'hook2', ...); // register func(s) to hook(s)
// Plug::applyToAll('func1', 'func2', ...); // register func(s) to all hooks
// Plug::send($arg1, $arg2, ...)->to('hook1', 'hook2', ...); // call hook(s)
//
//-------------------------------------------------

class Plug
{
    //-------------------------------------------------
    // Is this hook being registered or called?
    //-------------------------------------------------

    protected $register = FALSE;

    //-------------------------------------------------
    // An array of hook ids that have been used.
    //-------------------------------------------------

    static protected $ids = array();

    //-------------------------------------------------
    // An array of the function names / class & function
    // names of the hooks that are being applied.
    //-------------------------------------------------

    static protected $hooks = array();

    //-------------------------------------------------
    // An array of hooks that are applied to all hooks when
    // executed.
    //-------------------------------------------------

    static protected $globals = array();

    //-------------------------------------------------
    // Arguments passed to Plug::send() to pass to the hooks
    // being called. After Plug::send()->to() is called,
    // this array is reset.
    //-------------------------------------------------

    static protected $args = array();

    //-------------------------------------------------
    // Function names passed to Plug::register() to pass
    // to the hooks being registered. After Plug::register()->to()
    // is called, this array is reset.
    //-------------------------------------------------

    static protected $funcs = array();

    //-------------------------------------------------
    // Constructor, this is only called if we are calling
    // or registering a hook.
    //-------------------------------------------------

    public function __construct($register = FALSE)
    {
        $this->register = (bool)$register;
    }

    //-------------------------------------------------
    // Register a global hook, this is a function that is
    // applied to all hooks, public method.
    //-------------------------------------------------

    static public function applyToAll()
    {
        $funcs = func_get_args();

        foreach($funcs as $str)
        {
            //-------------------------------------------------
            // This global doesn't exist yet so let's register it.
            //-------------------------------------------------

            if(!in_array($str, self::$globals))
            {
                self::$globals[] = $str;
            }

            //-------------------------------------------------
            // This is a new global hook so we need to manually
            // go through all of the hook ids and add this function.
            // This would seem counterintuitive but the whole point
            // is to maintain order.
            //-------------------------------------------------

            foreach(self::$ids as $id)
            {
                self::registerHook($id, $str);
            }
        }
    }

    //-------------------------------------------------
    // Register a hook, this applies a function to a hook
    // id for both single and global hooks, private method.
    //-------------------------------------------------

    static protected function registerHook($id, $str)
    {   
        //-------------------------------------------------
        // If this hook id has not been used yet, then lets
        // register it and add all of our global hooks, in the
        // order that they were added.
        //-------------------------------------------------

        if(!in_array($id, self::$ids))
        {
            self::$ids[] = $id;

            foreach(self::$globals as $func)
            {
                self::registerHook($id, $func);
            }
        }

        //-------------------------------------------------
        // Now, let's go and see if we need to add a function/
        // class method as the hook.
        //-------------------------------------------------

        if($str != '')
        {
            if(strpos($str, '::') !== FALSE)
            {
                list($class, $method) = explode('::', $str);

                if(class_exists($class))
                {
                    self::$hooks[$id][] = array($class, $method);
                }
            }
            else
            {
                if(function_exists($str))
                {
                    self::$hooks[$id][] = array($str);
                }
            }
        }
    }

    //-------------------------------------------------
    // Set the arguments to send to a hook.
    //-------------------------------------------------

    static public function send()
    {
        self::$args = func_get_args();
        return new self(FALSE);
    }

    //-------------------------------------------------
    // Set the function to register a hook(s) to.
    //-------------------------------------------------

    static public function apply()
    {
        self::$funcs = func_get_args();
        return new self(TRUE);
    }

    //-------------------------------------------------
    // Call/register a hook / multiple hooks at the same time.
    //-------------------------------------------------

    public function to()
    {
        $args = func_get_args();

        //-------------------------------------------------
        // Register the hooks.
        //-------------------------------------------------

        if($this->register)
        {
            foreach($args as $id)
            {
                foreach(self::$funcs as $func)
                {
                    self::registerHook($id, $func);
                }
            }

            self::$funcs = array();
        }

        //-------------------------------------------------
        // Call the hooks.
        //-------------------------------------------------

        else
        {
            foreach($args as $id)
            {
                self::callHooks($id, self::$args, FALSE);
            }

            self::$args = array();
        }
    }

    //-------------------------------------------------
    // Call all of the hooks of a specific id. $keep_going
    // is to stop possible infinite loops.
    //-------------------------------------------------

    static protected function callHooks($id, $args, $keep_going = FALSE)
    {
        $call = FALSE;

        if(isset(self::$hooks[$id]) && !empty(self::$hooks[$id]))
        {   
            //-------------------------------------------------
            // Go through this functions hooks and execute them.
            //-------------------------------------------------

            foreach(self::$hooks[$id] as $hook)
            {
                if(isset($hook[1]))
                {               
                    $obj = new $hook[0];

                    if(method_exists($obj, $hook[1]))
                    {
                        $call = array(&$obj, $hook[1]);
                    }
                }
                else
                {
                    $call = $hook[0];
                }

                //-------------------------------------------------
                // Call the hook.
                //-------------------------------------------------

                if($call)
                {
                    call_user_func_array($call, $args);
                }
            }
        }
        else
        {
            //-------------------------------------------------
            // If there are global hooks but no single custom hooks
            // have been applied to this hook yet, then add all of the
            // global hooks and re-call this function.
            //-------------------------------------------------

            if(!$keep_going && !empty(self::$globals))
            {
                self::registerHook($id, '');
                self::callHooks($id, $args, TRUE);
            }
        }
    }
}

Here's an example usage of the Plug (hooks) class.

lt;?php

//-------------------------------------------------
// Whatever, a random useless class.
//-------------------------------------------------

class Whatever
{
    //-------------------------------------------------
    // A random function that calls some hooks. This function
    // doesn't need to be in a class but whatever.
    //-------------------------------------------------

    public function moo()
    {   
        Plug::send()->to('before_moo');
        echo 'hi';
        Plug::send('after')->to('after_moo');
    }

    //-------------------------------------------------
    // This is a hooked function.
    //-------------------------------------------------

    public function hooked($arg)
    {
        echo '<br />This gets called '. $arg .'.';
    }
}

//-------------------------------------------------
// This is a hooked function.
//-------------------------------------------------

function test()
{
    echo 'This gets called before.<br />';
}

//-------------------------------------------------
// Register the hooks.
//-------------------------------------------------

Plug::apply('test')->to('before_moo');
Plug::apply('Whatever::hooked')->to('after_moo');
Plug::applyToAll('some_func');

//-------------------------------------------------
// Call our function which executes the hooks.
//-------------------------------------------------

$whatever = new Whatever;
$whatever->moo();

And here is the output...

This gets called before.
hi
This gets called after.

Well that's it, tell me what you think. For future versions, I was thinking of taking the main work out of the overloaded Plug::to() function and putting it in the Plug::__destruct() so that I can add in extra functionality beyond the Plug::to() function using chainable methods.


Comments


Comment