Platform Infrastructure Hooks

1 views Markdown

The eight hooks under the request: outgoing calls, accepted addresses, routing, template variables, API addresses and product types.

Overview

These hooks sit beneath a request: before an address resolves, before a template is shown, before a call leaves for the outside. All are hot paths, running on every request.

Two deserve care. The pre-match stands ahead of the real pages of the install, so an unproven claim shadows them. And template variables is not passed by link: the array you return stands in for all of them.

Reference

Changing an outgoing request

filterhttp.request
Utility::HttpRequest passed by link

Runs before the system sends a request outward. Every outgoing call passes here: server modules, registrars, payment gateways.

Parameters 1
$optionsarrayby linkThe request settings: address, method, body, headers, timeouts, certificate check. Raising a timeout can slow the whole system: this hook sees every call, not one.
Return 1
voidThe return is ignored; you write over the settings.
Listener
Hook::add('filter:http.request', 10, function (&$options) {
    // Touch only your own address: this hook sees EVERY outgoing call.
    if (!str_contains($options['url'] ?? '', 'api.acme.test')) return;

    $options['headers'][] = 'X-Acme-Tenant: ' . Acme::tenant();
});

Widening the accepted addresses

filterhttp.trusted_hosts
Kernel add, do not reset

Runs while the address of an incoming request is checked. The list arrives holding the address the licence is locked to; you add yours.

Parameters 2
$hostsarrayby linkThe accepted addresses. Do not reset the list: drop the locked address and the install becomes unreachable at its own address.
$contextarrayby linkInformation: the raw address the request came from and the one the licence is locked to.
Return 1
voidThe return is ignored; you add to the list.
Listener
Hook::add('filter:http.trusted_hosts', 10, function (&$hosts, &$context) {
    // Add, do not reset: the locked address must stay in the list.
    $hosts[] = 'panel.acme.test';
});

Registering an address of your own

registerroutes
Router through the object

Runs while the address table is built. This is the right way to open a page of your own: register the address rather than grabbing it in the match hooks.

Parameters 1
$routerobjectThe router. Being an object, calling a method on it is enough; nothing is registered by returning.
Return 1
voidThe return is ignored. Registration happens as a side effect of the call.
Listener
Hook::add('register:routes', 10, function ($router) {
    // Registration happens through the call, not the return.
    $router->add('acme-status', 'acme/status', 'AcmeStatusController');
});

Claiming an address ahead of the patterns

filterrouting.prematch
Router ahead of the patterns

Runs as an address starts to resolve, ahead of the registered patterns. Answering here means stepping in front of the real pages of the install.

Parameters 2
$urlstringThe address being resolved, with the language prefix already split off.
$routesarrayEvery registered address definition, there to look at.
Return 1
array|nullThe first listener returning an array with at least a controller key wins. ? The claim must be proven: answering "this might be mine" shadows the real pages of the install. Return null unless your own record stands behind it. For a permanent page the right place is the address registration, not this.
Listener
Hook::add('filter:routing.prematch', 10, function ($url, $routes) {
    // Claim with proof: return null unless you hold a record for it.
    if (!Acme::ownsSlug($url)) return null;

    return ['key' => 'acme-page', 'controller' => 'AcmePageController', 'params' => [$url]];
});

Catching an address nothing matched

filterrouting.match
Router when no pattern held

Runs when none of the registered patterns held. Unlike the pre-match, claiming here is safe: there is no real page left to shadow.

Parameters 2
$urlstringThe address that did not resolve.
$routesarrayThe registered address definitions.
Return 1
array|nullThe first listener returning an array with a controller key wins. With no answer the address falls to the not-found flow.
Listener
Hook::add('filter:routing.match', 10, function ($url, $routes) {
    // No pattern held this address, so claiming it is safe.
    $page = Acme::findVanity($url);
    if (!$page) return null;

    return ['key' => 'acme-vanity', 'controller' => 'AcmeVanityController', 'params' => [$page]];
});

Changing the variables a template receives

filtertemplate.variables
View::render return the whole array

Runs before a template is shown. This is how you carry one value onto every page.

Parameters 2
$template_pathstringThe full path of the template being drawn; use it to tell which page you are on.
$dataarrayEvery variable about to reach the template.
Return 1
array|null? It is not passed by link: the array you return replaces every variable, it is not merged. Return an array of your own without building on the incoming one and the whole page loses its data, leaving a blank screen. To add something, add to what arrived and return all of it. With several listeners the last return wins.
Listener
Hook::add('filter:template.variables', 10, function ($template_path, $data) {
    // ADD to what arrived and return all of it: this replaces, it does not merge.
    $data['acme_banner'] = Acme::banner();

    return $data;
});

Opening API addresses of your own

filterapi.routes
API Kernel three separate tables

Runs while the API address table is built. This is how a module opens its own endpoints without touching the core.

Parameters 2
$routesarrayby linkThe address table. Each row carries the method, pattern, group, action and access settings. Add, change or remove freely.
$audiencestringby linkWhich table: admin, client or module. The hook fires separately for all three: add without checking and your endpoint lands in every table.
Return 1
voidThe return is ignored; you write over the table.
Listener
Hook::add('filter:api.routes', 10, function (&$routes, &$audience) {
    // The hook fires for all three tables: check the branch.
    if ($audience !== 'admin') return;

    $routes[] = ['GET', 'acme/status', 'Module:Addons/Acme', 'status'];
});

Introducing a new product type

registerproduct_types
Products returns definitions

Runs while the product type list is built. Add your own type to the core list here.

Parameters 0
It takes no parameters.
Return 1
array|nullYou return a definition map; returns are merged into the core list. Each definition carries a title, a description and an icon. Empty returns are ignored.
Listener
Hook::add('register:product_types', 10, function () {
    return ['acme-vps' => [
        'title'       => 'Acme VPS',
        'description' => 'A virtual server on Acme',
        'icon'        => 'bi bi-hdd-rack fs-5',
    ]];
});

Pitfalls

Template variables replace, they do not merge

This filter is not passed by link. The array you return stands in for every variable. Return one of your own without building on what arrived and the page loses its data, leaving a blank screen. The fix: add to the incoming array and return all of it.

An unproven claim in the pre-match shadows real pages

The pre-match sits ahead of the registered patterns. Answering "this might be mine" makes the real pages of the install unreachable. Match only where your own record stands behind the address; for a permanent page the right place is the address registration.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.