Skip to content

使用自定义异常来编写更好、更清晰的代码

在开发逻辑中经常需要在一些处理逻辑时抛出错误的情况,比如在控制器中有如下逻辑代码:

php
<?php

namespace App\Http\Controllers;

use App\Models\Ticket;
use App\Services\CheckoutService;
use Illuminate\Http\Request;

class CheckoutController extends Controller
{
    public function __invoke(Request $request)
    {
        /** @var Ticket $ticket */
        $ticket = Ticket::query()->findOrFail($request->ticket);
        $user = $request->user();

        // 1. 当用户的积分小于票价则不允许提交
        if ($user->credits < $ticket->price) {
            abort(401, 'Not enough credits');
        }

        // 2. 检查当前票数量是否满足所提交的需求
        if (!$ticket->isAvailable($request->amount)) {
            abort(404, 'Not enough tickets');
        }

        $purchase = app(CheckoutService::class)->purchaseTicket($user, $ticket);

        return response()->json([
            'message' => 'Ticket purchased successfully.',
        ]);
    }
}

此时可以将上面的代码使用自定义异常来编写,如下所示:

php
<?php

namespace App\Http\Controllers;

use App\Exceptions\NotEnoughCreditsException;
use App\Exceptions\NotEnoughTicketsException;
use App\Models\Ticket;
use App\Services\CheckoutService;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Throwable;

class CheckoutController extends Controller
{
    /**
     * @throws Throwable
     */
    public function __invoke(Request $request)
    {
        /** @var Ticket $ticket */
        $ticket = Ticket::query()->findOrFail($request->ticket);
        $user = $request->user();

        try {
            $purchase = app(CheckoutService::class)->purchaseTicket($user, $ticket, $request->amount);
        } catch (NotEnoughCreditsException $exception) {
            // abort(401, $exception->message());
            throw ValidationException::withMessages([
                'credits' => 'Not enough credits',
            ])->status(401);
        } catch (NotEnoughTicketsException $exception) {
            throw $exception->validationMessage();
        }

        return response()->json([
            'message' => 'Ticket purchased successfully.',
        ]);
    }
}
php
<?php

namespace App\Services;

use Throwable;
use App\Models\User;
use App\Models\Ticket;
use App\Models\Purchse;
use App\Exceptions\NotEnoughCreditsException;
use App\Exceptions\NotEnoughTicketsException;

class CheckoutService
{
    /**
     * @throws NotEnoughCreditsException|NotEnoughTicketsException
     * @throws Throwable
     */
    public function purchaseTicket(User $user, Ticket $ticket, int $amount): Purchse
    {
        // 1. 当用户的积分小于票价则不允许提交
        throw_if($user->credits < $ticket->price, new NotEnoughCreditsException);

        // 2. 检查当前票数量是否满足所提交的需求
        throw_if(! $ticket->isAvailable($amount), new NotEnoughTicketsException);

        $user->credits -= $ticket->price;

        return new Purchse;
    }
}
php
<?php

namespace App\Exceptions;

use Exception;

class NotEnoughCreditsException extends Exception
{
    public function message(): string
    {
        return 'Not enough credits';
    }
}
php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Validation\ValidationException;

class NotEnoughTicketsException extends Exception
{
    public function validationMessage(): ValidationException
    {
        return ValidationException::withMessages([
            'ticket' => 'Not enough tickets',
        ]);
    }
}

相关代码仓库测试逻辑在这里:tests/Feature/CheckoutControllerTest.php