NAME event_hooks - PHP event system with filters, gates, and actions SYNOPSIS Register event handlers with attributes: #[OnEvent('event.name', priority: 10)] public static function handler_method($data) { // Process event return $data; } Trigger events from code: // Filter: Transform data through handler chain $result = Rsx::trigger_filter('event.name', $data); // Gate: First non-true response halts execution $result = Rsx::trigger_gate('event.name', $data); // Action: Fire and forget side effects Rsx::trigger_action('event.name', $data); DESCRIPTION The RSX event system provides attribute-based event handling similar to WordPress hooks or JavaScript event systems, allowing applications to modify framework behavior without changing core code. Unlike Laravel's event/listener system which requires manual registration in service providers, RSX events use #[OnEvent] attributes that are automatically discovered during manifest compilation. RSX vs Laravel Events: - Laravel: Register listeners in EventServiceProvider or listener classes - RSX: Mark methods with #[OnEvent], automatic manifest discovery The system provides three event types optimized for different use cases: - Filters: Transform data through multiple handlers - Gates: Authorization checks where first denial wins - Actions: Side effects without return values All handlers execute in priority order (lowest number first). BASIC USAGE Creating Event Handlers Create a class in /rsx/handlers/ with static methods marked with #[OnEvent]: namespace Rsx\Handlers; class Upload_Handlers { #[OnEvent('file.upload.authorize', priority: 10)] public static function require_auth($data) { if (!Session::is_logged_in()) { return response()->json([ 'error' => 'Authentication required' ], 403); } return true; } #[OnEvent('file.upload.params', priority: 20)] public static function add_metadata($params) { $params['uploaded_by'] = Session::id(); return $params; } } Handlers are automatically discovered by the manifest system. No manual registration required. Triggering Events Use Rsx:: methods to trigger events from your code: // In a controller or service $upload_params = Rsx::trigger_filter('file.upload.params', [ 'site_id' => 1, 'filename' => 'document.pdf' ]); // Returns modified params after all handlers run EVENT TYPES Filter Events (trigger_filter) Filters pass data through a chain of handlers, with each handler modifying and returning the data for the next handler. Usage: $result = Rsx::trigger_filter('event.name', $data); Handler signature: public static function handler($data) { // Modify $data return $data; // Must return modified data } Flow: $data = ['value' => 1]; Handler 1 (priority 10): receives ['value' => 1], returns ['value' => 2] Handler 2 (priority 20): receives ['value' => 2], returns ['value' => 3] Final result: ['value' => 3] Use cases: - Transforming request parameters - Modifying response data - Adding fields to data structures - Sanitizing or validating input Example: #[OnEvent('post.content.filter', priority: 10)] public static function sanitize_html($content) { return strip_tags($content, '
');
}
#[OnEvent('post.content.filter', priority: 20)]
public static function add_signature($content) {
return $content . "\n\nPosted via RSX";
}
// Usage:
$clean = Rsx::trigger_filter('post.content.filter', $raw_content);
Gate Events (trigger_gate)
Gates check authorization or validation. Handlers return true to allow
or any other value to deny. The first non-true response immediately
halts execution and is returned.
Usage:
$result = Rsx::trigger_gate('event.name', $data);
if ($result !== true) {
// Access denied, $result contains denial response
return $result;
}
Handler signature:
public static function handler($data) {
if (authorized) {
return true; // Allow, continue to next handler
}
return response()->json(['error' => 'Denied'], 403);
}
Flow:
Handler 1 (priority 10): returns true (continue)
Handler 2 (priority 20): returns JsonResponse (STOP)
Handler 3 (priority 30): never executes
Final result: JsonResponse from Handler 2
Use cases:
- Authorization checks
- Permission validation
- Rate limiting
- Blacklist checks
Example:
#[OnEvent('api.access.authorize', priority: 10)]
public static function check_authentication($data) {
if (!Session::is_logged_in()) {
return response()->json(['error' => 'Login required'], 401);
}
return true;
}
#[OnEvent('api.access.authorize', priority: 20)]
public static function check_permissions($data) {
if (!$data['user']->can($data['permission'])) {
return response()->json(['error' => 'Insufficient permissions'], 403);
}
return true;
}
// Usage:
$auth = Rsx::trigger_gate('api.access.authorize', [
'user' => Session::get_user(),
'permission' => 'edit_posts'
]);
if ($auth !== true) return $auth;
Action Events (trigger_action)
Actions perform side effects. Return values are ignored. All handlers
execute regardless of what previous handlers did.
Usage:
Rsx::trigger_action('event.name', $data);
Handler signature:
public static function handler($data): void {
// Perform side effect
// Return value ignored
}
Flow:
Handler 1 (priority 10): executes
Handler 2 (priority 20): executes
Handler 3 (priority 30): executes
All handlers always run
Use cases:
- Logging
- Sending notifications
- Triggering webhooks
- Cache invalidation
- Analytics tracking
Example:
#[OnEvent('user.registered', priority: 10)]
public static function send_welcome_email($data) {
Mail::to($data['user']->email)->send(new WelcomeEmail($data['user']));
}
#[OnEvent('user.registered', priority: 20)]
public static function log_registration($data) {
Log::info('New user registered', ['user_id' => $data['user']->id]);
}
#[OnEvent('user.registered', priority: 30)]
public static function notify_admins($data) {
Notification::send_admin_alert('New user: ' . $data['user']->email);
}
// Usage:
Rsx::trigger_action('user.registered', ['user' => $user]);
PRIORITY SYSTEM
Handlers execute in priority order (lowest number first). Default is 100.
#[OnEvent('event.name', priority: 10)] // Runs first
#[OnEvent('event.name', priority: 50)] // Runs second
#[OnEvent('event.name', priority: 100)] // Runs third (default)
Priority Guidelines:
1-10 Critical early processing (auth, validation)
11-50 Main application logic
51-100 Post-processing, cleanup
101+ Logging, analytics (fire last)
For gate events, lower priority handlers can block higher priority ones
from ever executing.
API REFERENCE
Rsx::trigger_filter(string $event, mixed $data): mixed
Passes $data through all handlers for $event, with each handler
receiving the output of the previous handler.
Parameters:
$event - Event name (e.g., 'file.upload.params')
$data - Initial data to transform
Returns:
Final data after all handlers have processed it
Example:
$params = ['filename' => 'test.pdf'];
$params = Rsx::trigger_filter('upload.params', $params);
// $params may now have additional fields added by handlers
Rsx::trigger_gate(string $event, mixed $data): mixed
Executes authorization handlers until one returns non-true. Returns
true if all handlers allow, or the first denial response.
Parameters:
$event - Event name (e.g., 'api.authorize')
$data - Authorization context data
Returns:
true if all handlers allow access
Response object (or any non-true value) if denied
Example:
$result = Rsx::trigger_gate('delete.authorize', ['post' => $post]);
if ($result !== true) {
return $result; // Return denial response
}
// Proceed with delete
Rsx::trigger_action(string $event, mixed $data): void
Executes all handlers as side effects. Return values ignored.
Parameters:
$event - Event name (e.g., 'user.logged_in')
$data - Event context data
Returns:
void
Example:
Rsx::trigger_action('order.completed', [
'order' => $order,
'user' => $user
]);
NAMING CONVENTIONS
Event names use dot notation to indicate scope and hierarchy:
{module}.{entity}.{action}
Examples:
file.upload.authorize File upload authorization
file.upload.params File upload parameter modification
file.upload.complete File upload completion
user.login.authorize User login authorization
user.login.success User login success
post.save.before Before saving post
post.save.after After saving post
Guidelines:
- Use lowercase with dots
- Start with module/feature name
- Include entity being acted upon
- End with action or lifecycle event
- Be specific and descriptive
FRAMEWORK EVENTS
The framework fires these events automatically:
File Upload Events
file.upload.authorize (gate)
When: Before processing upload
Data: {request: Request, user: User|null, params: array}
Use: Authorization, rate limiting, quota checks
file.upload.params (filter)
When: Before saving file
Data: array of upload parameters
Use: Add metadata, modify filename, set categories
file.upload.complete (action)
When: After file saved successfully
Data: {attachment: File_Attachment_Model, request: Request, params: array}
Use: Logging, notifications, post-processing
file.upload.response (filter)
When: Before sending response to client
Data: {success: bool, attachment: array}
Use: Add custom fields to response, modify URLs
IMPLEMENTATION DETAILS
Manifest Integration
Event handlers are discovered during manifest compilation by scanning
for #[OnEvent] attributes on public static methods.
The manifest extracts:
- Event name from first attribute parameter
- Priority from second parameter or 'priority' key
- Class and method name for callable construction
Handlers are stored in manifest_data.php under 'event_handlers' key:
'event_handlers' => [
'file.upload.authorize' => [
[
'class' => 'Rsx\\Handlers\\Upload_Handlers',
'method' => 'require_auth',
'priority' => 10,
'file' => '/path/to/Upload_Handlers.php'
]
]
]
This approach avoids runtime reflection - all handler discovery
happens during manifest compilation.
Event_Registry
The Event_Registry class loads handler definitions from the manifest
and constructs callables:
Event_Registry::get_handlers('event.name')
// Returns array of callables sorted by priority
Handler callables are created as closures that invoke the static
methods:
function ($data) use ($class, $method) {
return $class::$method($data);
}
Registry loads handlers lazily on first access.
Performance
Handler discovery: Zero runtime cost (manifest compilation time only)
Event triggering: O(n) where n = number of handlers for that event
Handler lookup: O(1) from manifest array
EXAMPLES
Complete Authorization Example
File: /rsx/handlers/Upload_Security.php
namespace Rsx\Handlers;
use App\RSpade\Core\Session\Session;
class Upload_Security
{
#[OnEvent('file.upload.authorize', priority: 10)]
public static function require_login($data)
{
if (!Session::is_logged_in()) {
return response()->json([
'success' => false,
'error' => 'Login required'
], 401);
}
return true;
}
#[OnEvent('file.upload.authorize', priority: 20)]
public static function check_quota($data)
{
$user = Session::get_user();
if ($user->storage_used >= $user->storage_quota) {
return response()->json([
'success' => false,
'error' => 'Storage quota exceeded'
], 403);
}
return true;
}
#[OnEvent('file.upload.authorize', priority: 30)]
public static function check_file_type($data)
{
$allowed = ['jpg', 'png', 'pdf', 'doc', 'docx'];
$ext = $data['request']->file('file')->getClientOriginalExtension();
if (!in_array(strtolower($ext), $allowed)) {
return response()->json([
'success' => false,
'error' => 'File type not allowed'
], 400);
}
return true;
}
}
Complete Filter Chain Example
File: /rsx/handlers/Content_Filters.php
namespace Rsx\Handlers;
class Content_Filters
{
#[OnEvent('post.content.filter', priority: 10)]
public static function strip_dangerous_html($content)
{
return strip_tags($content, '