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>
7.9 KiB
Executable File
Ajax - Unified AJAX Handling System
Overview
The Ajax class consolidates all AJAX-related functionality in one place, providing clear methods for different types of AJAX operations:
- Internal calls - Server-side PHP calling API methods directly
- Browser requests - Handling incoming AJAX requests from JavaScript
- Response modes - Managing JSON vs HTML error responses
- Future expansion - External API calls, WebSocket handling
Method Overview
Core Methods
// Internal server-side API calls (no HTTP overhead)
Ajax::internal($controller, $action, $params = [], $auth = [])
// Handle incoming browser AJAX requests (/_ajax route)
Ajax::handle_browser_request($request, $params)
// Set response mode for error handlers
Ajax::set_ajax_response_mode(bool $enabled)
// Check current response mode
Ajax::is_ajax_response_mode()
// Backward compatibility (deprecated)
Ajax::call() // Use internal() instead
Usage Examples
Internal Server-Side Calls
use App\RSpade\Core\Ajax\Ajax;
// Call an API method internally from PHP code
$result = Ajax::internal('User_Controller', 'get_profile', ['user_id' => 123]);
This is useful for:
- Service-to-service communication within the application
- Background jobs that need to call API methods
- Testing API methods without HTTP overhead
- Batch processing operations
How It Works
- Controller Discovery: Uses the Manifest to find the controller class by name
- Validation: Verifies that:
- The controller exists and extends
Rsx_Controller - The method exists and has the
Ajax_Endpointannotation
- The controller exists and extends
- Execution:
- Calls
pre_dispatch()if it exists on the controller - If pre_dispatch returns null, calls the actual method
- Calls
- Response Handling:
- Normal responses are filtered through JSON encode/decode to remove PHP objects
- Special response types throw specific exceptions
Browser AJAX Requests
The /_ajax/:controller/:action route is handled by Internal_Api::dispatch(), which delegates to Ajax::handle_browser_request(). This provides a consistent JSON response format for browser JavaScript code.
Calling from JavaScript
The RSX framework automatically generates JavaScript stub classes for PHP controllers with Ajax_Endpoint methods. This enables clean, type-hinted API calls from JavaScript:
// Auto-generated stub enables this clean syntax:
const result = await Demo_Index_Controller.hello_world();
console.log(result); // "Hello, world!"
// With parameters:
const profile = await User_Controller.get_profile(123);
console.log(profile.name);
The JavaScript stubs are:
- Auto-generated during manifest build for any controller with
Ajax_Endpointmethods - Included automatically in bundles when the PHP controller is referenced
- Use the
Ajax.call()method internally to make the actual AJAX request - Support arbitrary parameters via rest parameters (
...args)
Behind the scenes, the stub translates to:
class Demo_Index_Controller {
static async hello_world(...args) {
return Ajax.call(Rsx.Route('Demo_Index_Controller', 'hello_world'), args);
}
}
Note: Rsx.Route() generates type-safe URLs like /_ajax/Demo_Index_Controller/hello_world.
Response Format
Success responses:
{
"_success": true,
"_ajax_return_value": { /* method return value */ },
"console_debug": [ /* optional debug messages */ ]
}
Error responses:
{
"_success": false,
"error_type": "response_form_error",
"reason": "Validation failed",
"details": { /* error details */ }
}
Exception Handling
For internal calls (Ajax::internal()), the method throws specific exceptions for different error scenarios:
Authentication Required
use App\RSpade\Core\Ajax\Exceptions\AjaxAuthRequiredException;
try {
$result = Ajax::internal('Protected_Controller', 'secure_method');
} catch (AjaxAuthRequiredException $e) {
// Handle authentication requirement
echo "Auth required: " . $e->getMessage();
}
Unauthorized Access
use App\RSpade\Core\Ajax\Exceptions\AjaxUnauthorizedException;
try {
$result = Ajax::internal('Admin_Controller', 'admin_action');
} catch (AjaxUnauthorizedException $e) {
// Handle authorization failure
echo "Unauthorized: " . $e->getMessage();
}
Form Validation Errors
use App\RSpade\Core\Ajax\Exceptions\AjaxFormErrorException;
try {
$result = Ajax::internal('User_Controller', 'update_profile', $data);
} catch (AjaxFormErrorException $e) {
// Extract error details
$message = $e->getMessage();
$details = $e->get_details(); // Array of validation errors
foreach ($details as $field => $error) {
echo "Field $field: $error\n";
}
}
Fatal Errors
use App\RSpade\Core\Ajax\Exceptions\AjaxFatalErrorException;
try {
$result = Ajax::internal('Some_Controller', 'problematic_method');
} catch (AjaxFatalErrorException $e) {
// Handle fatal error
error_log("Fatal error: " . $e->getMessage());
}
Creating API Methods
To make a method callable via Ajax::internal(), it must:
- Be in a controller that extends
Rsx_Controller - Have the
Ajax_Endpointannotation - Accept
Request $requestandarray $params = []as parameters
Example:
class User_Controller extends Rsx_Controller
{
#[Ajax_Endpoint]
public static function get_profile(Request $request, array $params = [])
{
$user_id = $params['user_id'] ?? null;
if (!$user_id) {
return response_form_error('User ID required', ['user_id' => 'Missing']);
}
$user = User::find($user_id);
if (!$user) {
return response_form_error('User not found', ['user_id' => 'Invalid']);
}
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email
];
}
}
Response Filtering
Normal responses (arrays, objects) are automatically filtered through json_decode(json_encode()) to:
- Remove PHP object instances
- Convert objects to associative arrays
- Ensure only serializable data is returned
This prevents memory leaks and ensures clean data structures.
Special Response Types
The following helper functions create special responses that are handled differently:
response_auth_required($reason, $redirect)- ThrowsAjaxAuthRequiredExceptionresponse_unauthorized($reason, $redirect)- ThrowsAjaxUnauthorizedExceptionresponse_form_error($reason, $details)- ThrowsAjaxFormErrorExceptionwith extractable detailsresponse_fatal_error($reason, $details)- ThrowsAjaxFatalErrorException
Security Considerations
- Internal calls (
Ajax::internal()) bypass HTTP middleware, so authentication/authorization must be handled in the controller methods - Browser requests (
handle_browser_request()) go through normal HTTP middleware - Always validate input parameters in your API methods
- Use
pre_dispatch()in controllers for common security checks
Testing
The Ajax class is ideal for testing API methods:
class UserApiTest extends TestCase
{
public function test_get_profile()
{
$result = Ajax::internal('User_Controller', 'get_profile', ['user_id' => 1]);
$this->assertArrayHasKey('id', $result);
$this->assertArrayHasKey('name', $result);
$this->assertArrayHasKey('email', $result);
}
public function test_get_profile_missing_user()
{
$this->expectException(AjaxFormErrorException::class);
Ajax::internal('User_Controller', 'get_profile', ['user_id' => 999999]);
}
}
Limitations
- The
$authparameter is reserved for future implementation of authentication context switching - Methods must be static (following the RSX pattern)
- Only works with controllers in the manifest scan directories