Files
rspade_system/app/RSpade/man/controller.txt
root 77b4d10af8 Refactor filename naming system and apply convention-based renames
Standardize settings file naming and relocate documentation files
Fix code quality violations from rsx:check
Reorganize user_management directory into logical subdirectories
Move Quill Bundle to core and align with Tom Select pattern
Simplify Site Settings page to focus on core site information
Complete Phase 5: Multi-tenant authentication with login flow and site selection
Add route query parameter rule and synchronize filename validation logic
Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs
Implement filename convention rule and resolve VS Code auto-rename conflict
Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns
Implement RPC server architecture for JavaScript parsing
WIP: Add RPC server infrastructure for JS parsing (partial implementation)
Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation
Add JQHTML-CLASS-01 rule and fix redundant class names
Improve code quality rules and resolve violations
Remove legacy fatal error format in favor of unified 'fatal' error type
Filter internal keys from window.rsxapp output
Update button styling and comprehensive form/modal documentation
Add conditional fly-in animation for modals
Fix non-deterministic bundle compilation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 19:10:02 +00:00

464 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 (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');
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(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 (!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)