🪝 Hooks & Events

Hooks let modules extend PhPstrap without modifying core files. Use actions to react to events and filters to modify data as it flows through the system.

TL;DR: Register callbacks in your module’s module.php.
Use add_action('hook', fn() => ... , $priority) and add_filter('filter', fn($value) => $value, $priority, $args).

1) Core Concepts

<?php
// Pseudo-APIs exposed by PhPstrap bootstrap
add_action(string $hook, callable $fn, int $priority = 10): void;
do_action(string $hook, mixed ...$args): void;

add_filter(string $filter, callable $fn, int $priority = 10, int $accepted_args = 1): void;
apply_filters(string $filter, mixed $value, mixed ...$args): mixed;

2) App Lifecycle & Common Hooks

Typical high-level flow and suggested hook points (names may vary depending on your install):

PhaseAction HooksFilter Hooks
Bootstrap app_init, routes_register, modules_loaded config_loaded
Request request_start, before_dispatch, after_dispatch request_query_vars
Auth auth_login_success, auth_login_failed, auth_logout auth_user_can
Render before_render, after_render, enqueue_assets view_data, html_output
Domain user_created, user_updated, user_deleted user_profile_data
Modules module_install, module_enable, module_disable, module_uninstall module_settings_schema
Mail mail_send_attempt, mail_sent, mail_failed mail_headers, mail_body, mail_transport
Jobs cron_tick, scheduler_run job_payload

Note: Names above are canonical suggestions used across these docs. If your build uses different names, adapt accordingly.


3) Registering Hooks in a Module

Place registrations in your module’s module.php so they load when the module is enabled.

<?php // modules/my-module/module.php

// 3.1 Action: add a footer note for logged admins
add_action('after_render', function (&$html, $context) {
  if (!empty($context['is_admin'])) {
    $html .= "<!-- rendered by my-module v1.0.0 -->";
  }
}, 20);

// 3.2 Filter: customize outgoing email headers
add_filter('mail_headers', function (array $headers, array $ctx) {
  $headers['X-App'] = 'PhPstrap';
  return $headers;
}, 10, 2);

// 3.3 Routes: register a new route during bootstrap
add_action('routes_register', function ($router) {
  $router->get('/health', 'MyModule\\Controllers\\HealthController@index');
});

4) Filters vs Actions: Practical Examples

A) Filter user display name

add_filter('user_display_name', function (string $name, array $user) {
  if (!empty($user['company'])) {
    return "{$name} · {$user['company']}";
  }
  return $name;
}, 10, 2);

B) Sanitize HTML before output

add_filter('html_output', function (string $html) {
  // Run through your sanitizer library here
  return $html; // sanitized
});

C) Action on successful login

add_action('auth_login_success', function (array $user, array $context) {
  audit_log('login', ['user_id' => $user['id'], 'ip' => $context['ip'] ?? null]);
});

5) Priorities & Execution Order

// Two filters adjusting subject; the second sees the first one's result
add_filter('mail_subject', fn($s) => "[LexBoard] $s", 5);
add_filter('mail_subject', fn($s) => strtoupper($s), 15);

6) Data Shape & Contracts

Filters must return the same type they receive. When documenting your module, specify:

filter: mail_headers
params: (array $headers, array $ctx)
return: array
fires: before mail transport send, after body/subject filters

7) Error Handling

add_action('after_dispatch', function ($response) {
  try {
    Analytics::track('page_view', ['path' => $_SERVER['REQUEST_URI'] ?? '/']);
  } catch (Throwable $e) {
    log_error('analytics', $e->getMessage());
  }
});

8) Security & Performance


9) Suggested Hook Catalog (Reference)

Add or adapt these in your installation; modules can rely on them for stable integration points.

App / Routing

  • app_init (action)
  • modules_loaded (action)
  • routes_register (action, $router)
  • before_dispatch / after_dispatch (action, $request/$response)
  • before_render / after_render (action, &$html, $context)
  • html_output (filter, $html)

Auth / Users

  • auth_login_success / auth_login_failed / auth_logout (action)
  • auth_user_can (filter, $allowed, $user, $capability)
  • user_created, user_updated, user_deleted (action)
  • user_display_name, user_profile_data (filter)

Modules / Settings

  • module_install, module_enable, module_disable, module_uninstall (action)
  • module_settings_schema (filter, $schema, $module)
  • admin_menu (filter, $menu)
  • enqueue_assets (action)

Mail / Jobs

  • mail_subject, mail_headers, mail_body, mail_transport (filters)
  • mail_send_attempt, mail_sent, mail_failed (action)
  • cron_tick, scheduler_run (action)
  • job_payload (filter)

10) Example: Add an Admin Menu Item

<?php
// Append an entry to the Admin sidebar
add_filter('admin_menu', function (array $menu) {
  $menu[] = [
    'slug' => 'my-module',
    'title' => 'My Module',
    'icon' => 'fas fa-puzzle-piece',
    'href' => '/admin/my-module.php',
    'capability' => 'manage_options',
  ];
  return $menu;
});

11) Testing Your Hooks


12) Troubleshooting


Have a new hook to propose? Open a PR updating this catalog and include where it fires in the lifecycle. See Contributing.