在 Laravel 中使用一种更简洁的方法处理异常

3,963 阅读2分钟

在构建 Web 应用程序时,我们可能会使用各种 API 和服务来给你的应用程序添加功能。在使用这些外部服务时,可能会遇到各种各样导致应用程序崩溃的意外错误。为了避免这些意外崩溃并确保我们的应用程序正常运行,我们需要妥善地处理这些意外错误。

PHP 编程语言中处理异常的常规方法是使用 try-catch 块,这也适用于 Laravel 当中。但是在本文中,我将向你展示一种在 Laravel 中处理异常的更简洁、更清晰的方法。

1. 什么是异常

异常是在执行程序时出现并导致程序执行流程中断的情况。当发生异常时,程序崩溃,用户无法继续使用该程序。该程序必须重新启动才能再次使用。为了避免这些情况的发生,我们必须要妥善处理这些异常。

2. 在 Laravel 中处理异常

现在我们有一个带有输入字段的简单表单,我们可以通过电子邮件搜索用户,然后显示用户的详细信息。

简单的形式

我们有两条路由:

Route::get('/', [UserController::class, 'index']);
Route::post('/search', [UserController::class, 'search'])->name('search');

我们还有一个 Blade 模板文件。

resources/views/index.blade.php

<form action="{{ route('search') }}" method="post">
    @csrf
    <div class="mb-4 mt-4">
        <div class="form-floating mb-3">
            <input type="email" name="email" id="floatingEmail" class="form-control @error('email') is-invalid @enderror" value="{{ old('email') }}" placeholder="Enter your email">
            <label for="floatingEmail">Email address</label>
        </div>
        @error('email')
            <span class="text-danger mb-1">{{ $message }}</span>
        @enderror
    </div>
    <div class="d-grid col-6 col-md-3 mx-auto">
        <button class="btn btn-secondary" type="submit" id="store">Search</button>
    </div>
</form>

在控制器中,有两个方法:index 方法和 search 方法。该 index 方法渲染 index 模板。search 方法验证输入的电子邮件,然后使用它在数据库中搜索用户的详细信息。

class UserController extends Controller
{
    public function index()
    {
        return view('index');
    }
​
​
    public function search(Request $request)
    {
        $validated = $this->validate($request, [
            'email' => 'required|email',
        ]);
        $email = $validated["email"];
​
        $user = User::where('email', $email)->first();
        return view('result', compact('user'));
​
    }
​
}

当我们通过电子邮件搜索用户并找到用户时,我们将获得用户的详细信息,如下所示。

预期结果

那么,如果找不到用户时会发生什么呢?

3. 使用 Try-catch 块进行异常处理

如果找不到用户,我们会得到一个错误异常页面。

图片描述

这个错误页面可以返回错误错误信息,让用户知道发生了什么错误。我们可以使用 firstOrFail() 而不是 first()。这样 Laravel 会显示一个“404 | 未找到”页面。

图片描述

但是这个页面对用户也没有什么帮助。我们需要做的是捕获异常,然后显示一条可以让用户容易理解的信息。

在 Laravel 中,firstOrFail() 抛出 Eloquent 异常:ModelNotFoundException。基于此,我们就可以在用户控制器的 search 方法中使用 try-catch 块来处理异常。

public function search(Request $request)
{
    $validated = $this->validate($request, [
        'email' => 'required|email',
    ]);
​
    $email = $validated["email"];
​
    try {
        $user = User::where('email', $email)->firstOrFail();
    } catch (ModelNotFoundException $exception) {
        return back()->with('error',$exception->getMessage())->withInput();
    }
}

现在我们就可以在主页上显示错误了。所以我们转到我们的 Blade 模板文件,在 resources/views/index.blade.php 中显示错误信息。

resources/views/index.blade.php

@if (session('error'))
<div class="row justify-content-center">
    <div class="alert alert-danger alert-dismissible fade show col-md-6" role="alert">
        <strong>Message:</strong> {{ session('error') }}
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
    </div>
</div>
@endif

信息显示结果如下。

图片描述

现在假设我们必须处理不止一个异常。我们必须一个一个地捕获每个异常。此外,在我们的应用程序中使用外部服务时,这些外部服务可能有多个自定义异常类,具体取决于遇到的错误。那么一个比较好的处理方式是:使用一个很好的例子是使用 Stripe Payments API 的 Stripe 库

try {
  // Use Stripe's library to make requests...
} catch(\Stripe\Exception\CardException $e) {
  // Since it's a decline, \Stripe\Exception\CardException will be caught
  echo 'Status is:' . $e->getHttpStatus() . '\n';
  echo 'Type is:' . $e->getError()->type . '\n';
  echo 'Code is:' . $e->getError()->code . '\n';
  // param is '' in this case
  echo 'Param is:' . $e->getError()->param . '\n';
  echo 'Message is:' . $e->getError()->message . '\n';
} catch (\Stripe\Exception\RateLimitException $e) {
  // Too many requests made to the API too quickly
} catch (\Stripe\Exception\InvalidRequestException $e) {
  // Invalid parameters were supplied to Stripe's API
} catch (\Stripe\Exception\AuthenticationException $e) {
  // Authentication with Stripe's API failed
  // (maybe you changed API keys recently)
} catch (\Stripe\Exception\ApiConnectionException $e) {
  // Network communication with Stripe failed
} catch (\Stripe\Exception\ApiErrorException $e) {
  // Display a very generic error to the user, and maybe send
  // yourself an email
} catch (Exception $e) {
  // Something else happened, completely unrelated to Stripe
}

可以看到,我们必须捕获每一个错误,才能正确处理它们来满足我们的应用程序的需要。这就使得我们编写的代码行增加,进而让我们的代码更难维护。虽然这种方法非常好,但在 Laravel 中还有一种更短、更流畅的方法来处理异常。

4. 使用 Rescue Helper 处理异常

Laravel 为我们提供了rescue 助手函数,它以闭包的形式执行我们写的业务代码。rescue 助手函数能捕获在执行闭包期间发生的任何异常。捕获到的任何异常都会发送到 Laravel 应用程序中的异常处理程序

public function search(Request $request)
{
    $validated = $this->validate($request, [
        'email' => 'required|email',
    ]);
​
    $email = $validated["email"];
​
    return rescue(function () use ($email) {
        $user = User::where('email', $email)->firstOrFail();
        return view('result', compact('user'));
    }, function ($exception) {
        return back()->with('status', $exception->getMessage());
    }, true);
}

通常,rescue 方法可以接受三个参数。

  • 第一个参数是一个闭包,其中包含我们要执行的业务代码。在我们的例子中,我们想通过电子邮件搜索用户并在另一个页面上显示他们的详细信息。
  • 第二个参数(可选)是在前一个参数发生异常时执行的闭包。我们还可以传入表示抛出的异常的参数。
  • 第三个参数(可选)是一个布尔值。此参数告诉函数是否将异常报告给配置的异常处理程序。如果为真,则 rescue() 助手函数报告异常,反之亦然。

5. 结论

在我们可能需要处理多个异常的情况下,rescue() 帮助函数可以帮助我们减少编写的代码行数。它还可以帮助提高代码的可读性,从而使其随着时间的推移更易于维护。