# Platform Infrastructure Hooks

https://dev.wisecp.com/es/platform-infrastructure-hooks

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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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 PHP

```php
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.

## Related Articles

- [How Hooks Work](https://dev.wisecp.com/en/how-hooks-work)
- [Hook Domains](https://dev.wisecp.com/en/hook-domains)
- [Writing a Hook Listener](https://dev.wisecp.com/en/writing-a-hook-listener)
