Posted in ProcessWire 28 Oct 2017 @ 12:48 PM~3 min read

Using hook classes in ProcessWire modules

After shrugging at Jumplinks’ massive module class, I’ve begun writing in a more OOP way, and there are a couple of things that have come in handy in terms of SoC of late. One of them is hook classes, which helps split hook functions out into their own classes in an intuitive way.

In ProcessWire, many module developers have become used to the normal way of doing things, such as including all functionality in a single module class. Why is this? Perhaps, because everyone else is doing it. My thinking is starting to change, and with it comes new ways to make modules for ProcessWire.

Yesterday, I pushed a new package to GitHub and Packagist. Module helpers is a simple trait-based package that adds various helpers to ProcessWire modules. For example, it comes with a simple debugging utility for when you need to dd() like one does in Laravel. Or, it comes with a nice little field builder that makes for neater code.

Hook Classes

Hook classes are simple classes that can be used to delegate a hook’s handler to a class outside of the module scope, but with awareness of the module instance that called it, $event, and wire.

Each hook can be created anywhere in a module’s directory, and it needs to extend an abstract class called BaseHook, which is where the simple magic happens. BaseHook is a self-invoking class that uses protected condition-checking and invocation handlers. This allows our hooks to have pre-defined conditions that can be tied to the event or wire, or nothing at all.

When a hook is constructed, it first determines which module called it and gets the instance of that module. It then imports wire() from the ProcessWire namespace (yes, this means that it’s only compatible with ProcessWire 3.x, and no, I’m not building a bridge class to maintain PW 2.8.x compatibility – sorry about that).

Once that’s done, the hook is self-invoked, at which point its condition is checked. If the condition returns true, then the hook is finally invoked.

Creating a hook class

Here’s a simple example:

<?php

class RenderHook extends BaseHook
{
    /**
     * The page's template should not be admin
     * @return bool
     */
    protected function condition()
    {
        return $this->wire->page->template !== 'admin';
    }

    /**
     * Invoke the hook
     */
    protected function invoke()
    {
        // Get script tags from method in your module.
        // You could also do this via a hook property belonging to a page,
        // if such is configured by your module.
        $scriptTags = $this->module->getScriptTags();

        // Inject script tags into the page's body
        $bodyTag = '</body>';
        $this->event->return = str_ireplace(
            $bodyTag,
            $scriptTags . $bodyTag,
            $this->event->return
        );
    }
}

This is just a basic example, and doesn’t include the namespace declarations I’m using in development (see note below). When published, BaseHook will be made available in the module helper package described above (so, Rockett\Hooks\BaseHook). This means that your module will need to utilise Composer (this doesn’t need to be done in the ProcessWire scope – module local scope is fine, until PW natively supports package installation on module installation).

Using hook classes

Using hook classes is incredibly simple. In your module:

$this->addHookAfter('Page::render', new RenderHook());

It’s really that simple.

Note: On my side, I’m using ProcessWire’s class loader to import my hooks from a namespace.

What’s the purpose of this?

The size of the Jumplinks v1 module class is big enough (over 2000 lines on its own) to make me very unsettled with it. If you’ve seen my module releases of late, you’ll notice that I’m separating concerns into their own classes. These hook classes are part of that process, and, in my mind, it’s a much cleaner way to write module hooks. This helps us adhere to the SoC principal, and keep our code neat and easy to understand.

When is it being released?

Soonish? I’m building this inside a few modules that are currently under construction, and will release it when they’re done. That’s all I can say. ;-)

Loading Comments...