Menu
CoddyTech

自定义异常层次结构

本课属于 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;
    }
}

设计良好的异常层次结构能让代码更易于维护。捕获异常时,你可以根据需要选择宽泛或具体的范围,而且异常对象本身会携带有关错误原因的有用信息。

challenge icon

挑战

简单

让我们构建一个订单处理系统,并使用自定义异常层次结构处理不同类型的订单失败。你将创建携带错误相关上下文信息的异常,让错误处理更加精确、信息更丰富。

你将把代码分成四个文件:

  • 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])"。

有了这个层次结构,你既可以单独处理特定异常,也可以通过基类 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])"

?>
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: PHP 在线编译器