Files
rspade_system/app/RSpade/man/ajax_error_handling.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

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.$sid('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