api-error-handling

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API 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:
  • references/
    - Complete PHP classes (ApiResponse, ExceptionHandler, Exceptions)
  • examples/
    - Full endpoint implementation, frontend client
为PHP REST API实现一套全面的标准化错误响应系统,包含统一的JSON响应格式、特定错误信息提取以及SweetAlert2集成。
核心原则:
  • 所有端点使用统一的JSON响应格式
  • 从所有异常类型中提取特定错误信息
  • 为所有错误类别匹配合适的HTTP状态码
  • 提供机器可读的错误码以支持程序化处理
  • 提供适合SweetAlert2展示的人性化可读信息
  • 安全的错误处理机制(生产环境不泄露堆栈跟踪信息)
  • 带请求ID的全面日志记录
  • 关键要求:始终在SweetAlert中向用户展示错误信息(绝不静默失败)
安全基线(必填): 处理PHP API工作时,务必加载并应用Vibe Security Skill。禁止在响应或日志中泄露敏感数据。
子目录内容说明:
  • references/
    - 完整的PHP类(ApiResponse、ExceptionHandler、Exceptions)
  • 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状态码对应表

StatusError TypeUse CaseError Code Examples
400Bad RequestMalformed JSON, missing paramsINVALID_JSON, MISSING_PARAMETER
401UnauthorizedMissing/invalid auth tokenTOKEN_MISSING, TOKEN_EXPIRED
403ForbiddenValid auth but no permissionPERMISSION_DENIED, ACCESS_FORBIDDEN
404Not FoundResource doesn't existRESOURCE_NOT_FOUND, INVOICE_NOT_FOUND
405Method Not AllowedWrong HTTP methodMETHOD_NOT_ALLOWED
409ConflictBusiness rule violationALREADY_EXISTS, OVERPAYMENT
422Unprocessable EntityValidation errorsVALIDATION_FAILED, INVALID_EMAIL
429Too Many RequestsRate limitingRATE_LIMIT_EXCEEDED
500Internal Server ErrorUnexpected errorsINTERNAL_ERROR, DATABASE_ERROR
503Service UnavailableMaintenance/overloadSERVICE_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
references/ApiResponse.php
for complete implementation
php
// 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.php
php
// 成功响应
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
references/ExceptionHandler.php
for complete implementation
Key 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
references/CustomExceptions.php
for all classes
php
// 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.php
php
// 验证错误(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
references/bootstrap.php
for complete file
Include 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
examples/InvoicesEndpoint.php
for complete example
php
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.php
php
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
examples/ApiClient.js
for complete implementation
javascript
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.js
javascript
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, hideLoading

Critical 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
    response.success
    before proceeding
  • ✅ Show error message in SweetAlert (never silent failure)
  • ✅ Extract message from
    response.message
    or
    error.responseJSON.message
  • ✅ Close any open modals before showing error
  • ✅ Use
    await
    to ensure user sees error before code continues
强制要求:所有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
    not a function
    errors in JS, trace the full chain: service return → API wrapper → JS consumer.
  • Services may intentionally wrap arrays (e.g.,
    { sales: [...] }
    ) for mobile consumers while web UI expects a flat array.
  • Normalize defensively when response shape may vary:
javascript
const rows = Array.isArray(data)
  ? data
  : Array.isArray(data?.sales)
    ? data.sales
    : [];
  • 当JS中出现
    not a function
    错误时,追踪完整调用链:服务返回值 → API包装层 → JS消费者。
  • 服务可能会特意将数组包装(例如
    { sales: [...] }
    )以适配移动端消费者,而Web界面可能期望扁平数组。
  • 当响应结构可能变化时,要进行防御性标准化:
javascript
const rows = Array.isArray(data)
  ? data
  : Array.isArray(data?.sales)
    ? data.sales
    : [];

API Contract Validation (Frontend-Backend Alignment)

API契约验证(前后端对齐)

See
references/contract-validation.md
for complete guide
Critical 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 (
    $_GET
    vs
    $input
    ) AND where frontend sends it (URL vs body)."
完整指南请查看
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读取位置(
    $_GET
    vs
    $input
    )和前端发送位置(URL vs 请求体)。"

Error Message Extraction

错误信息提取示例

SQLSTATE 45000 (Trigger):
sql
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Overpayment not allowed';
-- Extracted: "Overpayment not allowed" → Code: OVERPAYMENT_NOT_ALLOWED
SQLSTATE 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 503
SQLSTATE 45000(触发器):
sql
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Overpayment not allowed';
-- 提取结果: "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"
死锁:
"Deadlock found when trying to get lock..."
-- 提取结果: "Database conflict. Please try again." → HTTP 503

Validation 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
    .invalid-feedback
    elements
后端:
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
    ApiResponse
    helper
  • 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
    APP_DEBUG=true
    for detailed errors
  • 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:
  1. Include
    bootstrap.php
    at top of all endpoints
  2. Use
    ApiResponse
    helper for all responses
  3. Throw custom exceptions for specific errors
  4. Let
    ExceptionHandler
    convert to JSON
  5. Use
    ApiClient
    class on frontend
  6. Display all errors via SweetAlert2
Key Files:
  • references/ApiResponse.php
    - Response helper
  • references/ExceptionHandler.php
    - Exception converter
  • references/CustomExceptions.php
    - Exception classes
  • references/bootstrap.php
    - API setup
  • examples/InvoicesEndpoint.php
    - Complete endpoint
  • examples/ApiClient.js
    - Frontend client
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.
实现步骤:
  1. 在所有端点顶部引入
    bootstrap.php
  2. 所有响应使用
    ApiResponse
    工具类
  3. 针对特定错误抛出自定义异常
  4. ExceptionHandler
    转换为JSON响应
  5. 前端使用
    ApiClient
  6. 所有错误通过SweetAlert2展示
核心文件:
  • references/ApiResponse.php
    - 响应工具类
  • references/ExceptionHandler.php
    - 异常转换器
  • references/CustomExceptions.php
    - 异常类
  • references/bootstrap.php
    - API初始化文件
  • examples/InvoicesEndpoint.php
    - 完整端点示例
  • examples/ApiClient.js
    - 前端客户端示例
优势:
  • 所有端点使用统一的错误格式
  • 从数据库异常中提取特定信息
  • 使用SweetAlert2实现美观的错误展示
  • 带请求ID的错误便于调试
  • 安全(无敏感数据泄露)
  • 对开发者友好(清晰的实现模式)
谨记: 所有错误信息必须适合直接在SweetAlert2中展示。请为终端用户编写信息,而非开发者。