🪝 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.
module.php.Use
add_action('hook', fn() => ... , $priority) and add_filter('filter', fn($value) => $value, $priority, $args).
1) Core Concepts
- Action: A fire-and-forget event. Use for side effects (logging, enqueue assets, send mail).
- Filter: Transforms and returns a value (e.g., sanitize input, alter email body).
- Priority: Lower runs earlier (default
10). Same-priority callbacks run FIFO by registration order. - Args: Filters may accept multiple parameters; declare the number your callback needs.
<?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):
| Phase | Action Hooks | Filter 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_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
- Lower numbers run earlier:
5runs before10. - Use early hooks (e.g.,
priority=1..5) for guards (auth, feature flags). - Use later hooks (e.g.,
15..20) to decorate output (append notes, analytics).
// 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:
- Hook name and kind (action/filter).
- Parameters (names & types).
- Return type (for filters).
- When in the lifecycle it fires.
filter: mail_headers
params: (array $headers, array $ctx)
return: array
fires: before mail transport send, after body/subject filters
7) Error Handling
- Wrap risky logic in
try/catchand log errors (never fatal the request). - Fail closed for security-sensitive filters (e.g., form validation) and fail open for decorative actions (e.g., analytics).
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
- Never trust input: Validate/sanitize inside your callbacks.
- Respect capability checks: Use
auth_user_can(or equivalent) to gate actions. - Be fast: Hooks run on every request—avoid blocking I/O; defer with queues/cron when possible.
- Order matters: Use priorities to ensure security filters run before rendering or side effects.
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
- Use a seed or fixture route to trigger your callbacks.
- Log your hook execution with context:
audit_log('hook', [...]). - Write unit tests for pure filters; e2e (browser) checks for actions that affect UI.
12) Troubleshooting
- Callback not firing: Is the module enabled? Is the hook name spelled correctly? Check priorities.
- Filter has no effect: Ensure you return the modified value and that nothing later overrides it.
- Order issues: Adjust priorities; verify registration runs before the event fires.
- Fatal error: Wrap with
try/catch; log and fail safely.
Have a new hook to propose? Open a PR updating this catalog and include where it fires in the lifecycle. See Contributing.