PHP 5 引入物件導向異常處理,取代傳統錯誤回呼。正確使用 try-catch、自訂異常類別、異常鏈與全域處理器,能建構健壯、可維護的應用程式。本文透過六個遞進範例完整展示。
<?php
function file_open($path) {
if (!file_exists($path)) {
throw new Exception("檔案不存在", 1);
}
if (!fopen($path, 'r')) {
throw new Exception("檔案無法開啟", 2);
}
return true;
}
$path = 'D:\\in.txt';
try {
file_open($path);
} catch (Exception $e) {
echo "異常訊息: {$e->getMessage()}\n";
}
?>
<?php
// 無 try-catch,異常會冒泡至全域,若無處理器則 Fatal Error
file_open('D:\\in.txt'); // 拋出異常
echo "這行不會執行"; // 程式已中止
?>
<?php
function exception_handler($e) {
echo "未捕獲異常: {$e->getMessage()}\n";
// 記錄日誌、發送通知、顯示友善錯誤頁
}
set_exception_handler('exception_handler');
// 此處拋出的異常會被 exception_handler 捕獲
throw new Exception("測試全域處理器");
?>
<?php
try {
file_open('D:\\in.txt');
} catch (Exception $e) {
echo "訊息: {$e->getMessage()}\n";
echo "代碼: {$e->getCode()}\n";
echo "檔案: {$e->getFile()}\n";
echo "行號: {$e->getLine()}\n";
echo "堆疊追蹤:\n{$e->getTraceAsString()}\n";
}
?>
依異常類型分流處理,符合開閉原則:
<?php
class FileNotFoundException extends Exception {}
class FileNotReadableException extends Exception {}
function file_open($path) {
if (!file_exists($path)) throw new FileNotFoundException("檔案不存在: $path", 1);
if (!is_readable($path)) throw new FileNotReadableException("檔案不可讀: $path", 2);
return fopen($path, 'r');
}
try {
file_open('D:\\in.txt');
} catch (FileNotFoundException $e) {
echo "請確認檔案路徑: {$e->getMessage()}";
} catch (FileNotReadableException $e) {
echo "請檢查檔案權限: {$e->getMessage()}";
} catch (Exception $e) {
echo "未知錯誤: {$e->getMessage()}";
}
?>
<?php
// ❌ 錯誤順序:Exception 在前,子類別永不被捕獲
try { file_open('x'); }
catch (Exception $e) { echo "父類別"; }
catch (FileNotFoundException $e) { echo "子類別 - 永遠不會執行"; }
// ✅ 正確順序:具體在前,一般在後
try { file_open('x'); }
catch (FileNotFoundException $e) { echo "子類別"; }
catch (Exception $e) { echo "父類別"; }
?>
底層捕獲後記錄日誌,再拋出原異常或包裝新異常:
<?php
function file_open($path) {
try {
if (!file_exists($path)) throw new FileNotFoundException("不存在", 1);
if (!is_readable($path)) throw new FileNotReadableException("不可讀", 2);
} catch (Exception $e) {
error_log("file_open 內部錯誤: {$e->getMessage()}");
throw $e; // 重拋原異常,保留堆疊
// 或: throw new RuntimeException("開啟失敗", 0, $e); // 包裝 (PHP 5.3+)
}
}
?>
| 原則 | 說明 |
|---|---|
| 僅捕獲可處理的異常 | 不知如何處理時勿捕獲,讓上層或全域處理器處理 |
| catch 區塊保持精簡 | 記錄、轉換、重拋;避免複雜業務邏輯 |
| 保留原始堆疊 | 重拋用 throw $e;,包裝用 throw new NewEx(..., 0, $e) |
| 自訂異常繼承 Exception | 不要繼承 Error (PHP 7 內部錯誤) |
| 程式碼異常 vs 邏輯異常 | 前者用 RuntimeException 系,後者用 LogicException 系 |
| 不要用異常控制流程 | 預期情況 (如查無資料) 用回傳值/空物件,非真正錯誤 |
PHP 7 引入 Throwable 介面,Exception 與 Error 皆實作之。捕獲所有錯誤:
try {
// 可能拋出 Exception 或 Error (TypeError, ParseError 等)
} catch (Throwable $t) {
// 處理所有可擲出物件
}