Laravel異常: 如何捕獲、處理和建立本身的異常

不少時候,web開發人員並不關心錯誤。 若是出現問題,你常常會看到默認的Laravel默認提示,例如 Whoops, something went wrong,或者更糟糕的是,異常代碼,這對訪問者根本沒有任何幫助。 因此我決定寫一篇關於如何以優雅的方式處理錯誤並向訪問者提供適當的錯誤信息的分步文章。php

提示:本文還將展現使用依賴注入建立本身的服務以及處理服務拋出的異常的示例。laravel

準備:用戶搜索任務

因此咱們有一個很是簡單的例子 - 經過他們的ID來查找用戶的表單。web

搜索初識界面

新建兩個路由瀏覽器

Route::get('/users', 'UserController@index')->name('users.index');
Route::post('/users/search', 'UserController@search')->name('users.search');

在控制器中新建兩個方法bash

class UserController extends Controller
{

    public function index()
    {
        return view('users.index');
    }

    public function search(Request $request)
    {
        $user = User::find($request->input('user_id'));
        return view('users.search',  compact('user'));
    }

}

最後來完善下模板文件session

// resources/views/users/index.blade.php

<form action="{{ route('users.search') }}" method="POST">
    @csrf

    <div class="form-group">
        <input name="user_id" type="text" id="user_id" placeholder="User ID" 
            class="form-control" value="{{ old('user_id') }}">
    </div>

    <input type="submit" class="btn btn-info" value="Search">
</form>

若是咱們搜索現有用戶並找到它,咱們會看到如下結果:app

正常結果

搜索文件模板oop

// resources/views/users/search.blade.php:

<h3 class="page-title text-center">User found: {{ $user->name }}</h3>

<b>Email</b>: {{ $user->email }}
<br />
<b>Registered on</b>: {{ $user->created_at }}

以上,這是咱們理想的場景。可是若是找不到用戶呢?post

異常處理

讓咱們走出理想的世界。咱們不檢查用戶是否存在,咱們只是在Controller中執行此操做:this

$user = User::find($request->input('user_id'));

若是用戶沒有找到,咱們會看到:

異常

固然,咱們能夠用 APP_DEBUG = false 設置 .env 文件,而後瀏覽器只顯示空白的 Whoops, looks like something went wrong.。可是這仍然沒有給咱們的訪問者提供任何有價值的信息。

咱們能夠作的另外一個快速解決方法是使用 User::findOrFail() 而不是 find() - 而後若是找不到用戶,Laravel將顯示帶有文本的404頁面 Sorry, the page you are looking for could not be found.。 可是,這是整個項目的默認404頁面,所以對用戶不是頗有幫助,不是嗎?

因此咱們須要捕捉錯誤,處理它們,而後用實際能夠理解的錯誤信息重定向到表單。

咱們須要知道它將返回的異常類型和類名。在 findOrFail() 的狀況下,它會拋出Eloquent異常ModelNotFoundException,因此咱們須要這樣作:

public function search(Request $request)
{
    try {
        $user = User::findOrFail($request->input('user_id'));
    } catch (ModelNotFoundException $exception) {
        return back()->withError($exception->getMessage())->withInput();
    }
    return view('users.search', compact('user'));
}

如今,讓咱們在Blade中實際顯示一個錯誤:

<h3 class="page-title text-center">Search for user by ID</h3>

@if (session('error'))
    <div class="alert alert-danger">{{ session('error') }}</div>
@endif

<form action="{{ route('users.search') }}" method="POST">
...

結果

異常處理1

太棒了,咱們會顯示錯誤信息!但它仍然不理想,對吧?而不是 $exception->getMessage(),咱們須要顯示咱們本身的消息:

return back()->withError('User not found by ID ' . $request->input('user_id'))->withInput();

最終效果:

優雅的異常處理

將錯誤消息處理轉移到服務Service中

如今,咱們已經在控制器中採用了一個很是簡單的一個動做示例 - 只需找到用戶便可。 在實際的應用程序中,它變得更加複雜,一般控制器正在調用某種外部服務或打包方法,這些服務或打包方法可能會因各類錯誤而失敗。

讓咱們建立咱們本身的service,它本質上會作一樣的事情,但會拋出異常,因此控制器甚至不須要知道消息文本。

讓咱們將咱們的邏輯移動到 app/Services/UserService.php

namespace App\Services;

use App\User;
use Illuminate\Database\Eloquent\ModelNotFoundException;

class UserService
{

    public function search($user_id)
    {
        $user = User::find($user_id);
        if (!$user) {
            throw new ModelNotFoundException('User not found by ID ' . $user_id);
        }
        return $user;
    }

}

在Controller中,咱們須要調用這個服務。首先,咱們將它注入到 __construct() 方法中:

use App\Services\UserService;

class UserController extends Controller
{

    private $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

// ...

若是你不熟悉依賴注入(DI)以及Laravel IOC容器的工做原理,請參閱官方文檔或有關它的好文章。

如今,下面是咱們的 search() 方法是這樣子的:

public function search(Request $request)
{
    try {
        $user = $this->userService->search($request->input('user_id'));
    } catch (ModelNotFoundException $exception) {
        return back()->withError($exception->getMessage())->withInput();
    }
    return view('users.search', compact('user'));
}

請注意,咱們能夠再次使用 $exception->getMessage(),而且全部錯誤驗證或消息邏輯都在service中發生 - 這是分離這些操做的目的之一,控制器不該執行它。

更進一步:建立咱們本身的異常類

本文的最後部分 - 當service引起與特定錯誤有關的異常時,甚至更好的體系結構,而且根據錯誤可能會有多個異常類。

那麼咱們如何建立本身的異常類呢?很簡單,用Artisan命令:

php artisan make:exception UserNotFoundException

如下是它會在 app/Exceptions/UserNotFoundException.php 中生成的內容:

namespace App\Exceptions;

use Exception;

class UserNotFoundException extends Exception
{
    //
}

這裏什麼也沒有,對吧?讓咱們用一些邏輯來填充咱們的異常。 這個類能夠有兩種方法:

  • report() 若是你想要作一些額外的日誌記錄,則使用report(), 好比將錯誤發送到電子郵件,Slack等。
  • render() 若是你想直接從Exception類重定向錯誤或返回HTTP響應(如本身的 Blade 文件),則使用render()

因此,在這個例子中,咱們填充了 render() 方法:

namespace App\Exceptions;

use Exception;

class UserNotFoundException extends Exception
{
    /**
     * Report or log an exception.
     *
     * @return void
     */
    public function report()
    {
        \Log::debug('User not found');
    }
}

最後,這就是咱們如何從控制器中調用這個異常:

public function search(Request $request)
{
    try {
        $user = $this->userService->search($request->input('user_id'));
    } catch (UserNotFoundException $exception) {
        report($exception);
        return back()->withError($exception->getMessage())->withInput();
    }
    return view('users.search', compact('user'));
}

因此,這就是我想向你展現異常處理和使用service的一個方面。

固然這個例子是很是簡單的,其餘人能夠用不一樣的方式使用Exceptions ,但我但願這篇文章能概述通常的異常狀況,並說明爲何你應該使用它們,向訪問者以優雅的方式顯示錯誤。

關於異常和錯誤處理的更多信息,請查看 Laravel官方文檔

更多PHP知識,能夠前往 PHPCasts

相關文章
相關標籤/搜索