Common Hook Scenarios

5 vues Markdown

Six common jobs under six different contracts: catching an event, blocking, adding to a screen, changing a value, carrying a variable, registering a route.

Overview

The six scenarios below cover all five families and all six behave differently. Two have you change a value by reference and one has you return it. Two have you touch an object handed to you. One has you return text and cut the action short.

The spread is deliberate. The shortest way to show that a hook's expectations do not follow from its family is two different contracts inside one family.

Prerequisites

The Scenarios

1. Tell the outside when an order is placed

This runs the moment an order is placed. The invoice is still unpaid here: a zero-total order, or one settled from the balance, closes later in the same request. Use the payment hook where you want an order that is paid for.

coremio/hooks/acme-crm.php
Hook::add('action:order.checkout_completed', 10,
    function ($order_id, $invoice_id, $pmethod, $member) {
        Crm::orderPlaced($order_id, [
            'invoice' => $invoice_id,          // not paid yet
            'method'  => $pmethod,             // 'Free' on a zero-total order
            'email'   => $member['email'] ?? '',
        ]);
    });

2. Block a sign-in

This runs after the credentials check: the password is already right and you stop the sign-in for some other reason. The hook takes plain text as well as the ['message' => …] form, and an empty return lets the sign-in carry on.

coremio/hooks/acme-guard.php
Hook::add('gate:user.login', 10, function ($user_id, $check, $ip) {
    if (!OfficeNetwork::covers($ip))
        return 'The panel is reachable from the office network only.';
    return null;
});

3. Add a tab to the customer detail

This is a screen hook, and yet you return no HTML: you are handed a tab object and you add to it. The return is not read. Most hooks in this family want HTML. This one wanting an object is the clearest case of a contract you cannot read off the family.

coremio/hooks/acme-tab.php
Hook::add('ui:admin.client_detail.tabs', 10, function ($tab, $user, $user_id) {
    $tab->add('acme', 'Acme Records', AcmePanel::render($user_id));
});

4. Change an order total

The totals array arrives by reference, and the total key is the amount that goes on the order. Touch the tax keys and the invoice lines follow them.

coremio/hooks/acme-pricing.php
Hook::add('filter:order.total', 10, function (&$tax_calc, $subtotal, $total_discount) {
    if ($subtotal >= 1000) $tax_calc['total'] = round($tax_calc['total'] * 0.95, 2);
});

5. Carry a variable into every template

This one does not work by reference: you return the changed array. What you return is not merged with the existing variables, it replaces them. So build on the array you were given and return all of it.

coremio/hooks/acme-vars.php
Hook::add('filter:template.variables', 10, function ($template_path, $data) {
    $data['acme_banner'] = AcmeBanner::current();   // built on what we were given
    return $data;                                   // ALL of it returned
});

6. Register a new route

You are handed the router object and you register on it; the return is not read. This is how modules open pages of their own.

coremio/modules/Addons/Acme/hooks.php
Hook::add('register:routes', 10, function ($router) {
    $router->add('acme-report', 'acme/report/(?)', 'Acme:report');
});

Example

All six can sit in one file. An add-on's whole set of bindings usually lives in a single hooks.php, with every line under a different contract.

coremio/modules/Addons/Acme/hooks.php
<?php

// an event: the return is ignored
Hook::add('action:invoice.payment_recorded', 10,
    function ($payment_id, $invoice_id, $payment) {
        Crm::paymentSeen($invoice_id, (float) ($payment['amount_in'] ?? 0));
    });

// a value: by reference
Hook::add('filter:order.total', 20, function (&$tax_calc, $subtotal) {
    if (AcmeCampaign::active()) $tax_calc['total'] = round($tax_calc['total'] * 0.9, 2);
});

// a value: by return, the WHOLE array
Hook::add('filter:template.variables', 20, function ($template_path, $data) {
    $data['acme_campaign'] = AcmeCampaign::active();
    return $data;
});

// a route: registered on the object
Hook::add('register:routes', 20, function ($router) {
    $router->add('acme-report', 'acme/report/(?)', 'Acme:report');
});

Pitfalls

On template variables the return REPLACES the array

Returning only the key you added on the variables hook wipes every other variable going to the template. The return is not merged, it replaces. With more than one listener the last return wins, so a listener after yours can wipe yours too. Build on the array you were given and return all of it.

The order hook is not the payment hook

The hook running when an order is placed sees the invoice unpaid. Orders settled from the balance, and zero-total ones, close later in that same request. Bind to the payment hook where the work depends on money arriving. Asking "is it paid" inside the order hook always answers no.

A screen hook does not always want HTML

Most hooks in the screen family expect HTML to print. Some hand you an object and want you to touch that instead, ignoring your return. Tab, menu and page-part hooks are usually of the second kind. Returning HTML and seeing nothing is most often this.

A gate does not check the password

The sign-in gate runs after the credentials check. The user record there is real and verified; the gate's job is not checking a password but stopping the sign-in for another reason: a network limit, working hours, a maintenance window.

Cet article vous a-t-il été utile ?

Merci pour votre retour !

Besoin d'aide supplémentaire ?

Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.