Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
458 lines
14 KiB
Plaintext
Executable File
458 lines
14 KiB
Plaintext
Executable File
CONTROLLER(3) RSX Framework Manual CONTROLLER(3)
|
|
|
|
NAME
|
|
Controller - RSX request handling and routing system
|
|
|
|
SYNOPSIS
|
|
use App\RSpade\Core\Controller\Rsx_Controller_Abstract;
|
|
|
|
class User_Controller extends Rsx_Controller_Abstract
|
|
{
|
|
#[Auth('Permission::authenticated()')]
|
|
#[Route('/users', methods: ['GET'])]
|
|
public static function index(Request $request, array $params = [])
|
|
{
|
|
return rsx_view('User_List');
|
|
}
|
|
|
|
#[Auth('Permission::authenticated()')]
|
|
#[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 also added to $params:
|
|
/users?sort=name
|
|
$params['sort'] === 'name'
|
|
|
|
REQUIRE ATTRIBUTE
|
|
#[Auth(callable, message, redirect, redirect_to)]
|
|
REQUIRED on all routes. Defines access control check.
|
|
callable: 'Class::method()' string to execute
|
|
message: Optional error message
|
|
redirect: Optional URL to redirect on failure (HTTP only)
|
|
redirect_to: Optional ['Controller', 'action'] (HTTP only)
|
|
|
|
All routes MUST have at least one #[Auth] attribute, either on:
|
|
- The route method itself
|
|
- The controller's pre_dispatch() method (applies to all routes)
|
|
- Both (pre_dispatch Require runs first, then route Require)
|
|
|
|
Multiple #[Auth] attributes are supported - all must pass.
|
|
|
|
Permission Method Contract:
|
|
public static function method_name(Request $request, array $params, ...$args): mixed
|
|
|
|
Returns:
|
|
- true or null: Allow access
|
|
- false: Deny access
|
|
- Response: Custom response (overrides default handling)
|
|
|
|
Examples:
|
|
// Public access
|
|
#[Auth('Permission::anybody()')]
|
|
#[Route('/')]
|
|
public static function index(Request $request, array $params = [])
|
|
{
|
|
return rsx_view('Landing');
|
|
}
|
|
|
|
// Authenticated users only
|
|
#[Auth('Permission::authenticated()',
|
|
message: 'Please log in',
|
|
redirect: '/login')]
|
|
#[Route('/dashboard')]
|
|
public static function dashboard(Request $request, array $params = [])
|
|
{
|
|
return rsx_view('Dashboard');
|
|
}
|
|
|
|
// Redirect using controller/action
|
|
#[Auth('Permission::authenticated()',
|
|
message: 'Login required',
|
|
redirect_to: ['Login_Index_Controller', 'show_login'])]
|
|
#[Route('/profile')]
|
|
public static function profile(Request $request, array $params = [])
|
|
{
|
|
return rsx_view('Profile');
|
|
}
|
|
|
|
// Permission with arguments
|
|
#[Auth('Permission::has_role("admin")')]
|
|
#[Route('/admin')]
|
|
public static function admin_panel(Request $request, array $params = [])
|
|
{
|
|
return rsx_view('Admin_Panel');
|
|
}
|
|
|
|
// Multiple requirements
|
|
#[Auth('Permission::authenticated()')]
|
|
#[Auth('Permission::has_permission("edit_users")')]
|
|
#[Route('/users/edit')]
|
|
public static function edit_users(Request $request, array $params = [])
|
|
{
|
|
return rsx_view('User_Edit');
|
|
}
|
|
|
|
// Controller-wide requirement
|
|
class Admin_Controller extends Rsx_Controller_Abstract
|
|
{
|
|
#[Auth('Permission::has_role("admin")',
|
|
message: 'Admin access required',
|
|
redirect: '/')]
|
|
public static function pre_dispatch(Request $request, array $params = [])
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// All routes in this controller require admin role
|
|
#[Route('/admin/users')]
|
|
public static function users(Request $request, array $params = [])
|
|
{
|
|
return rsx_view('Admin_Users');
|
|
}
|
|
}
|
|
|
|
Creating Permission Methods (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 REQUIRE
|
|
Ajax endpoints also require #[Auth] attributes.
|
|
For Ajax endpoints, redirect parameters are ignored and JSON errors returned:
|
|
|
|
#[Auth('Permission::authenticated()',
|
|
message: 'Login required')]
|
|
#[Ajax_Endpoint]
|
|
public static function get_data(Request $request, array $params = [])
|
|
{
|
|
return ['data' => 'value'];
|
|
}
|
|
|
|
On failure, returns:
|
|
{
|
|
"success": false,
|
|
"error": "Login required",
|
|
"error_type": "permission_denied"
|
|
}
|
|
|
|
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('User_Controller', 'search', args);
|
|
}
|
|
}
|
|
|
|
Stubs included automatically in bundles.
|
|
|
|
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')->url(['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 (!RsxAuth::check()) {
|
|
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 RsxAuth in pre_dispatch:
|
|
|
|
public static function pre_dispatch(Request $request, array $params = [])
|
|
{
|
|
if (!RsxAuth::check()) {
|
|
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
|
|
|
|
Missing #[Auth] attribute error:
|
|
- Add #[Auth('Permission::anybody()')] to route method
|
|
- OR add #[Auth] to pre_dispatch() for controller-wide access
|
|
- Rebuild manifest: php artisan rsx:manifest:build
|
|
|
|
Permission denied (403):
|
|
- Check permission method logic returns true
|
|
- Verify Session::is_logged_in() for authenticated routes
|
|
- Add message parameter for clearer errors
|
|
- Check permission method exists in rsx/permission.php
|
|
|
|
JavaScript stub missing:
|
|
- Ensure Ajax_Endpoint attribute present
|
|
- Ensure #[Auth] attribute present on Ajax method
|
|
- Rebuild manifest: php artisan rsx:manifest:build
|
|
- Check storage/rsx-build/js-stubs/
|
|
|
|
Authentication issues:
|
|
- Implement pre_dispatch hook
|
|
- Use Permission::authenticated() in Require
|
|
- Verify session configuration
|
|
|
|
SEE ALSO
|
|
manifest_api(3), bundle_api(3), jqhtml(3), rsx:routes(1)
|
|
|
|
RSX Framework 2025-09-17 CONTROLLER(3) |