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>
433 lines
13 KiB
Plaintext
Executable File
433 lines
13 KiB
Plaintext
Executable File
AJAX ERROR HANDLING
|
|
===================
|
|
|
|
OVERVIEW
|
|
|
|
RSpade implements a standardized Ajax error handling architecture that:
|
|
- Returns HTTP 200 for ALL Ajax responses (success and errors)
|
|
- Encodes success/failure in response body via _success field
|
|
- Differentiates between business logic errors and fatal PHP errors
|
|
- Provides consistent error display across dev and production modes
|
|
- Prepares for future batch request architecture
|
|
|
|
RESPONSE FORMATS
|
|
|
|
All Ajax endpoints return HTTP 200 with one of these formats:
|
|
|
|
Success Response
|
|
----------------
|
|
{
|
|
"_success": true,
|
|
"_ajax_return_value": { /* developer's return value */ },
|
|
"console_debug": [ /* optional debug messages */ ]
|
|
}
|
|
|
|
Business Logic Errors (Validation, Auth)
|
|
-----------------------------------------
|
|
{
|
|
"_success": false,
|
|
"error_type": "response_form_error|response_auth_required|response_unauthorized",
|
|
"reason": "User-friendly message",
|
|
"details": { /* optional, e.g., field-specific validation errors */ },
|
|
"console_debug": [ /* optional debug messages */ ]
|
|
}
|
|
|
|
Fatal PHP Errors (Uncaught Exceptions)
|
|
---------------------------------------
|
|
{
|
|
"_success": false,
|
|
"error_type": "fatal",
|
|
"error": {
|
|
"file": "path/to/file.php",
|
|
"line": 123,
|
|
"error": "Full exception message with SQL/context",
|
|
"backtrace": [
|
|
{
|
|
"file": "path/to/file.php",
|
|
"line": 456,
|
|
"function": "methodName",
|
|
"class": "ClassName",
|
|
"type": "->"
|
|
},
|
|
/* ... up to 10 frames */
|
|
]
|
|
},
|
|
"console_debug": [ /* optional debug messages */ ]
|
|
}
|
|
|
|
Network/Infrastructure Errors
|
|
------------------------------
|
|
No response body - PHP never executed
|
|
xhr.status === 0 or timeout
|
|
Only occurs when nginx/PHP-FPM is down
|
|
|
|
HTTP STATUS CODE STRATEGY
|
|
|
|
ALL Ajax Responses: HTTP 200
|
|
----------------------------
|
|
- Success: HTTP 200 with _success: true
|
|
- Validation errors: HTTP 200 with _success: false
|
|
- Fatal PHP errors: HTTP 200 with _success: false
|
|
- Auth errors: HTTP 200 with _success: false
|
|
|
|
Non-200 Responses
|
|
-----------------
|
|
Only when server infrastructure is completely down (nginx crashed,
|
|
PHP-FPM dead, network failure). These bypass PHP entirely.
|
|
|
|
Rationale
|
|
---------
|
|
- Batch requests need uniform status codes (some succeed, some fail)
|
|
- Always get parseable response body
|
|
- Simpler client handling - check _success field, not HTTP status
|
|
- Non-200 only means "couldn't reach PHP at all"
|
|
|
|
ERROR TYPES
|
|
|
|
fatal
|
|
-----
|
|
Uncaught PHP exceptions, database errors, bugs in code.
|
|
Dev mode: Shows file/line/error/backtrace
|
|
Prod mode: Generic "An error occurred" message
|
|
|
|
response_form_error (validation)
|
|
--------------------------------
|
|
Validation failures with field-specific details.
|
|
Used via: return response_form_error($reason, $details);
|
|
Example:
|
|
return response_form_error('Validation failed', [
|
|
'email' => 'Email address is required',
|
|
'phone' => 'Invalid phone number format'
|
|
]);
|
|
|
|
response_auth_required
|
|
----------------------
|
|
User not logged in, needs authentication.
|
|
Used via: return response_auth_required($reason, $redirect);
|
|
|
|
response_unauthorized
|
|
---------------------
|
|
User logged in but lacks permission for this action.
|
|
Used via: return response_unauthorized($reason, $redirect);
|
|
|
|
network
|
|
-------
|
|
Client-side only. Server unreachable (xhr.status === 0).
|
|
|
|
SERVER-SIDE IMPLEMENTATION
|
|
|
|
Ajax Endpoints Return Plain Data
|
|
---------------------------------
|
|
Endpoint methods return plain arrays/objects. Framework wraps:
|
|
|
|
// Your endpoint
|
|
#[Ajax_Endpoint]
|
|
public static function get_user(Request $request, array $params = []): array
|
|
{
|
|
$user = User::find($params['id']);
|
|
|
|
if (!$user) {
|
|
return response_form_error('User not found', ['id' => 'Invalid']);
|
|
}
|
|
|
|
return [
|
|
'id' => $user->id,
|
|
'name' => $user->name,
|
|
'email' => $user->email
|
|
];
|
|
}
|
|
|
|
// Framework wraps response:
|
|
{
|
|
"_success": true,
|
|
"_ajax_return_value": {
|
|
"id": 123,
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
}
|
|
|
|
Never Return _success Field Manually
|
|
-------------------------------------
|
|
The _success field is RESERVED for framework wrapping. If your endpoint
|
|
returns an array with a boolean _success key, the framework throws an error:
|
|
|
|
// ❌ WRONG - Framework throws exception
|
|
return ['_success' => true, 'data' => $user];
|
|
|
|
// ✅ CORRECT - Let framework wrap
|
|
return ['data' => $user];
|
|
|
|
Exception Handling - Let It Bubble
|
|
-----------------------------------
|
|
Don't wrap database/framework operations in try/catch. Let exceptions
|
|
bubble to the global handler which formats them properly.
|
|
|
|
// ❌ WRONG
|
|
try {
|
|
$user->save();
|
|
} catch (Exception $e) {
|
|
return ['_success' => false, 'message' => $e->getMessage()];
|
|
}
|
|
|
|
// ✅ CORRECT
|
|
$user->save(); // Let it throw - framework catches and formats
|
|
|
|
When Try/Catch IS Appropriate
|
|
------------------------------
|
|
Use try/catch for EXPECTED failures:
|
|
- File uploads (may fail validation)
|
|
- External API calls (network issues expected)
|
|
- User input parsing (malformed data expected)
|
|
|
|
Don't use for:
|
|
- Database operations (schema errors are bugs)
|
|
- Framework operations (let global handler catch)
|
|
- "Just in case" wrapping
|
|
|
|
CLIENT-SIDE IMPLEMENTATION
|
|
|
|
Ajax.js Automatically Unwraps Responses
|
|
----------------------------------------
|
|
When you call an Ajax endpoint, Ajax.js:
|
|
1. Makes HTTP request
|
|
2. Receives 200 response with JSON body
|
|
3. Checks _success field:
|
|
- _success: true → Resolve promise with _ajax_return_value
|
|
- _success: false → Reject promise with Error object
|
|
4. Network errors also reject with Error object
|
|
|
|
Exception-Driven Pattern
|
|
------------------------
|
|
Ajax calls work like any async operation:
|
|
- Success: Promise resolves with unwrapped value
|
|
- Failure: Promise rejects with Error object
|
|
|
|
try {
|
|
const user = await User_Controller.get_user(123);
|
|
// user is the _ajax_return_value, already unwrapped
|
|
console.log(user.name);
|
|
} catch (error) {
|
|
// error is standard Error object with extra properties
|
|
console.error(error.message); // User-displayable message
|
|
}
|
|
|
|
Error Object Structure
|
|
----------------------
|
|
Rejected promises receive standard Error objects with additional properties:
|
|
|
|
error.type - 'fatal'|'form_error'|'auth_required'|
|
|
'unauthorized'|'network'
|
|
error.message - User-displayable message
|
|
error.details - Full error data from server
|
|
(for form_error: field-specific errors)
|
|
(for fatal: file/line/backtrace)
|
|
|
|
Automatic Error Handling
|
|
------------------------
|
|
Uncaught Ajax errors automatically display via Modal.error():
|
|
|
|
// No try/catch - error shows in modal automatically
|
|
const user = await User_Controller.get_user(123);
|
|
|
|
Global unhandled rejection handler detects uncaught Ajax promises and
|
|
shows red danger modal with error details. This prevents silent failures.
|
|
|
|
Rsx_Form Error Handling
|
|
-----------------------
|
|
Rsx_Form components have built-in error handling:
|
|
|
|
const result = await Modal.form({
|
|
title: 'Add User',
|
|
component: 'Add_User_Form',
|
|
on_submit: async (form) => {
|
|
try {
|
|
const values = form.vals();
|
|
const result = await Controller.add_user(values);
|
|
return result; // Success - close modal
|
|
} catch (error) {
|
|
await form.render_error(error);
|
|
return false; // Keep modal open
|
|
}
|
|
}
|
|
});
|
|
|
|
Rsx_Form.render_error() handles all error types:
|
|
- form_error: Shows inline field errors + unmatched errors alert
|
|
- fatal/network/auth: Shows error in form's error container
|
|
|
|
Form_Utils Error Display
|
|
------------------------
|
|
For non-Rsx_Form forms, use Form_Utils.apply_form_errors():
|
|
|
|
try {
|
|
const result = await Controller.save_user(form_data);
|
|
await Modal.alert('User saved!');
|
|
} catch (error) {
|
|
if (error.type === 'form_error') {
|
|
await Form_Utils.apply_form_errors($form, error.details);
|
|
} else {
|
|
Rsx.render_error(error, '#error_container');
|
|
}
|
|
}
|
|
|
|
Form_Utils.apply_form_errors() automatically:
|
|
- Matches errors to fields by name attribute
|
|
- Adds .is-invalid class and inline error text
|
|
- Shows alert ONLY for errors that couldn't match to fields
|
|
- No duplicate alerts for matched fields
|
|
|
|
Generic Error Display
|
|
---------------------
|
|
Rsx.render_error() displays errors in any container:
|
|
|
|
// Display in specific container
|
|
Rsx.render_error(error, '#error_container');
|
|
|
|
// Display in form's error container (for Rsx_Form use form.render_error())
|
|
Rsx.render_error(error, this.$id('error'));
|
|
|
|
Handles all error types:
|
|
- fatal: Shows file:line and full message
|
|
- form_error: Shows bulleted list of unmatched validation errors
|
|
- auth/network: Shows user-friendly message
|
|
- generic: Shows error message in danger alert
|
|
|
|
Modal Error Display
|
|
-------------------
|
|
For critical errors that need immediate attention:
|
|
|
|
await Modal.error(error, 'Critical Error');
|
|
|
|
- Shows red danger modal
|
|
- Can stack over other open modals
|
|
- Used automatically for uncaught Ajax errors
|
|
- Displays error message in monospace pre format
|
|
|
|
DEVELOPER VS PRODUCTION MODE
|
|
|
|
Developer Mode (IS_DEVELOPER=true)
|
|
-----------------------------------
|
|
Fatal errors show:
|
|
- Full exception message
|
|
- File path and line number
|
|
- SQL queries with parameter values
|
|
- Stack trace (up to 10 frames)
|
|
- Console debug messages
|
|
|
|
Example display:
|
|
Fatal Error
|
|
|
|
SQLSTATE[23000]: Integrity constraint violation: 1048
|
|
Column 'login_user_id' cannot be null (Connection: mysql,
|
|
SQL: insert into `users` ...)
|
|
|
|
vendor/laravel/framework/src/Illuminate/Database/Connection.php:829
|
|
|
|
Production Mode (IS_DEVELOPER=false)
|
|
-------------------------------------
|
|
Fatal errors show:
|
|
- Generic message: "An unexpected error occurred. Please try
|
|
again or contact support."
|
|
- No file paths, SQL, or technical details
|
|
- Errors logged server-side for admin review
|
|
|
|
Validation/auth errors show same user-friendly messages in both modes.
|
|
|
|
BATCH REQUESTS (FUTURE)
|
|
|
|
When batch requests are implemented, response format:
|
|
|
|
{
|
|
"batch_results": [
|
|
{"_success": true, "_ajax_return_value": {...}},
|
|
{"_success": false, "error_type": "validation", "reason": "...", "details": {...}},
|
|
{"_success": true, "_ajax_return_value": {...}},
|
|
{"_success": false, "error_type": "fatal", "error": {...}}
|
|
]
|
|
}
|
|
|
|
HTTP status: Always 200
|
|
Each result: Has own _success field
|
|
Client: Process each result individually
|
|
|
|
TESTING
|
|
|
|
Test Success Response
|
|
---------------------
|
|
php artisan rsx:ajax Controller action --args='{"id":1}'
|
|
|
|
Test Validation Error
|
|
---------------------
|
|
php artisan rsx:ajax Controller action --args='{"id":""}'
|
|
|
|
Test Fatal Error
|
|
----------------
|
|
Trigger exception in endpoint (e.g., invalid SQL, missing file)
|
|
|
|
Test Network Error
|
|
------------------
|
|
Stop PHP-FPM/nginx, try Ajax call from browser
|
|
|
|
CONSOLE DEBUG MESSAGES
|
|
|
|
Add debug output to Ajax responses:
|
|
|
|
console_debug('channel', $var1, $var2);
|
|
|
|
In response:
|
|
{
|
|
"_success": true,
|
|
"_ajax_return_value": {...},
|
|
"console_debug": [
|
|
["channel", [arg1, arg2]],
|
|
["log", ["message"]]
|
|
]
|
|
}
|
|
|
|
JavaScript automatically logs these to browser console.
|
|
|
|
COMMON PATTERNS
|
|
|
|
Simple Data Retrieval
|
|
----------------------
|
|
#[Ajax_Endpoint]
|
|
public static function get_data(Request $request, array $params = []): array
|
|
{
|
|
return ['items' => Item::all()->toArray()];
|
|
}
|
|
|
|
Validation with Response Helper
|
|
--------------------------------
|
|
#[Ajax_Endpoint]
|
|
public static function save(Request $request, array $params = []): array
|
|
{
|
|
if (empty($params['email'])) {
|
|
return response_form_error('Validation failed', [
|
|
'email' => 'Email is required'
|
|
]);
|
|
}
|
|
|
|
$user = User::create($params);
|
|
return ['id' => $user->id];
|
|
}
|
|
|
|
Let Database Errors Bubble
|
|
---------------------------
|
|
#[Ajax_Endpoint]
|
|
public static function save(Request $request, array $params = []): array
|
|
{
|
|
$user = new User();
|
|
$user->name = $params['name'];
|
|
$user->save(); // If this fails, exception bubbles naturally
|
|
|
|
return ['id' => $user->id];
|
|
}
|
|
|
|
SEE ALSO
|
|
|
|
php artisan rsx:man ajax - Ajax system overview
|
|
php artisan rsx:man jqhtml - Component system
|
|
php artisan rsx:man modals - Modal dialogs
|
|
php artisan rsx:man error_handling - General error handling
|