CONTROLLER(3) RSX Framework Manual CONTROLLER(3) NAME Controller - RSX request handling and routing system SYNOPSIS use App\RSpade\Core\Controller\Rsx_Controller_Abstract; use App\RSpade\Core\Session\Session; class User_Controller extends Rsx_Controller_Abstract { public static function pre_dispatch(Request $request, array $params = []) { if (!Session::is_logged_in()) { return response_unauthorized(); } return null; } #[Route('/users', methods: ['GET'])] public static function index(Request $request, array $params = []) { return rsx_view('User_List'); } #[Ajax_Endpoint] public static function get_profile(Request $request, array $params = []) { return ['name' => 'John', 'email' => 'john@example.com']; } } DESCRIPTION RSX Controllers provide a simplified approach to request handling through static methods and automatic discovery. Unlike Laravel's dependency injection heavy controllers that require constructor injection and service container resolution, RSX uses static methods that can be called from anywhere without instantiation. The framework automatically discovers controllers through the manifest system - no manual registration in route files required. Routes are defined directly on methods using attributes, keeping routing logic with the code it affects. Key differences from Laravel: - Laravel: Instance methods with dependency injection - RSX: Static methods with explicit parameters - Laravel: Routes defined in separate routes/*.php files - RSX: Routes defined via #[Route] attributes on methods - Laravel: Manual API resource controllers - RSX: Automatic JavaScript stub generation for Ajax methods - Laravel: Middleware defined in route files or constructors - RSX: pre_dispatch hooks for authentication/authorization Benefits: - No dependency injection complexity - Routes live with their handlers - Automatic Ajax/JavaScript integration - Simple static method calls - No service container overhead CREATING A CONTROLLER 1. Extend Rsx_Controller_Abstract 2. Add static methods with Request and params parameters 3. Use attributes for routing class Dashboard_Controller extends Rsx_Controller_Abstract { #[Route('/dashboard')] public static function index(Request $request, array $params = []) { return rsx_view('Dashboard'); } } ROUTE ATTRIBUTES #[Route(path, methods)] Define HTTP route. path: URL pattern with optional parameters methods: ['GET'] or ['POST'] (default both) Examples: #[Route('/users')] #[Route('/users/{id}', methods: ['GET'])] #[Route('/api/users', methods: ['POST'])] ROUTE PARAMETERS URL segments in braces become $params entries: #[Route('/users/{id}/posts/{post_id}')] public static function show(Request $request, array $params = []) { $user_id = $params['id']; $post_id = $params['post_id']; } Query parameters (GET) also added to $params: /users?sort=name $params['sort'] === 'name' POST data accessed via $request, NOT $params: $name = $request->input('name'); $email = $request->post('email'); AUTHENTICATION RSX uses manual authentication checks rather than declarative attributes. Auth checks are placed directly in controller code for visibility. A code quality rule (PHP-AUTH-01) verifies all endpoints have auth checks, either in the method body or controller's pre_dispatch(). Controller-Wide Authentication (Recommended): class Dashboard_Controller extends Rsx_Controller_Abstract { public static function pre_dispatch(Request $request, array $params = []) { if (!Session::is_logged_in()) { return response_unauthorized(); } return null; } #[Route('/dashboard')] public static function index(Request $request, array $params = []) { // Auth checked in pre_dispatch return rsx_view('Dashboard'); } } Method-Level Authentication: #[Route('/admin')] public static function admin_panel(Request $request, array $params = []) { if (!Session::is_logged_in()) { return response_unauthorized(); } if (!Permission::has_role(User_Model::ROLE_ADMIN)) { return response_unauthorized('Admin access required'); } return rsx_view('Admin_Panel'); } Response Helpers: response_unauthorized(?string $message = null) Context-aware: JSON for Ajax, redirect/403 for web response_not_found(?string $message = null) Context-aware: JSON for Ajax, 404 page for web Public Endpoints: Mark public endpoints with @auth-exempt in class docblock: /** * @auth-exempt Public landing page */ class Landing_Controller extends Rsx_Controller_Abstract { #[Route('/')] public static function index(Request $request, array $params = []) { return rsx_view('Landing'); } } Permission Helpers (rsx/permission.php): class Permission extends Permission_Abstract { public static function anybody(Request $request, array $params): mixed { return true; // Always allow } public static function authenticated(Request $request, array $params): mixed { return Session::is_logged_in(); } public static function has_role(Request $request, array $params, string $role): mixed { if (!Session::is_logged_in()) { return false; } return Session::get_user()->has_role($role); } } AJAX ENDPOINTS AND AUTHENTICATION Ajax endpoints use the same auth pattern as routes. The pre_dispatch() check covers all methods in the controller: #[Ajax_Endpoint] public static function get_data(Request $request, array $params = []) { // Auth checked in pre_dispatch return ['data' => 'value']; } On response_unauthorized(), Ajax returns: { "success": false, "error_code": "unauthorized" } HTTP Status: 403 Forbidden API_INTERNAL ATTRIBUTE #[Ajax_Endpoint] Makes method callable via Ajax. Generates JavaScript stub automatically. Returns JSON response. PHP: #[Ajax_Endpoint] public static function search(Request $request, array $params = []) { $query = $params['query'] ?? ''; return User::where('name', 'like', "%$query%")->get(); } JavaScript (auto-generated): const results = await User_Controller.search({query: 'john'}); JAVASCRIPT STUB GENERATION Controllers with Ajax_Endpoint methods get stubs in storage/rsx-build/js-stubs/ControllerName.js: class User_Controller { static async search(...args) { return Ajax.call(Rsx.Route('User_Controller', 'search'), args); } } Stubs included automatically in bundles. Note: Rsx.Route() generates type-safe URLs like /_ajax/User_Controller/search CALLING API METHODS From JavaScript: // Single argument const user = await User_Controller.get_user(123); // Named parameters const results = await User_Controller.search({ query: 'john', limit: 10 }); // Multiple arguments const data = await User_Controller.process(id, options); From PHP: User_Controller::get_user($request, ['id' => 123]); ROUTE RESOLUTION PHP: $url = Rsx::Route('User_Controller', 'show', ['id' => 5]); // Returns: "/users/5" if (Rsx::Route('User_Controller')->is_current()) { // Current page is users index } JavaScript: const url = Rsx.Route('User_Controller', 'show', {id: 5}); // Returns: "/users/5" PRE_DISPATCH HOOK Run before any action in controller: public static function pre_dispatch(Request $request, array $params = []) { // Check authentication if (!Session::is_logged_in()) { return redirect('/login'); } // Return null to continue return null; } Return non-null to override response. RESPONSE TYPES Views: return rsx_view('View_Name', ['data' => $value]); JSON (Ajax_Endpoint): return ['key' => 'value']; // Auto-converted to JSON Redirects: return redirect('/path'); return redirect()->route('route.name'); Raw responses: return response('content', 200); return response()->json(['key' => 'value']); AUTHENTICATION Use Session:: in pre_dispatch: public static function pre_dispatch(Request $request, array $params = []) { if (!Session::is_logged_in()) { if ($request->ajax()) { return response()->json(['error' => 'Unauthorized'], 401); } return redirect('/login'); } return null; } ERROR HANDLING Throw exceptions for errors: public static function show(Request $request, array $params = []) { $user = User::find($params['id']); if (!$user) { throw new NotFoundHttpException('User not found'); } return rsx_view('User_Profile', ['user' => $user]); } AJAX VS PAGE REQUESTS Detect Ajax requests: if ($request->ajax()) { return ['data' => $data]; // JSON } else { return rsx_view('Page', ['data' => $data]); // HTML } EXAMPLES // CRUD controller class Product_Controller extends Rsx_Controller_Abstract { #[Route('/products')] public static function index(Request $request, array $params = []) { $products = Product::paginate(20); return rsx_view('Product_List', compact('products')); } #[Route('/products/{id}')] public static function show(Request $request, array $params = []) { $product = Product::findOrFail($params['id']); return rsx_view('Product_Detail', compact('product')); } #[Ajax_Endpoint] public static function create(Request $request, array $params = []) { $product = Product::create($params); return $product; } #[Ajax_Endpoint] public static function update(Request $request, array $params = []) { $product = Product::findOrFail($params['id']); $product->update($params); return $product; } #[Ajax_Endpoint] public static function delete(Request $request, array $params = []) { Product::destroy($params['id']); return ['success' => true]; } } // JavaScript usage async function create_product(data) { const product = await Product_Controller.create(data); console.log('Created:', product); } async function update_product(id, data) { const product = await Product_Controller.update({id, ...data}); console.log('Updated:', product); } ROUTE CACHING Routes extracted from manifest and cached. Clear cache after adding routes: php artisan rsx:clean NAMING CONVENTIONS Controllers: Noun_Controller (User_Controller, Product_Controller) Actions: verb or verb_noun (index, show, create, update_profile) Routes: RESTful patterns (/users, /users/{id}) FILE ORGANIZATION rsx/app/module/ ├── module_controller.php # Main controller ├── module_api_controller.php # API endpoints └── feature/ └── module_feature_controller.php TESTING ROUTES Use rsx:debug command: php artisan rsx:debug /users php artisan rsx:debug /api/search --post='{"query":"test"}' TROUBLESHOOTING Route not found: - Check Route attribute syntax - Run php artisan rsx:routes to list all - Clear cache: php artisan rsx:clean PHP-AUTH-01 code quality error: - Add auth check to pre_dispatch() (recommended) - OR add auth check at start of method - OR add @auth-exempt comment for public endpoints - Run: php artisan rsx:check Permission denied / Unauthorized: - Check Session::is_logged_in() returns true - Verify Permission helpers in rsx/permission.php - Check user has required role/permission JavaScript stub missing: - Ensure Ajax_Endpoint attribute present - Rebuild manifest: php artisan rsx:manifest:build - Check storage/rsx-build/js-stubs/ Authentication issues: - Implement pre_dispatch() with Session::is_logged_in() check - Return response_unauthorized() on failure - Verify session configuration SEE ALSO manifest_api(3), bundle_api(3), jqhtml(3), rsx:routes(1) RSX Framework 2025-09-17 CONTROLLER(3)