WordPress-like Hooks and Filters in Laravel

Posted on Updated on Laravel by Gergő D. Nagy

One of the most powerful tools in WordPress is the hook system it uses. It’s a nice way to modify values from anywhere. It adds huge flexibility to any WordPress site. Let’s take a look, how to implement this in Laravel.

The Basic Concept

The basic idea is really the same as the WordPress hook system. There are some values somewhere in the code, and we want to modify them easily from outside, without modifying any code. Also, setting up a priority would be nice, since we have more control over the modifications.

Events vs. Hooks

It would be really nice if we could use Laravel’s event system to implement this functionality, but this is not really ideal for that. Because of this, we’ll implement a small hook repository that will store and order the registered hooks based on their priority.

The Hook Repository

This repository will be a really simple place to store our hooks and use them whenever we need:

class HookRepository
{
    /**
     * The repository items.
     *
     * @var \Illuminate\Support\Collection
     */
    protected $items;

    /**
     * Create a new repository instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->items = collect();
    }

    /**
     * Dynamically call methods.
     *
     * @param  string  $method
     * @param  array  $arguments
     * @return mixed
     */
    public function __call(string $method, array $arguments)
    {
        return $this->items->{$method}(...$arguments);
    }

    /**
     * Register a new hook callback.
     *
     * @param  string|array  $hook
     * @param  callable  $callback
     * @param  int  $priority
     * @return void
     */
    public function register($hook, callable $callback, int $priority = 10): void
    {
        $this->items->push(compact('hook', 'callback', 'priority'));
    }

    /**
     * Apply the callbacks on the given hook and value.
     *
     * @param  string  $hook
     * @param  array  $arguments
     * @return mixed
     */
    public function apply(string $hook, ...$arguments)
    {
        return $this->items->filter(function ($filter) use ($hook) {
            return !! array_filter((array) $filter['hook'], function ($item) use ($hook) {
                return Str::is($item, $hook);
            });
        })->sortBy('priority')->reduce(function ($value, $filter) use ($arguments) {
            return call_user_func_array($filter['callback'], [$value] + $arguments);
        }, $arguments[0] ?? null);
    }
}

So, we have two methods here, the register and the apply. All the other calls will be forwarded to the collection instance that holds the hooks.

We can register hooks in a service provider for example:

public function boot()
{
    Hook::register('jobs.tags', function ($tags, $job) {
        return array_merge($tags, $job->someCondition() ? ['foo'] : ['bar']);
    });
}
Note, with this approach, we can register hooks with a wildcard or array notation as well. For example: [jobs.tags, posts.tags], or jobs.*.

Applying Hooks

So, where to apply hooks? Basically anywhere you want. The upside of this solution is, you don’t really need a complex value manager for different places. You can use hooks, pass any parameter you want and return with the modified value that will be used later.

public function tags()
{
    return Hook::apply('jobs.tags', ['a', 'b'], $this);
}

We call the apply method and pass the hook and the value first, then any other parameter we may need in the callback.

Summary

The hook system is a nice and slim solution that you can use everywhere in your system. This is very handy especially when you are hooking into the application from a package or another service.

Of course, this example class can be extended with many other methods like flush or remove, but it represents the basic idea well enough.

Need a web developer? Maybe we can help, get in touch!