There is the Observer Pattern which comes to mind. Plugins will register themselves and will get notifications when the hook is invoked.
Another thing that comes to mind are callbacks in PHP. And there was a similar question already with an answer that came to mind. It shows hooks based on callbacks.
The Observer Pattern runs a bit short because with hooks you often want to provide things like arguments and a return value. The linked answer which uses callbacks does not have this either, so I wrote a little Hooks
example class that provides named hooks/events to registered callbacks, and a way to register your own classes, e.g. a plugin.
The idea is pretty basic:
- A hook has a name and zero or more callbacks attached.
- All hooks are managed in a
Hooks
class.
- The main code invokes hooks by calling a function on the
Hooks
object.
- Plugins (and other classes) can register their own callbacks, which is done with the help of the
Registerable
interface.
Some example code with one plugin and two hooks:
<?php
Namespace Addon;
class Hooks
{
private $hooks = array();
private $arguments;
private $name;
private $return;
public function __call($name, array $arguments)
{
$name = (string) $name;
$this->name = $name;
$this->arguments = $arguments;
$this->return = NULL;
foreach($this->getHooks($name) as $hook)
{
$this->return = call_user_func($hook, $this);
}
return $this->return;
}
public function getHooks($name)
{
return isset($this->hooks[$name]) ? $this->hooks[$name] : array();
}
public function getArguments()
{
return $this->arguments;
}
public function getName()
{
return $this->name;
}
public function getReturn()
{
return $this->return;
}
public function setReturn($return)
{
$this->return = $return;
}
public function attach($name, $callback)
{
$this->hooks[(string) $name][] = $callback;
}
public function register(Registerable $plugin)
{
$plugin->register($this);
}
}
interface Registerable
{
public function register(Hooks $hooks);
}
class MyPlugin implements Registerable
{
public function register(Hooks $hooks)
{
$hooks->attach('postPublished', array($this, 'postPublished'));
$hooks->attach('postDisplayFilter', array($this, 'filterToUpper'));
}
public function postPublished()
{
echo "MyPlugin: postPublished.\n";
}
public function filterToUpper(Hooks $context)
{
list($post) = $context->getArguments();
return strtoupper($post);
}
}
$hooks = new Hooks();
$plugin = new MyPlugin();
$hooks->register($plugin);
$hooks->postPublished();
echo $hooks->postDisplayFilter("Some post text\n");
I've done it this way to prevent that each Plugin must have a concrete base class only because it wants to make use of hooks. Additionally everything can register hooks, the only thing needed is a callback. For example an anonymous function:
$hooks->attach('hookName', function() {echo "Hook was called\n";});
You can however create yourself a plugin base class, that for example implements the register
function and will automatically register functions that have a certain docblock tag or the name of a function
class MyNewPlugin extends PluginSuper
{
/**
* @hook postPublished
*/
public function justAnotherFunction() {}
public hookPostPublished() {}
}
The superclass can make use of Reflection to add the hooks on runtime. However reflection can slow things down and might make things harder to debug.