3. Understanding and using registration hooks

Portal development makes heavy use of a concept called registration hooks. Say you need to create a new view, or add an item to the menu, or insert some javascript code into the header? The answer is to do so via registration hooks. There are a handful of default hooks you can make use of, but it’s really simple to create your own custom hooks to do with whatever you like.

Portal Registration Hook Reference

Let’s look at an example from the Homepage module:

__register('VIEW', "/", ['callback' => '\Papal\View\Homepage::display']);

Note that we’re calling the __register() function with three arguments. The first is the registration hook, in this case we’re registering a new view. The second is the trigger to be registered, in this case we want to the view to be displayed at / or the base URL for the application.

The third argument is where the real magic happens. Different registration hooks will have different usage requirements, but this is basically an array of data that tells the hook how to operate. In the case of the VIEW hook, the only thing necessary is a callback which is the function that will output the HTML for the view.

Let’s take a look at another example, this time from /templates/Sidebar.php in the portal core code:

__register('FORM', 'ajaxDisplayMenu', ['callback' => '\Papal\Menu::setDisplayMenuCurrent', 'args' => ['value']]);

In this case we’re registering a form handler with the ajaxDisplayMenu trigger. How this works is actually quite simple: Whenever a POST request is submitted to the application that contains a value for the ajaxDisplayMenu key, it executes the specified callback with the specified arguments using the appropriate POST values. The above would functionally do the same as the following:

if (isset($_POST['ajaxDisplayMenu'])) {
    \Papal\Menu::setDisplayMenuCurrent($_POST['value']);
}

So why use this paradigm instead of something more conventional? The answer is simple: registration hooks make it very easy to build extensible features. Because of how they’re implemented any developer can, for example, register a new menu item from any PHP file that gets evaluated in their module.

For another example, say a dev named Jim uses registration hooks to build a table for displaying CDR data. Later on another dev named George could then add columns to Jim’s table to display additional data that wasn’t part of the original scope. And George can do it barely having to look at Jim’s code, much less modify it in any capacity.

How it Works

So let’s get under the hood and take a look at how the __register() function works so you can learn how to better make use of registration hooks and even implement your own. In the portal’s index.php the function is declared as follows:

function __register($hook, $trigger, $args = true) {
    $GLOBALS["_PAPAL_{$hook}"][$trigger] = $args;
}

As you can see, the __register() function is simply a clean way of leveraging the PHP superglobal variable called $GLOBALS. All of the code that actually works with registration hooks utilizes $GLOBALS directly. As an example, here is part of the \Papal\Form::handle() function:

foreach ($GLOBALS["_PAPAL_FORM"] as $trigger => $form) {
    if (isset($_POST[$trigger]) || isset($_FILES[$trigger])) {
        ...
    }
}

Custom Registration Hooks

So how would you go about implementing a custom trigger? Simple, just write a function that uses the registration hook paradigm and call it from somewhere. Here is an example:

namespace Papal;
class LogRegister {
  public static function logIt() {
    foreach ($GLOBALS["_PAPAL_LOG"] as $trigger => $data) {
      Log::write("{$trigger} -- {$data['content']}");
    }
  }
}

Here would be the registration hook:

__register('LOG', 'test', ['content' => 'Success!']);

And the registration hooks would be processed anywhere in your module where you call the \Papal\LogRegister::logIt() function.

Overriding Hooks

It’s possible to override registration hooks in some cases. Say for instance the author of the Homepage module want a particular menu item to display on the home page. Remember our example from above where we saw that the Homepage module uses a VIEW registration hook to display the result of the \Papal\View\Homepage::display() function? Let’s say the author doesn’t want to display the item for /infrastructure from the Infrastructure module. All the author has to do is to place the following within the \Papal\View\Homepage::display() function before the HTML is output:

__register('MENU', "/infrastructure", []);

It’s advisable to not try and override registration hooks from within the global namespace. Rather you should only ever attempt a registration hook override from within your function, which should be in a properly namespaced class.

Conventions

The correct convention for assembling and using a registration hook is to pass an uppercase string for the $hook value, a non-uppercase or mixed-case string for the $trigger value and an associative array for the $args value, even if you only require a single value from $args. You should always pass the value for $hook explicitly though it’s perfectly acceptable to use the result of a function for $trigger and/or $args. Just be sure the function is loaded and available for your registration hook.

If you implement custom registration hooks within your module, please document them in the README.md file so that other developers can easily know that they’re available and how to make use of them.

Content Hooks

In various places through the portal’s templates and views, you may encounter functions similar to the following:

\Papal\Hooks::insert('SIDEBAR_JS_BEFORE');

This is what is referred to as a content hook. Basically a content hook is a simple way to add content to areas of a page. They’re simple to use and they’re even in use within the portal core code itself.

So how do they work? Simple, just call make a registration containing three arguments: a callback, an optional array of args, and an optional priority. The callback will obviously be a string containing a namespaced function name. The args will obviously be the arguments passed to that function. The priority is the order in which the content displays if there are multiple registrations to the same content hook.

As an example, let’s start by defining a function:

namespace Papal;
class HookTest {
  public static function hookDemo() {
    ?>
      <!-- TESTING -->
    <?php
  }
}

Now let’s assemble the registration:

__register('SIDEBAR_JS_BEFORE', 'hookdemo', ['callback' => '\Papal\HookTest::hookDemo']);

The result will be the comment output by the function appearing in the page where the SIDEBAR_JS_BEFORE hook is called using the \Papal\Hooks::insert() function mentioned above.

Now that you have an understanding of how registration hooks work, let’s put them to use and assemble a view:

Assembling views.