# RSX Dispatch System Documentation ## Overview The RSX Dispatch System is the core request handling mechanism that processes HTTP requests through an attribute-driven, extensible pipeline. It replaces traditional Laravel routing with a more flexible, discoverable system based on PHP 8 attributes. ## Architecture ### Core Components ``` Request → Dispatcher → AssetHandler → RouteResolver → AttributeProcessor → HandlerFactory → Response ``` ## Dispatcher The central hub that coordinates request processing. ### Key Responsibilities 1. **Request Routing** - Finds matching routes from the manifest 2. **Asset Serving** - Delegates static file requests to AssetHandler 3. **Attribute Processing** - Coordinates before/after attribute processors 4. **Response Building** - Converts handler results to HTTP responses ### Usage ```php // In RsxServiceProvider $dispatcher = new Dispatcher( $manifest, $route_resolver, $attribute_processor, $asset_handler, $api_handler ); // Handle request $response = $dispatcher->dispatch($url, $method, $params); ``` ### Handler Priority The dispatcher processes handlers in this order: 1. **Assets** (Priority: 10) - Static files from `/rsx/*/public/` 2. **API** (Priority: 20) - API endpoints 3. **Controllers** (Priority: 30) - Web controllers 4. **Files** (Priority: 40) - File download endpoints ## Route Resolution ### Route Patterns Routes support various pattern types: ```php #[Route('/users')] // Static route #[Route('/users/:id')] // Required parameter #[Route('/users/:id?')] // Optional parameter #[Route('/posts/:category/:slug')] // Multiple parameters #[Route('/files/*')] // Wildcard (captures rest of URL) ``` ### Route Matching Routes are matched based on specificity: 1. Exact static matches 2. Routes with fewer parameters 3. Routes with required parameters before optional 4. Wildcard routes last ### Parameter Extraction Routes use `:param` syntax for parameters: ```php // Route: /users/:id/posts/:post_id? // URL: /users/123/posts/456?sort=date $params = [ 'id' => '123', // URL route parameter 'post_id' => '456', // URL route parameter 'sort' => 'date', // GET parameter '_route' => '/users/:id/posts/:post_id?', '_method' => 'GET' ]; ``` ### Parameter Priority Parameters are merged with the following priority order (earlier takes precedence): 1. **URL Route Parameters** - Values extracted from the route pattern (e.g., `:id`) 2. **GET Parameters** - Query string parameters 3. **POST Parameters** - Request body parameters If the same parameter name exists in multiple sources, the higher priority value is used: ```php // Route: /users/:id // URL: /users/123?id=456 // POST body: id=789 // Result: $params['id'] = '123' (URL route parameter wins) ``` ## Attribute System ### Available Attributes #### Route Attribute Defines HTTP endpoints: ```php use App\RSpade\Core\Attributes\Route; class UserController extends Rsx_Controller { #[Route('/users', methods: ['GET', 'POST'])] public function index($params) { } #[Route('/users/:id', methods: ['GET'], name: 'user.show')] public function show($params) { } } ``` #### Cache Attribute Caches responses: ```php use App\RSpade\Core\Attributes\Cache; #[Cache(ttl: 3600, key: 'user-list', tags: ['users'])] public function list_users($params) { // Expensive operation cached for 1 hour return User::with('posts')->get(); } ``` #### RateLimit Attribute Throttles requests: ```php use App\RSpade\Core\Attributes\RateLimit; #[RateLimit( max_attempts: 60, decay_minutes: 1, key: 'ip' // or 'user', 'api_key' )] public function api_endpoint($params) { } ``` #### Middleware Attribute Applies Laravel middleware: ```php use App\RSpade\Core\Attributes\Middleware; #[Middleware(['auth', 'verified'])] public function protected_action($params) { } #[Middleware(['auth'], except: ['index', 'show'])] class PostController { } ``` #### Cors Attribute Configures Cross-Origin Resource Sharing: ```php use App\RSpade\Core\Attributes\Cors; #[Cors( allowed_origins: ['https://app.example.com', 'https://*.example.com'], allowed_methods: ['GET', 'POST', 'PUT', 'DELETE'], allowed_headers: ['Content-Type', 'Authorization'], exposed_headers: ['X-Total-Count'], max_age: 3600, allow_credentials: true )] class ApiController { } ``` #### ApiVersion Attribute Manages API versioning: ```php use App\RSpade\Core\Attributes\ApiVersion; #[ApiVersion( version: 'v2', deprecated: true, deprecation_message: 'Use /api/v3/users instead', sunset_date: '2025-01-01' )] public function legacy_endpoint($params) { } ``` ### Attribute Processing Pipeline Attributes are processed in a specific order based on processor priority: 1. **RateLimit** (Priority: 90) - Blocks excessive requests early 2. **Middleware** (Priority: 80) - Authentication/authorization 3. **Cache** (Priority: 70) - Returns cached responses 4. **Cors** (Priority: 60) - Adds CORS headers ### Creating Custom Attributes 1. Create the attribute class: ```php namespace App\Attributes; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)] class Audit { public function __construct( public string $action, public bool $log_params = false ) {} } ``` 2. Create the processor: ```php namespace App\Processors; use App\RSpade\Core\Dispatch\Processors\ProcessorInterface; class AuditProcessor implements ProcessorInterface { public function can_handle($attribute): bool { return $attribute instanceof Audit; } public function process_before($attribute, Request $request, array &$context) { Log::info('Audit: ' . $attribute->action, [ 'user' => $request->user()?->id, 'params' => $attribute->log_params ? $request->all() : null ]); } public function process_after($attribute, Response $response, array $context): ?Response { // Log response if needed return $response; } public function get_priority(): int { return 50; } } ``` 3. Register the processor: ```php // In a service provider $attribute_processor->register_processor(new AuditProcessor()); ``` ## API Handler ### JSON-Only Responses The API handler enforces JSON-only responses (opinionated design): ```php // Always returns JSON, regardless of Accept header #[Route('/api/users', methods: ['GET'])] public function get_users($params) { return ['users' => User::all()]; // Automatically converted to JSON } ``` ### API Parameters The API handler automatically extracts common parameters: ```php public function list_items($params) { // Pagination $page = $params['_page'] ?? 1; // ?page=2 $per_page = $params['_per_page'] ?? 25; // ?per_page=50 // Sorting $sort = $params['_sort'] ?? 'id'; // ?sort=name $order = $params['_order'] ?? 'asc'; // ?order=desc // Searching $search = $params['_search'] ?? null; // ?q=term or ?search=term // Field filtering $fields = $params['_fields'] ?? null; // ?fields=id,name,email // API key $api_key = $params['_api_key'] ?? null; // ?api_key=secret or X-API-Key header } ``` ### Internal API Execution (Future) ```php // Call API methods internally without HTTP overhead $result = $api_handler->execute_internal( 'UserApi.get_profile', ['user_id' => 123] ); // Returns PHP array/object, not JSON response ``` ## Asset Handler ### Static File Serving Automatically serves files from `/rsx/*/public/` directories: ``` /rsx/shop/public/logo.png → Accessible at /rsx/shop/public/logo.png /rsx/blog/public/styles.css → Accessible at /rsx/blog/public/styles.css ``` ### Security Features - Path traversal protection - MIME type detection - Cache headers (1 day default) - Only serves from designated public directories ### Usage Files are automatically served without configuration: ```html Shop Logo ``` ## Error Handling ### Built-in Error Responses The dispatcher handles common errors: ```php // 404 Not Found if (!$route_match) { return $this->handle_not_found($url, $method); } // 405 Method Not Allowed if (!in_array($method, $route['methods'])) { return $this->handle_method_not_allowed($method, $route['methods']); } // 500 Internal Server Error try { // ... dispatch logic } catch (Throwable $e) { return $this->handle_exception($e); } ``` ### Custom Error Handling Override error methods in a custom dispatcher: ```php class CustomDispatcher extends Dispatcher { protected function handle_not_found($url, $method) { return response()->json([ 'error' => 'Endpoint not found', 'url' => $url, 'method' => $method ], 404); } } ``` ## Performance Optimization ### Caching Strategy 1. **Memory Cache** - In-process array cache for current request 2. **File Cache** - Persistent cache using Laravel's cache system 3. **Route Cache** - Pre-compiled routes for production ### Cache Invalidation ```php // Clear specific cache tags Cache::tags(['users'])->flush(); // Clear all RSX caches php artisan rsx:manifest:clear php artisan rsx:routes:clear ``` ### Production Optimization ```bash # Build and cache for production php artisan rsx:manifest:build php artisan rsx:routes:cache php artisan config:cache ``` ## Testing ### Unit Testing Dispatching ```php use Tests\TestCase; use App\RSpade\Core\Dispatch\Dispatcher; class DispatcherTest extends TestCase { public function test_routes_to_correct_handler() { $dispatcher = $this->create_test_dispatcher(); $response = $dispatcher->dispatch('/users', 'GET'); $this->assertEquals(200, $response->getStatusCode()); $this->assertJson($response->getContent()); } public function test_applies_rate_limiting() { $dispatcher = $this->create_test_dispatcher(); // Make requests up to limit for ($i = 0; $i < 60; $i++) { $dispatcher->dispatch('/api/limited', 'GET'); } // Next request should fail $this->expectException(TooManyRequestsHttpException::class); $dispatcher->dispatch('/api/limited', 'GET'); } } ``` ### Integration Testing ```php class ApiIntegrationTest extends TestCase { public function test_complete_api_flow() { $response = $this->get('/api/users', [ 'Accept' => 'application/xml' // Should still return JSON ]); $response->assertStatus(200) ->assertHeader('Content-Type', 'application/json') ->assertJsonStructure(['users']); } } ``` ## Debugging ### Debug Mode Enable detailed logging: ```php // In .env RSX_DEBUG=true RSX_LOG_DISPATCH=true ``` ### Viewing Routes ```bash # List all discovered routes php artisan rsx:routes:list # Search for specific route php artisan rsx:routes:list | grep "users" # Show route details php artisan rsx:routes:show /users/:id ``` ### Tracing Requests ```php // In Dispatcher Log::debug('Dispatch', [ 'url' => $url, 'method' => $method, 'route' => $route_match, 'attributes' => $attributes, 'params' => $params ]); ``` ## Migration Guide ### From Laravel Routes Before (Laravel): ```php // routes/web.php Route::get('/users', [UserController::class, 'index']); Route::post('/users', [UserController::class, 'store']); Route::get('/users/:id', [UserController::class, 'show']); ``` After (RSX): ```php // /rsx/controllers/UserController.php class UserController extends Rsx_Controller { #[Route('/users', methods: ['GET'])] public function index($params) { } #[Route('/users', methods: ['POST'])] public function store($params) { } #[Route('/users/:id', methods: ['GET'])] public function show($params) { } } ``` ### From Laravel Middleware Before: ```php Route::middleware(['auth', 'verified'])->group(function () { Route::get('/dashboard', [DashboardController::class, 'index']); }); ``` After: ```php #[Middleware(['auth', 'verified'])] class DashboardController extends Rsx_Controller { #[Route('/dashboard', methods: ['GET'])] public function index($params) { } } ``` ## Best Practices ### 1. Attribute Organization Place related attributes together: ```php #[Route('/api/users', methods: ['GET'])] #[Cache(ttl: 300, tags: ['users'])] #[RateLimit(max_attempts: 100)] #[Middleware(['auth:api'])] public function list_users($params) { } ``` ### 2. Parameter Validation Validate parameters early: ```php public function get_user($params) { $user_id = $params['id'] ?? null; if (!$user_id || !is_numeric($user_id)) { return response()->json(['error' => 'Invalid user ID'], 400); } // Process request... } ``` ### 3. Error Handling Use consistent error responses: ```php public function process_order($params) { try { $result = $this->order_service->process($params); return ['success' => true, 'order' => $result]; } catch (ValidationException $e) { return response()->json(['error' => $e->getMessage()], 422); } catch (Exception $e) { Log::error('Order processing failed', ['error' => $e]); return response()->json(['error' => 'Processing failed'], 500); } } ``` ### 4. Cache Strategy Use appropriate cache TTLs and tags: ```php #[Cache(ttl: 3600, tags: ['products', 'catalog'])] // 1 hour, tagged public function get_product_catalog($params) { } #[Cache(ttl: 60, key: 'hot-deals')] // 1 minute for frequently changing public function get_hot_deals($params) { } #[Cache(ttl: 86400, tags: ['static'])] // 1 day for static content public function get_terms_of_service($params) { } ``` ### 5. Rate Limiting Apply appropriate limits: ```php // Public endpoints - strict limits #[RateLimit(max_attempts: 10, decay_minutes: 1)] public function public_search($params) { } // Authenticated - higher limits #[RateLimit(max_attempts: 100, decay_minutes: 1, key: 'user')] public function user_search($params) { } // Internal APIs - very high limits #[RateLimit(max_attempts: 1000, decay_minutes: 1, key: 'api_key')] public function internal_api($params) { } ``` ## Summary The RSX Dispatch System provides: - **Attribute-driven routing** without route files - **Automatic discovery** of controllers and routes - **Extensible processing** through attribute processors - **Built-in features** like caching, rate limiting, CORS - **Static asset serving** without configuration - **JSON-only APIs** for consistency - **Performance optimization** through caching - **Easy testing** with comprehensive test helpers It replaces traditional Laravel routing with a more flexible, discoverable system that keeps route definitions with the code they control.