自定义异常层次结构
本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 67 课,共 91 课。
虽然 PHP 内置的异常类可以处理常见情况,但实际应用通常需要更具体的错误类型。创建自定义异常层次结构,可以让你定义符合自身领域的异常,并按不同的具体程度捕获它们。
要创建自定义异常,只需继承 Exception 类或它的某个子类:
<?php
class PaymentException extends Exception {}
class InsufficientFundsException extends PaymentException {
public function __construct(
private float $required,
private float $available
) {
parent::__construct("Need $required but only $available available");
}
public function getShortfall(): float {
return $this->required - $this->available;
}
}
注意,InsufficientFundsException 继承自 PaymentException,而不是直接继承自 Exception。这样就形成了一个层次结构,你可以用一个 catch 代码块捕获所有与支付相关的异常,也可以分别处理特定的异常。
自定义异常可以包含与错误相关的其他属性和方法。这里,getShortfall() 提供了通用异常无法提供的有用信息:
<?php
class Wallet {
public function __construct(private float $balance) {}
public function pay(float $amount): void {
if ($amount > $this->balance) {
throw new InsufficientFundsException($amount, $this->balance);
}
$this->balance -= $amount;
}
}
设计良好的异常层次结构能让代码更易于维护。捕获异常时,你可以根据需要选择宽泛或具体的范围,而且异常对象本身会携带有关错误原因的有用信息。
挑战
简单让我们构建一个订单处理系统,并使用自定义异常层次结构处理不同类型的订单失败。你将创建携带错误相关上下文信息的异常,让错误处理更加精确、信息更丰富。
你将把代码分成四个文件:
OrderException.php:创建一个继承自Exception的基类OrderException。它作为所有订单相关异常的父类,这样在需要时,你可以用一个 catch 代码块捕获任何订单问题。OutOfStockException.php:创建一个继承自OrderException的OutOfStockException类。这个异常的构造函数应接收产品名称和请求数量。使用构造函数属性提升来定义这些私有属性。调用父类构造函数,并传入格式为"[productName] is out of stock (requested: [quantity])"的消息。添加一个方法getProductName(): string,用于返回产品名称。InvalidQuantityException.php:创建一个继承自OrderException的InvalidQuantityException类。这个异常的构造函数应接收数量值,并使用构造函数属性提升将它定义为私有属性。调用父类构造函数,并传入消息"Invalid quantity: [quantity]"。添加一个方法getQuantity(): int,用于返回数量值。main.php:引入这三个异常文件。你将收到两个输入:产品名称和数量(这是一个需要转换为整数的字符串)。根据数量值,抛出相应的异常:
- 如果数量小于 1,抛出
InvalidQuantityException - 如果数量大于 100,抛出
OutOfStockException(模拟库存不足) - 否则,输出
"Order placed: [quantity]x [productName]"
使用异常层次结构捕获异常。先捕获
InvalidQuantityException,并使用异常消息和getQuantity()方法输出"Quantity error: [message] (value: [quantity])"。然后捕获OutOfStockException,并使用异常消息和getProductName()方法输出"Stock error: [message] (product: [productName])"。- 如果数量小于 1,抛出
有了这个层次结构,你既可以单独处理特定异常,也可以通过基类 OrderException 捕获所有订单相关问题。每个异常上的自定义方法还能提供通用异常无法提供的上下文信息。
亲手试试
<?php
// 引入所有异常文件
require_once 'OrderException.php';
require_once 'OutOfStockException.php';
require_once 'InvalidQuantityException.php';
// 读取输入
$productName = trim(fgets(STDIN));
$quantity = intval(trim(fgets(STDIN)));
// TODO: 使用 try-catch 处理订单
// 在 try 块中:
// 如果 quantity < 1,抛出 InvalidQuantityException
// 如果 quantity > 100,抛出 OutOfStockException
// 否则,输出 "Order placed: [quantity]x [productName]"
//
// 先捕获 InvalidQuantityException:
// 使用 getMessage() 和 getQuantity() 输出 "Quantity error: [message] (value: [quantity])"
//
// 捕获 OutOfStockException:
// 使用 getMessage() 和 getProductName() 输出 "Stock error: [message] (product: [productName])"
?>这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: PHP 在线编译器