try catch 是 PHP 接住執行期錯誤的主要方式:把可能出錯的程式碼放進 try,用 catch 依例外類型決定怎麼回應,必要時用 finally 做清理。這篇從 php try catch 最小語法出發,一路講到 Throwable、自訂 Exception、PDO 實戰與常見踩坑。
讀完後,你應該能寫出可運作的 try catch finally、分辨 Exception 與 Error 的差異,並知道哪些函式失敗時不會自動拋例外。
語法速查
try {
// 可能拋出例外的程式碼
} catch (SpecificException $e) {
// 處理特定例外
} catch (Throwable $e) {
// 兜底:任何 Error 或 Exception
} finally {
// 無論成功失敗都會執行(關連線、釋放資源)
}| 關鍵字 | 用途 |
|---|---|
throw new Exception('msg') | 主動拋出例外 |
catch (Type $e) | 依類型接住 |
finally | 清理資源(可省略) |
Throwable | PHP 7+ 同時涵蓋 Error 與 Exception |
目錄
PHP try catch 最小範例
下面是最常見的寫法:在 try 呼叫可能失敗的函式,用 catch 接住特定例外:
<?php
function divide(int $dividend, int $divisor): float
{
if ($divisor === 0) {
throw new InvalidArgumentException('除數不能是 0。');
}
return $dividend / $divisor;
}
try {
echo divide(10, 2); // 5
} catch (InvalidArgumentException $e) {
echo '輸入無效:' . $e->getMessage();
}
throw 之後,同一區塊中尚未執行的程式碼會被跳過,PHP 會往外尋找第一個符合類型的 catch。若一路找不到,例外就會繼續往呼叫堆疊上層傳遞,最後可能成為未捕獲例外。
加上 finally 後,無論成功或失敗都會執行清理:
<?php
try {
echo divide(10, 2);
} catch (InvalidArgumentException $e) {
echo '輸入無效:' . $e->getMessage();
} finally {
echo PHP_EOL . '這段清理流程一定會執行。';
}
Exception、Error 與 Throwable 有什麼差?
PHP 中可以被 throw 拋出、再由 catch 接住的物件,必須實作 Throwable 介面:
| 類型 | 代表什麼 | catch (Exception $e) 能接住嗎? |
|---|---|---|
Exception | 應用程式或函式庫預期可處理的例外 | ✅ |
Error | PHP 執行期錯誤(如型別錯誤、部分算術錯誤) | ❌ |
Throwable | 兩者共同的上層介面 | ✅(兩者皆可) |
因此,catch (Exception $e) 不會接住 Error。若需要同時處理兩者,可以捕獲 Throwable;不過,這不代表所有錯誤都適合被吃掉。語法錯誤、設定問題或程式設計錯誤,通常應該在開發階段被找出,而不是在生產環境偷偷假裝沒事。
實務建議:
- 業務邏輯:優先
catch具體的Exception子類別。 - 應用程式邊界(API 回應、全域錯誤處理):可考慮
catch (Throwable $e)記錄未預期錯誤。
檔案操作不一定會自動拋出例外
這是初學者很容易踩到的坑:並非所有 PHP 函式失敗時都會拋出 Exception。例如 fopen() 找不到檔案時,通常會回傳 false 並產生警告;單純把它放進 try,不會自動變成可捕獲的例外。
如果你的程式需要用例外流程處理這種失敗,就要明確檢查回傳值,再自行拋出例外:
<?php
function openFile(string $path)
{
$handle = fopen($path, 'r');
if ($handle === false) {
throw new RuntimeException('無法開啟檔案:' . $path);
}
return $handle;
}
$handle = null;
try {
$handle = openFile(__DIR__ . '/example.txt');
$content = stream_get_contents($handle);
} catch (RuntimeException $exception) {
echo '檔案處理失敗:' . $exception->getMessage();
} finally {
if (is_resource($handle)) {
fclose($handle);
}
}
重點不是把每個警告都硬轉成例外,而是先確認你使用的 API 如何回報失敗,再選擇一致的處理方式。
建立自訂 Exception
當程式只丟出一個通用的 Exception,呼叫端往往只能看訊息猜發生什麼事。建立有語意的例外類別,可以讓 catch 更精準,也讓日後搜尋記錄檔容易很多。
<?php
class FileNotFoundException extends RuntimeException
{
}
function readConfig(string $path): string
{
if (!is_file($path)) {
throw new FileNotFoundException('找不到設定檔:' . $path);
}
$config = file_get_contents($path);
if ($config === false) {
throw new RuntimeException('無法讀取設定檔:' . $path);
}
return $config;
}
try {
$config = readConfig(__DIR__ . '/config.php');
} catch (FileNotFoundException $exception) {
echo '設定檔不存在,請檢查部署內容。';
}
如果需要保留較低層的原因,可以在建立新例外時傳入上一個例外(例外鏈結):
<?php
class DatabaseException extends RuntimeException
{
}
try {
throw new PDOException('連線被拒絕');
} catch (PDOException $exception) {
throw new DatabaseException('無法完成資料庫操作。', 0, $exception);
}
上層可以用 getMessage() 取得對使用者較安全的訊息,也可以用 getPrevious() 追查原始原因。請避免直接把資料庫帳號、密碼、完整 SQL 或堆疊資訊顯示給使用者。
多個 catch:順序就是優先權
一個 try 後面可以接多個 catch。PHP 會從上到下比對,執行第一個符合的區塊。因此,具體的例外要放前面,較一般的類型放後面:
<?php
try {
// 可能拋出不同類型的 Throwable
} catch (FileNotFoundException $exception) {
echo '找不到檔案。';
} catch (RuntimeException $exception) {
echo '執行時發生可預期的問題。';
} catch (Throwable $exception) {
echo '發生未預期的錯誤。';
}
若不同例外需要完全相同的處理方式,也可以使用聯合捕獲語法(PHP 8.0+):
<?php
try {
// 可能拋出這兩類例外
} catch (InvalidArgumentException | UnexpectedValueException $exception) {
echo '收到無效的資料。';
}
實用案例:包裝 PDO 例外
以下用新增書籍的流程示範例外分層。範例明確設定 PDO 使用例外模式,避免把不同的錯誤回報方式混在一起:
<?php
class DatabaseException extends RuntimeException
{
}
function addBook(PDO $db, string $title, string $author): void
{
try {
$statement = $db->prepare(
'INSERT INTO books (title, author) VALUES (:title, :author)'
);
$statement->execute([
'title' => $title,
'author' => $author,
]);
} catch (PDOException $exception) {
throw new DatabaseException('新增書籍失敗。', 0, $exception);
}
}
try {
$db = new PDO(
'mysql:host=localhost;dbname=library;charset=utf8mb4',
$username,
$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]
);
addBook($db, '1984', 'George Orwell');
echo '書籍新增成功。';
} catch (DatabaseException $exception) {
echo '目前無法新增書籍,請稍後再試。';
// 實務上可在這裡記錄 $exception 與 getPrevious() 的資訊
}
這個範例有三個值得保留的設計:
addBook()將底層的PDOException包裝成應用程式看得懂的DatabaseException。throw new DatabaseException(..., 0, $exception)保留原始例外,方便記錄與除錯。- 對外只顯示安全、友善的訊息,不把資料庫細節直接送到瀏覽器。
常見錯誤與排解
try catch 接不到錯誤?
| 症狀 | 可能原因 | 解法 |
|---|---|---|
| 程式直接 Fatal error | 語法錯誤、記憶體不足 | try catch 接不住,需在開發階段修正 |
catch (Exception) 沒反應 | 實際拋出的是 Error | 改用 catch (Throwable $e) 或修正型別錯誤 |
| 函式失敗但沒進 catch | 函式回傳 false 而非拋例外 | 檢查回傳值後自行 throw |
PDO 沒拋 PDOException | 未設定 ERRMODE_EXCEPTION | 設定 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION |
RuntimeException vs Exception 怎麼選?
RuntimeException 是 Exception 的子類別,代表「執行時發現的、通常可透過程式修正的問題」。PHP 內建還有 LogicException(程式邏輯錯誤)等子類別。自訂例外時,繼承最接近語意的內建類別即可;不必為了「看起來專業」而一律繼承 Exception。
finally 裡可以 return 嗎?
技術上可以,但 finally 中的 return 可能覆蓋 try 或 catch 原本要回傳的值,讓除錯難度升級。清理區塊應保持簡單,避免在 finally 裡 return 或再次拋出無關例外。
常見問題
catch (Exception $exception) 能接住所有錯誤嗎?
不能。Error 不繼承 Exception。若需要同時接住實作 Throwable 的 Exception 與 Error,可以使用 catch (Throwable $exception),但應先確認這樣做符合你的錯誤處理策略。
所有 PHP 函式失敗時都會拋出例外嗎?
不會。不同函式的行為不同,有些會回傳 false、null 或錯誤碼,有些才會拋出例外。請先查閱該函式的文件,再決定是否需要檢查回傳值或設定錯誤模式。
finally 一定會執行嗎?
在正常離開 try/catch 的流程中,finally 會執行,包括 try 或 catch 中遇到 return 的情況。但若程式直接終止(如 exit),或 finally 自己拋出例外,就可能改變原本的流程與結果。
總結
PHP 例外處理的核心不是「把所有錯誤抓起來」,而是讓錯誤在合適的邊界被辨識、記錄與回應。先分清楚 Exception、Error 與 Throwable,再用具體的自訂例外表達意圖;需要清理時使用 finally,需要跨層轉換時保留 getPrevious() 的原始脈絡。
下一步可繼續閱讀 PHP 類別與物件入門 與 存取修飾符。
參考資料
- PHP 官方文件:Exceptions:說明
try、catch、finally、例外傳遞與Throwable。 - PHP 官方文件:Errors in PHP 7:說明
Error與Exception的差異,以及Throwable的角色。 - PHP 官方文件:PDO — Errors and error handling:說明 PDO 的錯誤模式與
PDOException。
