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

相关文章
相关标签/搜索