錯誤分類與顯示設定
<?php
// 開發環境:顯示所有錯誤
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
// 生產環境:記錄不顯示
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php/error.log');
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
原則: 開發全開、生產全記錄、永不對用戶顯示堆疊追蹤。
五大常見錯誤類型與修正
1. 白屏 (White Screen of Death)
現象: 瀏覽器完全空白、無回應、View Source 也是空。
| 原因 | 排查步驟 | 修正 |
| 致命錯誤 + display_errors=Off | 檢查 PHP 錯誤日誌 tail -f /var/log/php/error.log | 修正程式碼、開啟 display_errors 除錯 |
| 語法錯誤 (Parse Error) | 日誌會顯示行號:Parse error: syntax error, unexpected '}' in ... | 修正語法、檢查括號配對、分號遺漏 |
| 記憶體耗盡 | 日誌:Allowed memory size of 134217728 bytes exhausted | 優化演算法、分批處理、調大 memory_limit |
| 無限迴圈/遞迴爆棧 | CPU 100%、請求超時、日誌無輸出 | 加入迴圈計數器、遞迴深度限制、Xdebug 追蹤 |
| 輸出緩衝區過大未 flush | ob_start() 但未 ob_end_flush() | 確保正確結束緩衝、或關閉輸出緩衝 |
2. 語法錯誤
method() // 缺少分號
function foo($a, $b = 1, $c) {} // 有預設值參數必須在最後
class A { use T1, T2 { T1::foo insteadof T2; } } // Trait 衝突解決語法錯誤
// 工具:PHP lint 快速檢查
php -l your_file.php
3. 變數未定義 / 型別錯誤
| 現象 | 原因 | 修正 |
| Notice: Undefined variable $x | 使用前未賦值 | $x = $x ?? 'default' 或 isset($x) 判斷 |
Notice: Undefined array key "name" | 陣列無此鍵 | $arr['name'] ?? '' 或 array_key_exists |
| Warning: Undefined property | 物件無此屬性 | 檢查類別定義、用 property_exists |
| TypeError: Argument must be array, string given | 傳參型別不符 | 加入型別宣告、驗證輸入、PHPStan 靜態分析 |
| Fatal: Call to member function on null | 物件為 null 呼叫方法 | $obj?->method() (PHP 8) 或 if ($obj) 守衛 |
4. 編碼亂碼
exec("SET NAMES utf8mb4");
// 3. HTML 輸出
header('Content-Type: text/html; charset=utf-8');
echo '<meta charset="utf-8">';
// 4. 檔案編碼
// 編輯器存檔為 UTF-8 無 BOM
// 5. 表單提交
// <form accept-charset="utf-8">
// 偵測工具
mb_detect_encoding($str, ['UTF-8', 'GBK', 'Big5'], true);
bin2hex($str) // 查看原始位元組
5. SQL 錯誤與注入風險
prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute([$name]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 除錯:查看實際 SQL
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 或開啟 MySQL 一般查詢日誌
// SET GLOBAL general_log = 'ON';
除錯工具鏈
| 工具 | 用途 | 安裝/使用 |
| Xdebug 3 | 斷點、堆疊、變數檢查、程式碼覆蓋率 | pecl install xdebug + IDE 設定 (PhpStorm/VS Code) |
| PHPStan / Psalm | 靜態分析、型別錯誤、死碼偵測 | composer require --dev phpstan/phpstan |
| var_dump / dd() | 快速印出變數結構 | Laravel dump()、Symfony VarDumper |
| ErrorException | 將錯誤轉為例外統一捕獲 | set_error_handler(fn($s,$m,$f,$l) => throw new ErrorException($m,0,$s,$f,$l)) |
| Monolog | 結構化日誌、多通道、Slack/Email 告警 | composer require monolog/monolog |
生產環境錯誤處理模式
<?php
// bootstrap.php
set_error_handler(function($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) return; // 忽略被抑制的錯誤
throw new ErrorException($message, 0, $severity, $file, $line);
});
set_exception_handler(function(Throwable $e) {
// 記錄完整堆疊
error_log(json_encode([
'time' => date('c'),
'type' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
], JSON_UNESCAPED_UNICODE));
// 回傳友善頁面
http_response_code(500);
if (php_sapi_name() === 'cli') {
echo "Error: {$e->getMessage()}\n";
} else {
// 區分 AJAX/一般請求
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
header('Content-Type: application/json');
echo json_encode(['error' => '伺服器內部錯誤,請稍後再試']);
} else {
include __DIR__ . '/templates/500.php';
}
}
exit;
});
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// 捕獲致命錯誤
$e = new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
// 交給 exception handler
// (需自行實作或使用框架內建)
}
});
總結
- 錯誤報告層級:開發 E_ALL、生產記錄不顯示。
- 白屏:看日誌、開 display_errors、檢查記憶體/迴圈。
- 語法錯誤:
php -l、IDE 即時提示、CI 整合 lint。
- 變數未定義:
??、isset、型別宣告、PHPStan。
- 編碼亂碼:PHP/DB/HTML/檔案/表單全鏈路 UTF-8。
- SQL 注入:永遠用預處理、
ERRMODE_EXCEPTION 捕獲錯誤。
- 建立全域例外處理器 + 結構化日誌,生產環境可觀測、可追蹤、可告警。