api-error-handling
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAPI Error Handling
API错误处理
Implement comprehensive, standardized error response system for PHP REST APIs with consistent JSON envelopes, specific error message extraction, and SweetAlert2 integration.
Core Principles:
- Consistent JSON envelope across all endpoints
- Specific error messages extracted from all exception types
- Appropriate HTTP status codes for all error categories
- Machine-readable error codes for programmatic handling
- Human-readable messages for SweetAlert2 display
- Secure error handling (no stack traces in production)
- Comprehensive logging with request IDs
- CRITICAL: Always show error messages to users in SweetAlert (never silent failures)
Security Baseline (Required): Always load and apply the Vibe Security Skill for PHP API work. Do not leak sensitive data in responses or logs.
See subdirectories for:
- - Complete PHP classes (ApiResponse, ExceptionHandler, Exceptions)
references/ - - Full endpoint implementation, frontend client
examples/
为PHP REST API实现一套全面的标准化错误响应系统,包含统一的JSON响应格式、特定错误信息提取以及SweetAlert2集成。
核心原则:
- 所有端点使用统一的JSON响应格式
- 从所有异常类型中提取特定错误信息
- 为所有错误类别匹配合适的HTTP状态码
- 提供机器可读的错误码以支持程序化处理
- 提供适合SweetAlert2展示的人性化可读信息
- 安全的错误处理机制(生产环境不泄露堆栈跟踪信息)
- 带请求ID的全面日志记录
- 关键要求:始终在SweetAlert中向用户展示错误信息(绝不静默失败)
安全基线(必填): 处理PHP API工作时,务必加载并应用Vibe Security Skill。禁止在响应或日志中泄露敏感数据。
子目录内容说明:
- - 完整的PHP类(ApiResponse、ExceptionHandler、Exceptions)
references/ - - 完整的端点实现、前端客户端示例
examples/
Response Envelope Standard
响应格式标准
Success:
json
{
"success": true,
"data": {
/* payload */
},
"message": "Optional success message",
"meta": {
"timestamp": "2026-01-24T10:30:00Z",
"request_id": "req_abc123"
}
}Error:
json
{
"success": false,
"message": "Human-readable error for SweetAlert2",
"error": {
"code": "ERROR_CODE",
"type": "validation_error",
"details": {
/* field-specific errors */
}
},
"meta": {
"timestamp": "2026-01-24T10:30:00Z",
"request_id": "req_abc123"
}
}成功响应:
json
{
"success": true,
"data": {
/* 负载数据 */
},
"message": "可选的成功提示信息",
"meta": {
"timestamp": "2026-01-24T10:30:00Z",
"request_id": "req_abc123"
}
}错误响应:
json
{
"success": false,
"message": "供SweetAlert2展示的人性化错误信息",
"error": {
"code": "ERROR_CODE",
"type": "validation_error",
"details": {
/* 字段级错误详情 */
}
},
"meta": {
"timestamp": "2026-01-24T10:30:00Z",
"request_id": "req_abc123"
}
}HTTP Status Codes
HTTP状态码对应表
| Status | Error Type | Use Case | Error Code Examples |
|---|---|---|---|
| 400 | Bad Request | Malformed JSON, missing params | INVALID_JSON, MISSING_PARAMETER |
| 401 | Unauthorized | Missing/invalid auth token | TOKEN_MISSING, TOKEN_EXPIRED |
| 403 | Forbidden | Valid auth but no permission | PERMISSION_DENIED, ACCESS_FORBIDDEN |
| 404 | Not Found | Resource doesn't exist | RESOURCE_NOT_FOUND, INVOICE_NOT_FOUND |
| 405 | Method Not Allowed | Wrong HTTP method | METHOD_NOT_ALLOWED |
| 409 | Conflict | Business rule violation | ALREADY_EXISTS, OVERPAYMENT |
| 422 | Unprocessable Entity | Validation errors | VALIDATION_FAILED, INVALID_EMAIL |
| 429 | Too Many Requests | Rate limiting | RATE_LIMIT_EXCEEDED |
| 500 | Internal Server Error | Unexpected errors | INTERNAL_ERROR, DATABASE_ERROR |
| 503 | Service Unavailable | Maintenance/overload | SERVICE_UNAVAILABLE, DEADLOCK |
| 状态码 | 错误类型 | 使用场景 | 错误码示例 |
|---|---|---|---|
| 400 | 错误请求 | JSON格式错误、参数缺失 | INVALID_JSON, MISSING_PARAMETER |
| 401 | 未授权 | 缺失/无效的认证令牌 | TOKEN_MISSING, TOKEN_EXPIRED |
| 403 | 禁止访问 | 认证有效但无权限 | PERMISSION_DENIED, ACCESS_FORBIDDEN |
| 404 | 资源不存在 | 请求的资源不存在 | RESOURCE_NOT_FOUND, INVOICE_NOT_FOUND |
| 405 | 方法不允许 | 使用了错误的HTTP方法 | METHOD_NOT_ALLOWED |
| 409 | 冲突 | 违反业务规则 | ALREADY_EXISTS, OVERPAYMENT |
| 422 | 无法处理的实体 | 验证错误 | VALIDATION_FAILED, INVALID_EMAIL |
| 429 | 请求过于频繁 | 触发速率限制 | RATE_LIMIT_EXCEEDED |
| 500 | 内部服务器错误 | 意外错误 | INTERNAL_ERROR, DATABASE_ERROR |
| 503 | 服务不可用 | 维护中/系统过载 | SERVICE_UNAVAILABLE, DEADLOCK |
ApiResponse Helper (Quick Reference)
ApiResponse工具类(速查)
See for complete implementation
references/ApiResponse.phpphp
// Success responses
ApiResponse::success($data, $message, $status);
ApiResponse::created($data, $message);
// Error responses
ApiResponse::error($message, $code, $status, $type, $details);
ApiResponse::validationError($errors, $message);
ApiResponse::notFound($resource, $identifier);
ApiResponse::unauthorized($message);
ApiResponse::forbidden($permission);
ApiResponse::conflict($message, $code);
ApiResponse::methodNotAllowed($allowedMethods);
ApiResponse::rateLimited($retryAfter);
ApiResponse::serverError($message);
ApiResponse::serviceUnavailable($message);完整实现请查看
references/ApiResponse.phpphp
// 成功响应
ApiResponse::success($data, $message, $status);
ApiResponse::created($data, $message);
// 错误响应
ApiResponse::error($message, $code, $status, $type, $details);
ApiResponse::validationError($errors, $message);
ApiResponse::notFound($resource, $identifier);
ApiResponse::unauthorized($message);
ApiResponse::forbidden($permission);
ApiResponse::conflict($message, $code);
ApiResponse::methodNotAllowed($allowedMethods);
ApiResponse::rateLimited($retryAfter);
ApiResponse::serverError($message);
ApiResponse::serviceUnavailable($message);Exception Handler (Quick Reference)
异常处理器(速查)
See for complete implementation
references/ExceptionHandler.phpKey Features:
- Extracts specific messages from PDOException
- Parses SQLSTATE 45000 (user-defined exceptions from triggers)
- Extracts constraint violation messages
- Handles deadlocks gracefully
- Logs all errors with request ID
- Hides stack traces in production
PDOException Parsing:
php
// SQLSTATE 45000: Business rule from trigger
// "SQLSTATE[45000]: <<1>>: 1644 Overpayment not allowed"
// Extracted: "Overpayment not allowed"
// SQLSTATE 23000: Duplicate entry
// "Duplicate entry 'john@example.com' for key 'uk_email'"
// Extracted: "A record with this Email already exists: 'john@example.com'"
// SQLSTATE 23000: Foreign key violation
// "Cannot delete or update a parent row..."
// Extracted: "Referenced Customer does not exist or cannot be deleted"完整实现请查看
references/ExceptionHandler.php核心功能:
- 从PDOException中提取特定信息
- 解析SQLSTATE 45000(触发器抛出的用户自定义异常)
- 提取约束违例信息
- 优雅处理死锁问题
- 记录所有带请求ID的错误
- 生产环境隐藏堆栈跟踪信息
PDOException解析示例:
php
// SQLSTATE 45000: 触发器定义的业务规则
// "SQLSTATE[45000]: <<1>>: 1644 Overpayment not allowed"
// 提取结果: "Overpayment not allowed"
// SQLSTATE 23000: 重复条目
// "Duplicate entry 'john@example.com' for key 'uk_email'"
// 提取结果: "A record with this Email already exists: 'john@example.com'"
// SQLSTATE 23000: 外键违例
// "Cannot delete or update a parent row..."
// 提取结果: "Referenced Customer does not exist or cannot be deleted"Custom Exception Classes
自定义异常类
See for all classes
references/CustomExceptions.phpphp
// Validation (422)
throw new ValidationException([
'email' => 'Invalid email format',
'phone' => 'Phone number required'
], 'Validation failed');
// Authentication (401)
throw new AuthenticationException('Token expired', 'expired');
// Authorization (403)
throw new AuthorizationException('MANAGE_USERS');
// Not Found (404)
throw new NotFoundException('Invoice', 'INV-123');
// Conflict (409)
throw new ConflictException('Already voided', 'ALREADY_VOIDED');
// Rate Limit (429)
throw new RateLimitException(60);所有异常类请查看
references/CustomExceptions.phpphp
// 验证错误(422)
throw new ValidationException([
'email' => 'Invalid email format',
'phone' => 'Phone number required'
], 'Validation failed');
// 认证错误(401)
throw new AuthenticationException('Token expired', 'expired');
// 授权错误(403)
throw new AuthorizationException('MANAGE_USERS');
// 资源不存在(404)
throw new NotFoundException('Invoice', 'INV-123');
// 冲突错误(409)
throw new ConflictException('Already voided', 'ALREADY_VOIDED');
// 速率限制(429)
throw new RateLimitException(60);API Bootstrap Pattern
API初始化模式
See for complete file
references/bootstrap.phpInclude at top of all API endpoints:
php
require_once __DIR__ . '/bootstrap.php';
// Helper functions available:
require_method(['POST', 'PUT']);
$data = read_json_body();
validate_required($data, ['customer_id', 'amount']);
$db = get_db();
$token = bearer_token();
require_auth();
require_permission('MANAGE_INVOICES');
handle_request(function() { /* endpoint logic */ });完整文件请查看
references/bootstrap.php在所有API端点的顶部引入:
php
require_once __DIR__ . '/bootstrap.php';
// 可用的工具函数:
require_method(['POST', 'PUT']);
$data = read_json_body();
validate_required($data, ['customer_id', 'amount']);
$db = get_db();
$token = bearer_token();
require_auth();
require_permission('MANAGE_INVOICES');
handle_request(function() { /* 端点逻辑 */ });Endpoint Implementation Pattern
端点实现模式
See for complete example
examples/InvoicesEndpoint.phpphp
require_once __DIR__ . '/../bootstrap.php';
use App\Http\ApiResponse;
use App\Http\Exceptions\{NotFoundException, ValidationException};
require_auth();
handle_request(function() {
$method = $_SERVER['REQUEST_METHOD'];
match ($method) {
'POST' => handlePost(),
default => ApiResponse::methodNotAllowed('POST')
};
});
function handlePost(): void {
$data = read_json_body();
validate_required($data, ['customer_id', 'items']);
if (empty($data['items'])) {
throw new ValidationException(['items' => 'Required']);
}
// Business logic...
ApiResponse::created(['id' => $id], 'Created successfully');
}完整示例请查看
examples/InvoicesEndpoint.phpphp
require_once __DIR__ . '/../bootstrap.php';
use App\Http\ApiResponse;
use App\Http\Exceptions\{NotFoundException, ValidationException};
require_auth();
handle_request(function() {
$method = $_SERVER['REQUEST_METHOD'];
match ($method) {
'POST' => handlePost(),
default => ApiResponse::methodNotAllowed('POST')
};
});
function handlePost(): void {
$data = read_json_body();
validate_required($data, ['customer_id', 'items']);
if (empty($data['items'])) {
throw new ValidationException(['items' => 'Required']);
}
// 业务逻辑...
ApiResponse::created(['id' => $id], 'Created successfully');
}Frontend Integration
前端集成
See for complete implementation
examples/ApiClient.jsjavascript
const api = new ApiClient("./api");
// GET - Errors automatically shown via SweetAlert2
const response = await api.get("invoices.php", { status: "pending" });
if (response) renderInvoices(response.data);
// POST - Validation errors highlight form fields
showLoading("Creating...");
const result = await api.post("invoices.php", formData);
hideLoading();
if (result) showSuccess("Created successfully");
// DELETE - With confirmation
const { value: reason } = await Swal.fire({
title: "Void?",
input: "textarea",
showCancelButton: true,
});
if (reason) {
const res = await api.delete(`invoices.php?id=${id}`, { reason });
if (res) showSuccess("Voided");
}
// Helpers: showSuccess/Error/Warning/Info, showConfirm, showLoading, hideLoading完整实现请查看
examples/ApiClient.jsjavascript
const api = new ApiClient("./api");
// GET请求 - 错误将自动通过SweetAlert2展示
const response = await api.get("invoices.php", { status: "pending" });
if (response) renderInvoices(response.data);
// POST请求 - 验证错误将高亮对应表单字段
showLoading("Creating...");
const result = await api.post("invoices.php", formData);
hideLoading();
if (result) showSuccess("Created successfully");
// DELETE请求 - 带确认弹窗
const { value: reason } = await Swal.fire({
title: "Void?",
input: "textarea",
showCancelButton: true,
});
if (reason) {
const res = await api.delete(`invoices.php?id=${id}`, { reason });
if (res) showSuccess("Voided");
}
// 工具函数: showSuccess/Error/Warning/Info, showConfirm, showLoading, hideLoadingCritical Error Display Pattern
关键错误展示模式
MANDATORY: Always show API errors to users in SweetAlert2
javascript
try {
const response = await $.ajax({
url: "./api/endpoint.php?action=verify",
method: "POST",
contentType: "application/json",
data: JSON.stringify({ id: 123 }),
});
// Check response.success BEFORE using data
if (!response.success) {
await Swal.fire({
icon: "error",
title: "Operation Failed",
text: response.message || "An error occurred",
confirmButtonText: "OK",
});
return; // Stop execution
}
// Success path
await Swal.fire({
icon: "success",
title: "Success!",
text: response.message || "Operation completed",
});
} catch (error) {
// Extract error message from different formats
let errorMessage = "An unexpected error occurred";
if (error.responseJSON && error.responseJSON.message) {
errorMessage = error.responseJSON.message; // API error message
} else if (error.message) {
errorMessage = error.message; // JavaScript error
}
await Swal.fire({
icon: "error",
title: "Error",
text: errorMessage,
confirmButtonText: "OK",
});
}Key Points:
- ✅ Always check before proceeding
response.success - ✅ Show error message in SweetAlert (never silent failure)
- ✅ Extract message from or
response.messageerror.responseJSON.message - ✅ Close any open modals before showing error
- ✅ Use to ensure user sees error before code continues
await
强制要求:所有API错误必须通过SweetAlert2展示给用户
javascript
try {
const response = await $.ajax({
url: "./api/endpoint.php?action=verify",
method: "POST",
contentType: "application/json",
data: JSON.stringify({ id: 123 }),
});
// 使用数据前务必检查response.success
if (!response.success) {
await Swal.fire({
icon: "error",
title: "操作失败",
text: response.message || "发生了一个错误",
confirmButtonText: "确定",
});
return; // 终止后续执行
}
// 成功流程
await Swal.fire({
icon: "success",
title: "成功!",
text: response.message || "操作完成",
});
} catch (error) {
// 从不同格式中提取错误信息
let errorMessage = "发生了一个意外错误";
if (error.responseJSON && error.responseJSON.message) {
errorMessage = error.responseJSON.message; // API返回的错误信息
} else if (error.message) {
errorMessage = error.message; // JavaScript错误
}
await Swal.fire({
icon: "error",
title: "错误",
text: errorMessage,
confirmButtonText: "确定",
});
}关键点:
- ✅ 始终先检查 再使用数据
response.success - ✅ 通过SweetAlert展示错误信息(绝不静默失败)
- ✅ 从 或
response.message提取错误信息error.responseJSON.message - ✅ 展示错误前关闭所有打开的模态框
- ✅ 使用 确保用户看到错误后再继续执行代码
await
Debugging Data Shape Mismatches
数据结构不匹配调试
- When you see errors in JS, trace the full chain: service return → API wrapper → JS consumer.
not a function - Services may intentionally wrap arrays (e.g., ) for mobile consumers while web UI expects a flat array.
{ sales: [...] } - Normalize defensively when response shape may vary:
javascript
const rows = Array.isArray(data)
? data
: Array.isArray(data?.sales)
? data.sales
: [];- 当JS中出现 错误时,追踪完整调用链:服务返回值 → API包装层 → JS消费者。
not a function - 服务可能会特意将数组包装(例如 )以适配移动端消费者,而Web界面可能期望扁平数组。
{ sales: [...] } - 当响应结构可能变化时,要进行防御性标准化:
javascript
const rows = Array.isArray(data)
? data
: Array.isArray(data?.sales)
? data.sales
: [];API Contract Validation (Frontend-Backend Alignment)
API契约验证(前后端对齐)
See for complete guide
references/contract-validation.mdCritical Pattern: Always validate that frontend data matches backend API requirements. Missing fields cause 400 errors that are difficult to debug without detailed error messages.
Common Scenario:
javascript
// Frontend sends incomplete data
{ agent_id: 2, payment_method: "Cash", amount: 10000 }
// API expects (but doesn't communicate):
{ agent_id: 2, sales_point_id: 5, remittance_date: "2026-02-10", ... }
// Result: Generic 400 Bad Request ❌Solution: Field-Specific Validation
php
// Backend: Return specific errors (HTTP 422)
$errors = [];
if (empty($input['sales_point_id'])) {
$errors['sales_point_id'] = 'Sales point ID is required';
}
if (empty($input['remittance_date'])) {
$errors['remittance_date'] = 'Remittance date is required';
}
if (!empty($errors)) {
ResponseHandler::validationError($errors);
}Quick Checklist:
- ✅ Document required fields in endpoint comments
- ✅ Return field-specific errors (HTTP 422, not 400)
- ✅ Validate frontend data before API call
- ✅ Match parameter names exactly (snake_case consistency)
- ✅ Log validation failures with request context
- ✅ Action in query string, payload in JSON body (see below)
Action Parameter Location:
javascript
// ✅ CORRECT: Action in URL
url: "./api/endpoint.php?action=create",
data: JSON.stringify({ agent_id: 2, amount: 10000 })
// ❌ WRONG: Action in body
url: "./api/endpoint.php",
data: JSON.stringify({ action: "create", agent_id: 2 })Why: Query string for routing, JSON body for payload. Makes URLs clear, enables caching, readable logs.
Debugging: Check browser console → network tab → PHP error log → compare contracts → test with curl
Key Takeaways:
- "Always validate API requirements against the frontend data being sent. Generic 400 errors without field details make debugging exponentially harder."
- "Use query string for action/method routing, JSON body for request payload. Check BOTH where API reads it (vs
$_GET) AND where frontend sends it (URL vs body)."$input
完整指南请查看
references/contract-validation.md关键模式: 始终验证前端数据是否符合后端API要求。缺失字段会导致400错误,且如果没有详细错误信息,调试会非常困难。
常见场景:
javascript
// 前端发送不完整数据
{ agent_id: 2, payment_method: "Cash", amount: 10000 }
// API期望的字段(但未明确告知):
{ agent_id: 2, sales_point_id: 5, remittance_date: "2026-02-10", ... }
// 结果: 通用400错误 ❌解决方案:字段级验证
php
// 后端:返回特定字段错误(HTTP 422)
$errors = [];
if (empty($input['sales_point_id'])) {
$errors['sales_point_id'] = 'Sales point ID is required';
}
if (empty($input['remittance_date'])) {
$errors['remittance_date'] = 'Remittance date is required';
}
if (!empty($errors)) {
ResponseHandler::validationError($errors);
}快速检查清单:
- ✅ 在端点注释中记录必填字段
- ✅ 返回字段级错误(使用HTTP 422,而非400)
- ✅ API调用前先验证前端数据
- ✅ 严格匹配参数名称(保持snake_case一致性)
- ✅ 记录带请求上下文的验证失败日志
- ✅ 操作类型放在查询字符串中,负载放在JSON请求体中(见下文)
操作参数位置规范:
javascript
// ✅ 正确:操作类型在URL中
url: "./api/endpoint.php?action=create",
data: JSON.stringify({ agent_id: 2, amount: 10000 })
// ❌ 错误:操作类型在请求体中
url: "./api/endpoint.php",
data: JSON.stringify({ action: "create", agent_id: 2 })原因: 查询字符串用于路由,JSON体用于负载。使URL更清晰,支持缓存,日志可读性更高。
调试步骤: 检查浏览器控制台 → 网络标签 → PHP错误日志 → 对比契约 → 使用curl测试
核心要点:
- "始终验证API要求与前端发送的数据是否匹配。没有字段详情的通用400错误会让调试难度呈指数级上升。"
- "使用查询字符串进行操作/方法路由,JSON请求体传递请求负载。同时检查API读取位置(vs
$_GET)和前端发送位置(URL vs 请求体)。"$input
Error Message Extraction
错误信息提取示例
SQLSTATE 45000 (Trigger):
sql
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Overpayment not allowed';
-- Extracted: "Overpayment not allowed" → Code: OVERPAYMENT_NOT_ALLOWEDSQLSTATE 23000 (Duplicate):
"Duplicate entry 'john@example.com' for key 'uk_email'"
-- Extracted: "A record with this Email already exists: 'john@example.com'"SQLSTATE 23000 (Foreign Key):
"Cannot delete or update a parent row..."
-- Extracted: "Referenced Customer does not exist or cannot be deleted"Deadlock:
"Deadlock found when trying to get lock..."
-- Extracted: "Database conflict. Please try again." → HTTP 503SQLSTATE 45000(触发器):
sql
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Overpayment not allowed';
-- 提取结果: "Overpayment not allowed" → 错误码: OVERPAYMENT_NOT_ALLOWEDSQLSTATE 23000(重复条目):
"Duplicate entry 'john@example.com' for key 'uk_email'"
-- 提取结果: "A record with this Email already exists: 'john@example.com'"SQLSTATE 23000(外键违例):
"Cannot delete or update a parent row..."
-- 提取结果: "Referenced Customer does not exist or cannot be deleted"死锁:
"Deadlock found when trying to get lock..."
-- 提取结果: "Database conflict. Please try again." → HTTP 503Validation Pattern
验证模式
Backend:
php
$errors = [];
if (empty($data['email'])) $errors['email'] = 'Email required';
elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL))
$errors['email'] = 'Invalid email';
if ($errors) ApiResponse::validationError($errors);Frontend automatically:
- SweetAlert2 with formatted error list
- Highlights fields with
.is-invalid - Adds elements
.invalid-feedback
后端:
php
$errors = [];
if (empty($data['email'])) $errors['email'] = 'Email required';
elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL))
$errors['email'] = 'Invalid email';
if ($errors) ApiResponse::validationError($errors);前端自动处理:
- 带格式化错误列表的SweetAlert2弹窗
- 为错误字段添加 类以高亮
.is-invalid - 添加 元素展示错误信息
.invalid-feedback
Business Rule Pattern
业务规则实现模式
php
$stmt = $db->prepare("SELECT status FROM invoices WHERE id = ? FOR UPDATE");
$stmt->execute([$id]);
$invoice = $stmt->fetch();
if (!$invoice) throw new NotFoundException('Invoice', $id);
if ($invoice['status'] === 'voided')
throw new ConflictException('Already voided', 'ALREADY_VOIDED');
if ($invoice['status'] === 'paid')
throw new ConflictException('Cannot void paid invoice', 'INVOICE_PAID');php
$stmt = $db->prepare("SELECT status FROM invoices WHERE id = ? FOR UPDATE");
$stmt->execute([$id]);
$invoice = $stmt->fetch();
if (!$invoice) throw new NotFoundException('Invoice', $id);
if ($invoice['status'] === 'voided')
throw new ConflictException('Already voided', 'ALREADY_VOIDED');
if ($invoice['status'] === 'paid')
throw new ConflictException('Cannot void paid invoice', 'INVOICE_PAID');Logging Pattern
日志记录模式
All errors logged with context:
[req_abc123] PDOException: SQLSTATE[45000]: Overpayment not allowed in /api/payments.php:45
Context: {"user_id":123,"franchise_id":5,"url":"/api/payments.php","method":"POST"}所有错误都会附带上下文信息记录:
[req_abc123] PDOException: SQLSTATE[45000]: Overpayment not allowed in /api/payments.php:45
Context: {"user_id":123,"franchise_id":5,"url":"/api/payments.php","method":"POST"}Implementation Checklist
实现检查清单
Backend:
- All endpoints use helper
ApiResponse - All exceptions caught by
ExceptionHandler - Specific error codes for each error type
- PDOException messages properly extracted
- Trigger errors parsed (SQLSTATE 45000)
- Constraint violations give meaningful messages
- Stack traces hidden in production
- All errors logged with request ID
- HTTP status codes match error types
- Bootstrap file included in all endpoints
Frontend:
- All API calls use centralized
ApiClient - Errors displayed via SweetAlert2 only (no native alert/confirm)
- Validation errors highlight form fields
- Network errors handled gracefully
- Loading states shown during requests
- Error codes displayed in footer
- Offline state detected and handled
- Request IDs logged for debugging
Database:
- Triggers use SQLSTATE 45000 for business rules
- Clear, user-friendly error messages in triggers
- Constraints have descriptive names (uk_email_franchise)
- Foreign key errors give context
后端:
- 所有端点使用 工具类
ApiResponse - 所有异常由 捕获
ExceptionHandler - 为每种错误类型定义特定错误码
- 正确提取PDOException信息
- 解析触发器错误(SQLSTATE 45000)
- 约束违例返回有意义的信息
- 生产环境隐藏堆栈跟踪
- 所有错误带请求ID记录
- HTTP状态码与错误类型匹配
- 所有端点引入初始化文件
前端:
- 所有API调用使用集中式
ApiClient - 仅通过SweetAlert2展示错误(不使用原生alert/confirm)
- 验证错误高亮对应表单字段
- 优雅处理网络错误
- 请求过程中展示加载状态
- 页脚展示错误码
- 检测并处理离线状态
- 记录请求ID用于调试
数据库:
- 触发器使用SQLSTATE 45000定义业务规则
- 触发器中使用清晰、用户友好的错误信息
- 约束使用描述性名称(如uk_email_franchise)
- 外键错误提供上下文信息
Quick Error Reference
错误速查示例
php
// 400 - Bad Request
ApiResponse::error('Invalid request', 'BAD_REQUEST', 400);
// 401 - Unauthorized
ApiResponse::unauthorized('Session expired');
// 403 - Forbidden
ApiResponse::forbidden('MANAGE_USERS');
// 404 - Not Found
ApiResponse::notFound('Invoice', 'INV-123');
// 409 - Conflict
ApiResponse::conflict('Already voided', 'ALREADY_VOIDED');
// 422 - Validation
ApiResponse::validationError(['email' => 'Invalid format']);
// 500 - Server Error
ApiResponse::serverError('Unexpected error occurred');php
// 400 - 错误请求
ApiResponse::error('Invalid request', 'BAD_REQUEST', 400);
// 401 - 未授权
ApiResponse::unauthorized('Session expired');
// 403 - 禁止访问
ApiResponse::forbidden('MANAGE_USERS');
// 404 - 资源不存在
ApiResponse::notFound('Invoice', 'INV-123');
// 409 - 冲突
ApiResponse::conflict('Already voided', 'ALREADY_VOIDED');
// 422 - 验证错误
ApiResponse::validationError(['email' => 'Invalid format']);
// 500 - 内部服务器错误
ApiResponse::serverError('Unexpected error occurred');Security Considerations
安全注意事项
Production:
- Never expose stack traces
- Sanitize database error messages
- Log full errors server-side only
- Generic messages for unexpected errors
Development:
- Set for detailed errors
APP_DEBUG=true - Stack traces in logs
- Actual error messages displayed
Sensitive Data:
- Never include passwords in logs
- Sanitize SQL queries in error messages
- Remove file paths from production errors
生产环境:
- 绝不暴露堆栈跟踪信息
- 清理数据库错误信息
- 仅在服务器端记录完整错误
- 意外错误返回通用信息
开发环境:
- 设置 以查看详细错误
APP_DEBUG=true - 日志中记录堆栈跟踪
- 展示实际错误信息
敏感数据:
- 日志中绝不包含密码
- 清理错误信息中的SQL查询
- 生产环境错误中移除文件路径
Summary
总结
Implementation:
- Include at top of all endpoints
bootstrap.php - Use helper for all responses
ApiResponse - Throw custom exceptions for specific errors
- Let convert to JSON
ExceptionHandler - Use class on frontend
ApiClient - Display all errors via SweetAlert2
Key Files:
- - Response helper
references/ApiResponse.php - - Exception converter
references/ExceptionHandler.php - - Exception classes
references/CustomExceptions.php - - API setup
references/bootstrap.php - - Complete endpoint
examples/InvoicesEndpoint.php - - Frontend client
examples/ApiClient.js
Benefits:
- Consistent error format across all endpoints
- Specific messages from database exceptions
- Beautiful error display with SweetAlert2
- Easy debugging with request IDs
- Secure (no sensitive data exposure)
- Developer-friendly (clear patterns)
Remember: All error messages must be suitable for direct display in SweetAlert2. Write them for end users, not developers.
实现步骤:
- 在所有端点顶部引入
bootstrap.php - 所有响应使用 工具类
ApiResponse - 针对特定错误抛出自定义异常
- 由 转换为JSON响应
ExceptionHandler - 前端使用 类
ApiClient - 所有错误通过SweetAlert2展示
核心文件:
- - 响应工具类
references/ApiResponse.php - - 异常转换器
references/ExceptionHandler.php - - 异常类
references/CustomExceptions.php - - API初始化文件
references/bootstrap.php - - 完整端点示例
examples/InvoicesEndpoint.php - - 前端客户端示例
examples/ApiClient.js
优势:
- 所有端点使用统一的错误格式
- 从数据库异常中提取特定信息
- 使用SweetAlert2实现美观的错误展示
- 带请求ID的错误便于调试
- 安全(无敏感数据泄露)
- 对开发者友好(清晰的实现模式)
谨记: 所有错误信息必须适合直接在SweetAlert2中展示。请为终端用户编写信息,而非开发者。