A Practical Application of Hooks

Ryan Thompson Tips & Tricks


PyroCMS implements something called hooks. Hooks let you extend many objects from the outside as to not alter core. Closed for change - open to extension.

Generally my use of hooks thus far has been premeditated. For example the relationship and multiple field type use hooks to add methods to base models that return lookup tables for those models when used in a relationship. Being a hook - defined outside - you can easily define the hook method on your model and ship your addons with tighter built-in integration for things like these field types. They anticipate the use of the hook.

That's all fine and dandy but yesterday I used a hook in a way that really fired me up.

Pre-sale license holders have been loving on a Forms module and frankly I don't care for it. I made it in a hurry and we use it constantly but it's not what it should be and is due for a rewrite. So when we had a few very unique use cases (ok.. moderately unique to somewhat common I would imagine) come through I was not particularly looking forward to hacking this addon into doing what we needed - without rewriting it and taking the time to do things right.

The problem was this - per form on this car dealership website we needed to send lead forms to not only their CRM but also the appropriate sales staff. However, notifications in the forms module only allow a static set of emails and worse yet the mailer uses the notification outside of the extension layer which was poorly implemented. So no way even with extensions to create dynamic to addresses per form.

I knew hooks could probably help me so I started looking for the hook check in the EloquentModel and low and behold I found it: https://github.com/anomalylabs/streams-platform/blob/1.3/src/Model/EloquentModel.php#L610

I put it there knowing something like this might come up but I'm not quite out of the water yet I thought. __get is called when missing attributes are requested. However this was a field value I needed to intercept it even has an interface method guaranteeing it's access.. And that's where I got it - the interface methods just return $this->you_field_slug and that maps through __get which eventually looks runs through Eloquent's getAttribute like a normal model and entry models from there use getAttributuValue when a field slug comes through. So the hook will be run for this property since it also goes through the __get method.

All I had to do was bind a hook named after the notification_send_to field slug:

<?php namespace App\Providers;

use Anomaly\FormsModule\Form\Contract\FormInterface;
use Anomaly\FormsModule\Form\FormModel;
use Anomaly\Streams\Platform\Event\Ready;
use Anomaly\Streams\Platform\Traits\Hookable;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\ServiceProvider;

class HookServiceProviders extends ServiceProvider
{

    public function register()
    {
        $this->app->make(Dispatcher::class)->listen(
            Ready::class,
            function () {

                /* @var FormModel|Hookable $target */
                $target = app(FormModel::class);

                $target->bind(
                    'get_notification_send_to',
                    function () {

                        /* @var FormInterface $this */
                        if (isset($_POST['notification_send_to'])) {
                            return $_POST['notification_send_to'];
                        }

                        return $this->getFieldValue('notification_send_to');
                    }
                );
            }
        );
    }
}

Typically this wouldn't be so involved but I have been trying to test working within Pyro using mostly Laravel techniques. Adding this to an addon service provider would have been obvious. One thing I did have to do was fire it when the system was ready since the generated entry models are not yet loaded by the time the base providers are registered / booted.

Next all I had to do was include some hidden fields to control the value for this hook:

<input type="hidden" name="notification_send_to[]" value="[email protected]">
<input type="hidden" name="notification_send_to[]" value="[email protected]">

Now when the mailer sends the message and uses the notification_send_to field it will call the interface method:

$message->to($form->getNotificationSendTo());

Which will simply return the field attribute:

public function getNotificationSendTo()
{
    return $this->notification_send_to;
}

Which runs through __get and hooks are first in line to provide a return.

I love it when a plan comes together.

- Ryan