Event Hooks

A lightweight observational event system for reacting to app lifecycle stages without coupling to framework internals.

Overview

The App exposes on() and emit() for registering and firing lifecycle events. Listeners are observational only — they receive typed arguments but return values are ignored. Mutations belong at explicit layer boundaries (routes, controllers, Res::*), not inside event listeners.

This design aligns with PhlatPage's immutable data philosophy: the framework controls when state changes; hooks let you observe those changes.

API

// Register a listener
$app->on('app.route:after', function(Page $page) use ($app) {
    // observe the resolved page
});

// Emit an event (framework internals only)
$app->emit('app.route:after', $this->page);

Naming convention

Event names follow a structured pattern:

domain.action:timing
domain.action(qualifier):timing
Segment Examples
domain app, page, file, userrequired
action create, update, delete, render, login, route
qualifier (slug), (title), (password) — optional, narrows scope
timing :before, :after

Every event name must begin with a domain. Bare action names like route:after are not valid — use app.route:after.

Qualified events are distinct — a listener on page.update(title):after will not fire when page.update(slug):after is emitted. The full event name must match exactly.

Current hooks

Event When Args
app.route:after After $page->route() resolves the routed page Page $page

Addons

Event hooks are the primary way addons observe the request lifecycle without requiring changes to framework code. Register listeners in your addon's index.php:

$app->on('app.route:after', function(Page $page) use ($app) {
    // populate a Clockwork panel, log to a file, etc.
});

Listeners run in registration order. Because addons load before routing, app.route:after is the earliest point at which the resolved Page is available.

<?php

use Phlat\App;
use Phlat\Page;

// In an addon's index.php — $app is injected

// Observe the resolved page after routing
$app->on('app.route:after', function(Page $page) use ($app) {
    // Correct: read and observe
    $title = $page->title;
    error_log("Page resolved: {$title}");

    // Wrong: do not mutate or redirect from a listener
    // Res::redirect('/somewhere');  // violates the observational contract
});

// Multiple listeners on the same event are all called
$app->on('app.route:after', function(Page $page) {
    // also fires
});