首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 錯誤與異常處理完整指南:錯誤類型、報告級別、日誌記錄、自定義異常、錯誤轉異常與現代最佳實踐

     編輯:PHP 綜合知識

PHP 7+ 引入 Throwable 介面統一 Error 與 Exception,配合 set_error_handler、set_exception_handler、register_shutdown_function,可構建完整的錯誤捕獲、日誌記錄、友善回應機制。本文系統整理從基礎配置到現代框架整合的最佳實踐。

PHP 錯誤體系總覽

Throwable (介面)
├── Error (引擎錯誤,不可捕獲繼續執行)
│   ├── ArithmeticError
│   ├── AssertionError
│   ├── DivisionByZeroError
│   ├── CompileError
│   ├── ParseError
│   └── TypeError
└── Exception (使用者代碼拋出,可捕獲處理)
    ├── LogicException
    │   ├── InvalidArgumentException
    │   ├── DomainException
    │   └── ...
    └── RuntimeException
        ├── OutOfBoundsException
        ├── UnexpectedValueException
        └── ...

錯誤報告級別配置

// 開發環境:報告所有錯誤
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php/error.log');

// 生產環境:隱藏顯示,記錄日誌
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php/error.log');
ini_set('log_errors_max_len', '4096');

// 運行時動態調整
error_reporting(E_ALL); // 當前腳本報告所有

三大核心處理器

1. 錯誤處理器:將錯誤轉為異常

set_error_handler(function (int $severity, string $message, string $file, int $line) {
    // 忽略被 @ 抑制的錯誤
    if (error_reporting() === 0) return false;

    // 映射嚴重度到 ErrorException
    $exception = new \ErrorException($message, 0, $severity, $file, $line);
    throw $exception;
}, E_ALL); // 捕獲所有級別

2. 異常處理器:統一格式化輸出/日誌

set_exception_handler(function (\Throwable $e) {
    // 記錄日誌
    $log = [
        'time' => date('c'),
        'type' => get_class($e),
        'code' => $e->getCode(),
        'message' => $e->getMessage(),
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'trace' => $e->getTraceAsString(),
        'request' => [
            'uri' => $_SERVER['REQUEST_URI'] ?? '',
            'method' => $_SERVER['REQUEST_METHOD'] ?? '',
            'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
        ],
    ];
    error_log(json_encode($log, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n", 3, '/var/log/php/exception.log');

    // 回應客戶端
    if (php_sapi_name() === 'cli') {
        fwrite(STDERR, "{$e->getMessage()}\n{$e->getTraceAsString()}\n");
        exit(1);
    }

    http_response_code($e instanceof \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface ? $e->getStatusCode() : 500);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'error' => [
            'code' => $e->getCode() ?: 500,
            'message' => APP_ENV === 'prod' ? 'Internal Server Error' : $e->getMessage(),
            'debug' => APP_ENV !== 'prod' ? ['file' => $e->getFile(), 'line' => $e->getLine()] : null,
        ],
    ], JSON_UNESCAPED_UNICODE);
});

3. 關閉處理器:捕獲致命錯誤

register_shutdown_function(function () {
    $error = error_get_last();
    if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
        // 模擬異常處理
        $e = new \ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']);
        // 復用異常處理器邏輯
        // ...
    }
});

自定義業務異常體系

namespace App\Exception;

abstract class BusinessException extends \RuntimeException {
    protected int $httpCode = 400;
    public function getHttpCode(): int { return $this->httpCode; }
}

class ValidationException extends BusinessException {
    private array $violations;
    public function __construct(string $message, array $violations = [], int $code = 400) {
        parent::__construct($message, $code);
        $this->violations = $violations;
        $this->httpCode = 422;
    }
    public function getViolations(): array { return $this->violations; }
}

class NotFoundException extends BusinessException {
    public function __construct(string $resource = 'Resource') {
        parent::__construct("{$resource} not found", 404);
        $this->httpCode = 404;
    }
}

class UnauthorizedException extends BusinessException {
    public function __construct(string $message = 'Unauthorized') {
        parent::__construct($message, 401);
        $this->httpCode = 401;
    }
}

控制器中使用

public function show(int $id): JsonResponse {
    $user = $this->userRepository->find($id);
    if (!$user) {
        throw new NotFoundException('User');
    }
    return new JsonResponse($user->toArray());
}

public function update(int $id, Request $request): JsonResponse {
    $data = json_decode($request->getContent(), true);
    $violations = $this->validator->validate($data);
    if ($violations) {
        throw new ValidationException('Invalid input', $violations);
    }
    // ...
}

現代框架整合 (Symfony/Laravel)

  • Symfony:Kernel::handle() 內建 ErrorListener,自動將錯誤轉為 HttpException,透過 kernel.exception 事件分發,TwigBundle 渲染錯誤頁,API 回傳 JSON。配置 debug: false 生產環境隱藏堆疊。
  • Laravel:App\Exceptions\Handler 統一處理,render() 方法判斷異常類型回應 JSON/視圖,report() 送往 Sentry/Flare/日誌。renderable() 註冊自定義異常渲染閉包。

日誌最佳實踐 (PSR-3 + Monolog)

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\JsonFormatter;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\WebProcessor;

$log = new Logger('app');
$handler = new RotatingFileHandler('/var/log/app/app.log', 30, Logger::DEBUG);
$handler->setFormatter(new JsonFormatter());
$handler->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, ['Monolog\\', 'Symfony\\']));
$handler->pushProcessor(new WebProcessor());
$log->pushHandler($handler);

// 使用
$log->info('User logged in', ['user_id' => 123, 'ip' => '1.2.3.4']);
$log->error('Payment failed', ['order_id' => 'ABC', 'exception' => $e]);

避坑清單

  1. set_error_handler 無法捕獲 E_ERROR、E_PARSE、E_CORE_ERROR 等致命錯誤,需靠 register_shutdown_function。
  2. 異常處理器中不可拋出新異常,否則導致「Exception thrown without a stack frame」。
  3. error_log() 類型 1 (郵件) 需配置 sendmail_path,類型 3 (檔案) 需確保目錄可寫。
  4. 生產環境 display_errors=Off,但 log_errors=On。
  5. 自定義異常繼承 RuntimeException (可預期業務錯誤) 或 LogicException (程式邏輯錯誤),不要繼承 Error。

構建健壯的 PHP 錯誤處理體系:錯誤→異常轉換、統一異常處理器、關閉函數捕獲致命錯誤、業務異常分層、PSR-3 結構化日誌、框架原生機制優先。這樣無論是 CLI 腳本、HTTP API、佇列消費者,都能獲得一致的錯誤觀測與恢復能力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved