shipsaas/never-throw

优先响应而非抛出。PHP NeverThrow 库示例实现

2.0.0 2023-11-05 06:22 UTC

This package is auto-updated.

Last update: 2024-09-05 08:19:43 UTC


README

codecov Build & Test (PHP 8.1, 8.2)

为您的PHP应用程序提供一种严格类型的方式来处理成功与错误结果。

受到 NeverThrow 的启发,这里提供了PHP中NeverThrow的简单实现。

抛出和捕获与使用goto语句非常相似 - 换句话说,这使得对您的程序进行推理变得更加困难。其次,通过使用抛出,您假定您的函数调用者正在实现捕获。这是一个已知的错误来源。例如:一个开发者抛出了一个异常,另一个开发者在使用该函数时没有事先知道该函数会抛出异常。因此,一个边缘情况被忽略了,现在您有不满的用户、老板、猫等等。

尽管如此,在您的程序中使用抛出确实有很好的用例。但远没有您想象的那么多。

TL;DR:没有抛出 = 更快乐的生活与应用程序。在生产环境中减少错误,并增加您的PHP应用程序中的DRY和可重用事物。

支持

PHP 8.2+ 自v2.0.0版起

安装

composer require shipsaas/never-throw

顶级API

NeverThrow\\Result 类随时可扩展。

非静态

  • isOk()
  • isError()
  • getOkResult()
  • getErrorResult()

用法

创建您的响应类

我们将创建两个新的类:Success和Error。您可以随意放置任何数据

use NeverThrow\SuccessResult;
use NeverThrow\ErrorResult;

// Success
class BookShipperOkResult extends SuccessResult
{
    public function __construct(
        public string $bookingId
    ) {}
}

enum BookingErrors {
    case NO_SHIPPER_AVAILABLE;
    case OVERWEIGHT_PACKAGE;
    case INVALID_ADDRESS;
}

// Error
class BookShipperErrorResult extends ErrorResult
{
    public function __construct(
        public BookingErrors $outcome
    ) {}
}

创建您的专用Result类

使用NeverThrow之前的最后一步。创建一个专用Result类有助于我们

  • 定义错误和成功结果类的类型
    • => 适合IDE
    • => 让读者/调用者更快乐
  • 一个集中式的地方,用于引用成功和错误结果类。

第一种方法

use NeverThrow\Result;

class BookShipperResult extends Result
{
    public function getOkResult(): BookShipperOkResult
    {
        return parent::getOkResult();
    }
    
    public function getErrorResult(): BookShipperErrorResult
    {
        return parent::getErrorResult();
    }
}

在业务逻辑中返回Result

public function createBooking(User $user, BookingOrder $order): BookShipperResult
{
    $hasAnyShipperAvailable = $this->shipperService->hasAnyShipperAvailable();
    if (!$hasAnyShipperAvailable) {
        return new BookShipperResult(
            new BookShipperErrorResult(
                BookingErrors::NO_SHIPPER_AVAILABLE
            )
        );
    }
    
    $isOverweight = !$this->weightService->isValid($order);
    if ($isOverweight) {
        return new BookShipperResult(
            new BookShipperErrorResult(
                BookingErrors::OVERWEIGHT_PACKAGE
            )
        );
    }
    
    $bookingId = $this->book($user, $order);
   
    return new BookShipperResult(new BookShipperOkResult($bookingId));
}

检查响应

$bookingResult = $this->service->createBooking($user, $order);

if ($bookingResult->isError()) {
    $errorResult = $bookingResult->getErrorResult();

    // handle error
    return showError(match ($errorResult->outcome) {
        BookingErrors::NO_SHIPPER_AVAILABLE => 'No shipper available at the moment. Please wait',
        BookingErrors::OVERWEIGHT_PACKAGE => 'Your package is overweight',
    });
}

return showBooking($bookingResult->getOkResult()->bookingId);

结论

如您所见,上述代码中

  • 没有try/catch,0 try/catch滥用
  • 没有异常,永远不会
  • 明确的返回类型和信息。

这将使开发真正出色且无痛苦。

使用严格类型的返回类型,开发者可以了解其他服务/库中正在发生的事情。因此,使可重用性更好。我们不必包装try/catch并使我们的代码变得丑陋。

不要滥用异常,它们应该只用于意外情况(并且错误不等于异常,事实)。

附加

function transfer(): Transaction
{
    if (!$hasEnoughBalance) {
        thrown new InsufficientBalanceError();
    }
    
    if (!$invalidRecipient) {
        throw new InvalidRecipientError();
    }
    
    if (!$invalidMoney) {
        throw new InvalidTransferMoneyError();
    }
    
    $transaction = $this->transferService->transfer(...);
    if (!$transaction) {
        throw new TransferFailedError();
    }
    
    return $transaction;
}

这个函数的大部分实际上是关于可能会出错的事情,但我们的类型只告诉我们成功的路径。这意味着函数输出的4/5部分是未类型化的!

上述的“异常”或“错误”根本不是真正的异常或错误。它们是结果。它们是我们系统可预测、合理的部分。我的启发是,如果它们是产品经理会关心的事情,它们就不是异常,您不应该抛出它们!

异常是不可预测的事情,我们无法合理地计划,系统不应该尝试从中恢复,我们也不应该将其路由给用户。

许可证

MIT许可证