# Common Hook Scenarios

https://dev.wisecp.com/es/common-hook-scenarios

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

- [Writing a Hook Listener](https://dev.wisecp.com/en/writing-a-hook-listener) read first.
- The hook names in the examples are real in this installation; use the [domain directory](https://dev.wisecp.com/en/hook-domains) when adapting them to your own job.

## 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.

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

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

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

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

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

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

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

## Related Articles

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