Skip to content

PHP 异常处理

异常提供了一种面向对象的方式来处理错误和异常情况,与传统方法相比,它能实现更清晰、更健壮的错误管理。

PHP 具有强大的异常模型。异常是一个对象,它描述了脚本执行期间发生的错误或意外事件。

当发生异常情况时,脚本可以“抛出”(throw)一个异常。这会中断正常的执行流程。

抛出异常时会发生什么:

  • 当前代码状态被保存。
  • 执行跳转到可以处理所抛出异常类型的最近的 catch 块。
  • 如果在当前函数中没有找到匹配的 catch 块,它会沿着调用栈向上查找,直到找到处理程序。
  • 如果异常在没有被捕获的情况下到达全局作用域,脚本将终止并出现致命错误(除非设置了全局异常处理程序)。

我们将涵盖的关键概念:

  • 使用 try、throw 和 catch 块
  • finally 块(确保代码执行)
  • 创建自定义异常类
  • 处理多种异常类型
  • 重新抛出异常
  • 设置全局异常处理程序

注意:异常旨在处理异常情况(错误、无效状态),而不是用于 if/else 或循环等正常的程序控制流。

使用 throw 关键字抛出异常时,try 块内紧随其后的代码将被跳过。PHP 会搜索第一个匹配的 catch 块。

如果异常没有被捕获,会导致致命错误:

示例:抛出一个未捕获的异常:

<?php
declare(strict_types=1);
function checkNumber(int $number): bool {
if ($number > 1) {
// Throwing an instance of the base Exception class
// 抛出基本 Exception 类的实例
throw new Exception("Value must be 1 or below");
}
return true;
}
// Trigger exception - no try/catch block here!
// 触发异常 - 这里没有 try/catch 块!
try {
checkNumber(2);
} catch (Exception $e) {
// This catch block will handle the exception
// 这个 catch 块将处理异常
echo "Caught exception: " . $e->getMessage();
}
// If we called checkNumber(2) outside a try block:
// It would result in a Fatal error: Uncaught Exception: Value must be 1 or below...
// 如果我们在 try 块外部调用 checkNumber(2):
// 将导致致命错误:Uncaught Exception: Value must be 1 or below...
?>

为了优雅地处理异常,使用 try...catch 结构:

  1. try - 包含可能抛出异常的代码。
  2. throw - 用于发出异常信号的关键字。你抛出一个 Exception 或其子类(包括 PHP 7+ 中的 Error 子类)的 对象。
  3. catch - 声明一个异常处理程序。它指定可以捕获的异常类型,并提供一个变量(例如 $e)来访问异常对象。

示例:捕获异常:

<?php
declare(strict_types=1);
function checkNumber(int $number): bool {
if ($number > 1) {
throw new Exception("Value must be 1 or below");
}
return true;
}
try {
// Code that might throw an exception
// 可能抛出异常的代码
checkNumber(5);
// This line will not be executed if checkNumber(5) throws
// 如果 checkNumber(5) 抛出异常,此行将不会执行
echo 'If you see this, the number is 1 or below.';
} catch (Exception $e) {
// Code to handle the exception
// 处理异常的代码
// $e is an object containing details about the exception
// $e 是一个包含异常详情的对象
echo 'Caught Exception: ' . $e->getMessage() . PHP_EOL;
echo 'File: ' . $e->getFile() . PHP_EOL;
echo 'Line: ' . $e->getLine() . PHP_EOL;
}
echo 'Script continues after the try/catch block.';
// 脚本在 try/catch 块之后继续执行。
?>

上述示例的输出:

Caught Exception: Value must be 1 or below
File: /path/to/your/script.php
Line: 7
Script continues after the try/catch block.

可以在 catch 块之后添加一个 finally 块。finally 块中的代码在 try(以及任何 catch)块执行完毕后总是会执行,无论是否抛出或捕获了异常。

这对于必须执行的清理操作很有用,例如关闭文件句柄或数据库连接,无论操作成功还是失败。

<?php
declare(strict_types=1);
function processResource(bool $shouldFail) {
echo "Opening resource...\n";
// 正在打开资源...
try {
echo "Processing resource...\n";
// 正在处理资源...
if ($shouldFail) {
throw new Exception("Processing failed!");
// 处理失败!
}
echo "Resource processed successfully.\n";
// 资源处理成功。
} catch (Exception $e) {
echo "Caught exception: " . $e->getMessage() . "\n";
// 捕获到异常:
} finally {
// This code runs whether an exception occurred or not
// 这段代码无论是否发生异常都会运行
echo "Closing resource...\n";
// 正在关闭资源...
}
}
echo "--- Running without failure ---\n";
// --- 在没有失败的情况下运行 ---
processResource(false);
echo "\n--- Running with failure ---\n";
// --- 在失败的情况下运行 ---
processResource(true);
?>

输出:

--- Running without failure ---
Opening resource...
Processing resource...
Resource processed successfully.
Closing resource...
--- Running with failure ---
Opening resource...
Processing resource...
Caught exception: Processing failed!
Closing resource...

你可以通过扩展基础 Exception 类(或其他异常类)来创建自己的异常类型。这使你能够创建更具体的错误处理程序,并向你的异常添加自定义属性或方法。

自定义异常类继承了 Exception 的属性和方法(如 getMessage()、getFile()、getLine()、getCode()、getTraceAsString())。

示例:创建和使用 ValidationException:

<?php
declare(strict_types=1);
// Custom exception class
// 自定义异常类
class ValidationException extends Exception {
// You can add custom properties or methods
// 你可以添加自定义属性或方法
public function getDetailedMessage(): string {
return "Validation Error: {$this->getMessage()} in {$this->getFile()} on line {$this->getLine()}";
// 验证错误:
// 在 文件: 行:
}
}
function validateEmail(string $email): bool {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// Throw our custom exception
// 抛出我们的自定义异常
throw new ValidationException("'{$email}' is not a valid email address");
// 不是一个有效的电子邮件地址
}
// Example: Check if domain is blocked (conceptual)
// 示例:检查域名是否被阻止(概念性)
if (str_ends_with($email, '@blocked.com')) {
// Throwing a standard exception for a different issue
// 为不同的问题抛出标准异常
throw new Exception("Email domain is blocked");
// 电子邮件域名被阻止
}
return true;
}
$email = "invalid-email";
try {
validateEmail($email);
echo "Email is valid.";
// 电子邮件有效。
} catch (ValidationException $e) {
// Catch specifically our custom validation exception
// 特别捕获我们的自定义验证异常
echo $e->getDetailedMessage();
} catch (Exception $e) {
// Catch any other standard exceptions
// 捕获其他任何标准异常
echo "General Error: " . $e->getMessage();
// 一般错误:
}
?>

输出:

Validation Error: 'invalid-email' is not a valid email address in /path/to/your/script.php on line 13

你可以使用多个 catch 块来专门处理不同类型的异常。

PHP 按顺序检查 catch 块。第一个类型提示与抛出的异常(或其父类之一)匹配的块将被执行。

示例:使用多个 catch 块:

<?php
declare(strict_types=1);
class NetworkException extends Exception {}
class DatabaseException extends Exception {}
function performAction(int $type) {
if ($type === 1) {
throw new NetworkException("Could not connect to server");
// 无法连接到服务器
} elseif ($type === 2) {
throw new DatabaseException("Error querying database");
// 查询数据库时出错
} elseif ($type === 3) {
throw new InvalidArgumentException("Invalid action type"); // Built-in SPL exception
// 无效的操作类型 // 内置的 SPL 异常
} else {
throw new Exception("An unknown error occurred");
// 发生未知错误
}
}
for ($i = 1; $i <= 4; $i++) {
try {
echo "Attempting action type $i: ";
// 正在尝试操作类型 $i:
performAction($i);
echo "Success!\n";
// 成功!
} catch (NetworkException $e) {
echo "Network Error: " . $e->getMessage() . "\n";
// 网络错误:
} catch (DatabaseException $e) {
echo "Database Error: " . $e->getMessage() . "\n";
// 数据库错误:
} catch (InvalidArgumentException $e) { // Catching a specific built-in type
// 捕获特定的内置类型
echo "Input Error: " . $e->getMessage() . "\n";
// 输入错误:
} catch (Exception $e) { // Catch-all for other Exceptions
// 捕获所有其他异常
echo "General Error: " . $e->getMessage() . "\n";
// 一般错误:
}
}
?>

输出:

Attempting action type 1: Network Error: Could not connect to server
Attempting action type 2: Database Error: Error querying database
Attempting action type 3: Input Error: Invalid action type
Attempting action type 4: General Error: An unknown error occurred

PHP 7.1+ 还允许使用管道符号(|)在一个块中捕获多种类型:

<?php
// ... (Exception classes defined as above)
// ... (异常类如上定义)
try {
// ... code that might throw NetworkException or DatabaseException ...
// ... 可能抛出 NetworkException 或 DatabaseException 的代码 ...
} catch (NetworkException | DatabaseException $e) {
echo "Connectivity Error: " . $e->getMessage();
// 连接错误:
} catch (Exception $e) {
echo "Other Error: " . $e->getMessage();
// 其他错误:
}
?>

有时,catch 块可能会执行部分处理(如日志记录),然后决定将相同或不同的异常向上抛给调用栈中更高级别的处理程序来处理。

这对于将低级异常包装在更具上下文或更友好的用户消息中很有用。

<?php
declare(strict_types=1);
class ConfigurationException extends Exception {}
// 配置异常
function loadConfigFile(string $path) {
if (!file_exists($path)) {
throw new Exception("File not found at: {$path}"); // Generic low-level exception
// 文件未找到: // 通用的低级异常
}
// ... process file ...
// ... 处理文件 ...
if (false /* some config error */) {
// 如果(假 /* 某些配置错误 */)
throw new Exception("Invalid config format");
// 无效的配置格式
}
}
function initializeApp(string $configPath) {
try {
loadConfigFile($configPath);
} catch (Exception $e) {
// Log the original error
// 记录原始错误
error_log("Config Error: " . $e->getMessage());
// 配置错误:
// Re-throw a more specific, higher-level exception
// 重新抛出更具体、更高级别的异常
// Pass the original exception as the 'previous' exception (PHP 5.3+)
// 将原始异常作为“先前”异常传递(PHP 5.3+)
throw new ConfigurationException("Failed to initialize application due to configuration issue.", 0, $e);
// 由于配置问题,应用程序初始化失败。
}
}
try {
initializeApp("nonexistent_config.ini");
} catch (ConfigurationException $e) {
echo "Application Error: " . $e->getMessage() . "\n";
// 应用程序错误:
// Optionally inspect the original cause:
// 可选地检查原始原因:
if ($previous = $e->getPrevious()) {
echo "Original Cause: " . $previous->getMessage() . "\n";
// 原始原因:
}
}
?>

输出(同时会记录原始错误):

Application Error: Failed to initialize application due to configuration issue.
Original Cause: File not found at: nonexistent_config.ini

set_exception_handler() 函数注册一个回调函数,该函数将用于处理任何未被 try...catch 块捕获的异常。

这对于提供一种备用机制很有用,例如在生产环境中记录未捕获的异常或显示通用错误页面,防止将敏感的错误详情显示给用户。

<?php
declare(strict_types=1);
// Define the global exception handler function
// 定义全局异常处理函数
// It receives the Throwable object (Exception or Error)
// 它接收 Throwable 对象(Exception 或 Error)
function globalExceptionHandler(Throwable $exception) {
// Log the error (recommended for production)
// 记录错误(生产环境推荐)
error_log("Uncaught Exception: " . $exception->getMessage() . " in " . $exception->getFile() . ":" . $exception->getLine());
// 未捕获的异常: 在 文件: 行:
// Display a user-friendly message (avoid showing $exception details directly in production)
// 显示用户友好消息(避免在生产环境中直接显示 $exception 详情)
http_response_code(500); // Internal Server Error
// 内部服务器错误
echo "<h1>Oops! Something went wrong.</h1>";
// 糟糕!出了点问题。
echo "<p>We've been notified and are looking into it. Please try again later.</p>";
// 我们已收到通知并正在调查。请稍后重试。
}
// Register the handler
// 注册处理程序
set_exception_handler('globalExceptionHandler');
// Example of code throwing an exception that won't be caught elsewhere
// 示例代码,抛出一个不会在其他地方被捕获的异常
function riskyOperation() {
throw new Exception('Something bad happened unexpectedly!');
// 意外地发生了糟糕的事情!
}
riskyOperation();
echo "This line will not be reached.";
// 这行代码将不会被执行。
?>

输出(到浏览器):

<h1>Oops! Something went wrong.</h1>
<p>We've been notified and are looking into it. Please try again later.</p>

PHP 错误日志中也会出现一条错误消息。

在 PHP 7 及更高版本中,许多传统的致命错误(例如调用不存在的函数,或在启用严格类型时发生类型错误)现在会抛出 Error 异常。Error 和 Exception 都实现了 Throwable 接口。

这意味着你通常可以使用 catch (Error $e) 或 catch (Throwable $t) 来捕获这些关键错误。

通常:

  • Exception:用于应用逻辑中的错误,这些错误可能可以被处理(例如,无效输入、文件未找到、数据库查询失败)。
  • Error:用于 PHP 内部错误(例如,类型错误、解析错误、未定义函数)。捕获这些错误不太常见,通常表示应该修复的编程错误,但对于优雅关机或日志记录可能有用。

如果需要,你的 catch 块可以捕获 Throwable 来处理 Error 和 Exception 类型,尤其是在全局处理程序或 finally 块中。

<?php
declare(strict_types=1);
try {
// This will throw a TypeError in PHP 7+
// 这将在 PHP 7+ 中抛出 TypeError
strlen(123);
} catch (TypeError $e) {
echo "Caught TypeError: " . $e->getMessage();
// 捕获到 TypeError:
} catch (Throwable $t) {
// Fallback for any other Throwable (Error or Exception)
// 对任何其他 Throwable(Error 或 Exception)的备用处理
echo "Caught Throwable: " . $t->getMessage();
// 捕获到 Throwable:
}
?>