Files
rspade_system/app/RSpade/man/controller.txt
root 84ca3dfe42 Fix code quality violations and rename select input components
Move small tasks from wishlist to todo, update npm packages
Replace #[Auth] attributes with manual auth checks and code quality rule
Remove on_jqhtml_ready lifecycle method from framework
Complete ACL system with 100-based role indexing and /dev/acl tester
WIP: ACL system implementation with debug instrumentation
Convert rsx:check JS linting to RPC socket server
Clean up docs and fix $id→$sid in man pages, remove SSR/FPC feature
Reorganize wishlists: priority order, mark sublayouts complete, add email
Update model_fetch docs: mark MVP complete, fix enum docs, reorganize
Comprehensive documentation overhaul: clarity, compression, and critical rules
Convert Contacts/Projects CRUD to Model.fetch() and add fetch_or_null()
Add JS ORM relationship lazy-loading and fetch array handling
Add JS ORM relationship fetching and CRUD documentation
Fix ORM hydration and add IDE resolution for Base_* model stubs
Rename Json_Tree_Component to JS_Tree_Debug_Component and move to framework
Enhance JS ORM infrastructure and add Json_Tree class name badges

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 21:39:43 +00:00

446 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;
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)