首页 php框架 Laravel Laravel核心分析之异常处理(代码)

Laravel核心分析之异常处理(代码)

Feb 11, 2019 am 10:22 AM
laravel php

本篇文章给大家带来的内容是关于Laravel核心分析之异常处理(代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

异常处理是编程中十分重要但也最容易被人忽视的语言特性,它为开发者提供了处理程序运行时错误的机制,对于程序设计来说正确的异常处理能够防止泄露程序自身细节给用户,给开发者提供完整的错误回溯堆栈,同时也能提高程序的健壮性。

这篇文章我们来简单梳理一下Laravel中提供的异常处理能力,然后讲一些在开发中使用异常处理的实践,如何使用自定义异常、如何扩展Laravel的异常处理能力。

注册异常Handler

这里又要回到我们说过很多次的Kernel处理请求前的bootstrap阶段,在bootstrap阶段的Illuminate\Foundation\Bootstrap\HandleExceptions 部分中Laravel设置了系统异常处理行为并注册了全局的异常处理器:

class HandleExceptions
{
    public function bootstrap(Application $app)
    {
        $this->app = $app;

        error_reporting(-1);

        set_error_handler([$this, 'handleError']);

        set_exception_handler([$this, 'handleException']);

        register_shutdown_function([$this, 'handleShutdown']);

        if (! $app->environment('testing')) {
            ini_set('display_errors', 'Off');
        }
    }
    
    
    public function handleError($level, $message, $file = '', $line = 0, $context = [])
    {
        if (error_reporting() & $level) {
            throw new ErrorException($message, 0, $level, $file, $line);
        }
    }
}
登录后复制

set_exception_handler([$this, 'handleException'])将HandleExceptions的handleException方法注册为程序的全局处理器方法:

public function handleException($e)
{
    if (! $e instanceof Exception) {
        $e = new FatalThrowableError($e);
    }

    $this->getExceptionHandler()->report($e);

    if ($this->app->runningInConsole()) {
        $this->renderForConsole($e);
    } else {
        $this->renderHttpResponse($e);
    }
}

protected function getExceptionHandler()
{
    return $this->app->make(ExceptionHandler::class);
}

// 渲染CLI请求的异常响应
protected function renderForConsole(Exception $e)
{
    $this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
}

// 渲染HTTP请求的异常响应
protected function renderHttpResponse(Exception $e)
{
    $this->getExceptionHandler()->render($this->app['request'], $e)->send();
}
登录后复制

在处理器里主要通过ExceptionHandler的report方法上报异常、这里是记录异常到storage/laravel.log文件中,然后根据请求类型渲染异常的响应生成输出给到客户端。这里的ExceptionHandler就是\App\Exceptions\Handler类的实例,它是在项目最开始注册到服务容器中的:

// bootstrap/app.php

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
*/

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
*/
......

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);
登录后复制

这里再顺便说一下set_error_handler函数,它的作用是注册错误处理器函数,因为在一些年代久远的代码或者类库中大多是采用PHP那件函数trigger_error函数来抛出错误的,异常处理器只能处理Exception不能处理Error,所以为了能够兼容老类库通常都会使用set_error_handler注册全局的错误处理器方法,在方法中捕获到错误后将错误转化成异常再重新抛出,这样项目中所有的代码没有被正确执行时都能抛出异常实例了。

/**
 * Convert PHP errors to ErrorException instances.
 *
 * @param  int  $level
 * @param  string  $message
 * @param  string  $file
 * @param  int  $line
 * @param  array  $context
 * @return void
 *
 * @throws \ErrorException
 */
public function handleError($level, $message, $file = '', $line = 0, $context = [])
{
    if (error_reporting() & $level) {
        throw new ErrorException($message, 0, $level, $file, $line);
    }
}
登录后复制

常用的Laravel异常实例

Laravel中针对常见的程序异常情况抛出了相应的异常实例,这让开发者能够捕获这些运行时异常并根据自己的需要来做后续处理(比如:在catch中调用另外一个补救方法、记录异常到日志文件、发送报警邮件、短信)

在这里我列一些开发中常遇到异常,并说明他们是在什么情况下被抛出的,平时编码中一定要注意在程序里捕获这些异常做好异常处理才能让程序更健壮。

Illuminate\Database\QueryException Laravel中执行SQL语句发生错误时会抛出此异常,它也是使用率最高的异常,用来捕获SQL执行错误,比方执行Update语句时很多人喜欢判断SQL执行后判断被修改的行数来判断UPDATE是否成功,但有的情景里执行的UPDATE语句并没有修改记录值,这种情况就没法通过被修改函数来判断UPDATE是否成功了,另外在事务执行中如果捕获到QueryException 可以在catch代码块中回滚事务。

Illuminate\Database\Eloquent\ModelNotFoundException 通过模型的findOrFail和firstOrFail方法获取单条记录时如果没有找到会抛出这个异常(find和first找不到数据时会返回NULL)。

Illuminate\Validation\ValidationException 请求未通过Laravel的FormValidator验证时会抛出此异常。

Illuminate\Auth\Access\AuthorizationException  用户请求未通过Laravel的策略(Policy)验证时抛出此异常

Symfony\Component\Routing\Exception\MethodNotAllowedException 请求路由时HTTP Method不正确

Illuminate\Http\Exceptions\HttpResponseException  Laravel的处理HTTP请求不成功时抛出此异常

扩展Laravel的异常处理器

上面说了Laravel把\App\Exceptions\Handler 注册成功了全局的异常处理器,代码中没有被catch到的异常,最后都会被\App\Exceptions\Handler捕获到,处理器先上报异常记录到日志文件里然后渲染异常响应再发送响应给客户端。但是自带的异常处理器的方法并不好用,很多时候我们想把异常上报到邮件或者是错误日志系统中,下面的例子是将异常上报到Sentry系统中,Sentry是一个错误收集服务非常好用:

public function report(Exception $exception)
{
    if (app()->bound('sentry') && $this->shouldReport($exception)) {
        app('sentry')->captureException($exception);
    }

    parent::report($exception);
}
登录后复制

还有默认的渲染方法在表单验证时生成响应的JSON格式往往跟我们项目里统一的JOSN格式不一样这就需要我们自定义渲染方法的行为。

public function render($request, Exception $exception)
{
    //如果客户端预期的是JSON响应,  在API请求未通过Validator验证抛出ValidationException后
    //这里来定制返回给客户端的响应.
    if ($exception instanceof ValidationException && $request->expectsJson()) {
        return $this->error(422, $exception->errors());
    }

    if ($exception instanceof ModelNotFoundException && $request->expectsJson()) {
        //捕获路由模型绑定在数据库中找不到模型后抛出的NotFoundHttpException
        return $this->error(424, 'resource not found.');
    }


    if ($exception instanceof AuthorizationException) {
        //捕获不符合权限时抛出的 AuthorizationException
        return $this->error(403, "Permission does not exist.");
    }

    return parent::render($request, $exception);
}
登录后复制

自定义后,在请求未通过FormValidator验证时会抛出ValidationException, 之后异常处理器捕获到异常后会把错误提示格式化为项目统一的JSON响应格式并输出给客户端。这样在我们的控制器中就完全省略了判断表单验证是否通过如果不通过再输出错误响应给客户端的逻辑了,将这部分逻辑交给了统一的异常处理器来执行能让控制器方法瘦身不少。

使用自定义异常

这部分内容其实不是针对Laravel框架自定义异常,在任何项目中都可以应用我这里说的自定义异常。

我见过很多人在Repository或者Service类的方法中会根据不同错误返回不同的数组,里面包含着响应的错误码和错误信息,这么做当然是可以满足开发需求的,但是并不能记录发生异常时的应用的运行时上下文,发生错误时没办法记录到上下文信息就非常不利于开发者进行问题定位。

下面的是一个自定义的异常类

namespace App\Exceptions\;

use RuntimeException;
use Throwable;

class UserManageException extends RuntimeException
{
    /**
     * The primitive arguments that triggered this exception
     *
     * @var array
     */
    public $primitives;
    /**
     * QueueManageException constructor.
     * @param array $primitives
     * @param string $message
     * @param int $code
     * @param Throwable|null $previous
     */
    public function __construct(array $primitives, $message = "", $code = 0, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
        $this->primitives = $primitives;
    }

    /**
     * get the primitive arguments that triggered this exception
     */
    public function getPrimitives()
    {
        return $this->primitives;
    }
}
登录后复制

定义完异常类我们就能在代码逻辑中抛出异常实例了

class UserRepository
{
  
    public function updateUserFavorites(User $user, $favoriteData)
    {
        ......
        if (!$executionOne) {
            throw new UserManageException(func_get_args(), 'Update user favorites error', '501');
        }
        
        ......
        if (!$executionTwo) {
            throw new UserManageException(func_get_args(), 'Another Error', '502');
        }
        
        return true;
    }
}

class UserController extends ...
{
    public function updateFavorites(User $user, Request $request)
    {
        .......
        $favoriteData = $request->input('favorites');
        try {
            $this->userRepo->updateUserFavorites($user, $favoritesData);
        } catch (UserManageException $ex) {
            .......
        }
    }
}
登录后复制

除了上面Repository列出的情况更多的时候我们是在捕获到上面列举的通用异常后在catch代码块中抛出与业务相关的更细化的异常实例方便开发者定位问题,我们将上面的updateUserFavorites 按照这种策略修改一下

public function updateUserFavorites(User $user, $favoriteData)
{
    try {
        // database execution
        
        // database execution
    } catch (QueryException $queryException) {
        throw new UserManageException(func_get_args(), 'Error Message', '501' , $queryException);
    }

    return true;
}
登录后复制

在上面定义UserMangeException类的时候第四个参数$previous是一个实现了Throwable接口类实例,在这种情景下我们因为捕获到了QueryException的异常实例而抛出了UserManagerException的实例,然后通过这个参数将QueryException实例传递给PHP异常的堆栈,这提供给我们回溯整个异常的能力来获取更多上下文信息,而不是仅仅只是当前抛出的异常实例的上下文信息, 在错误收集系统可以使用类似下面的代码来获取所有异常的信息。

while($e instanceof \Exception) {
    echo $e->getMessage();
    $e = $e->getPrevious();
}
登录后复制

异常处理是PHP非常重要但又容易让开发者忽略的功能,这篇文章简单解释了Laravel内部异常处理的机制以及扩展Laravel异常处理的方式方法。更多的篇幅着重分享了一些异常处理的编程实践,这些正是我希望每个读者都能看明白并实践下去的一些编程习惯,包括之前分享的Interface的应用也是一样。

以上是Laravel核心分析之异常处理(代码)的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1654
14
CakePHP 教程
1413
52
Laravel 教程
1306
25
PHP教程
1252
29
C# 教程
1225
24
laravel入门实例 laravel入门实例 Apr 18, 2025 pm 12:45 PM

Laravel 是一款 PHP 框架,用于轻松构建 Web 应用程序。它提供一系列强大的功能,包括:安装: 使用 Composer 全局安装 Laravel CLI,并在项目目录中创建应用程序。路由: 在 routes/web.php 中定义 URL 和处理函数之间的关系。视图: 在 resources/views 中创建视图以呈现应用程序的界面。数据库集成: 提供与 MySQL 等数据库的开箱即用集成,并使用迁移来创建和修改表。模型和控制器: 模型表示数据库实体,控制器处理 HTTP 请求。

解决 Craft CMS 中的缓存问题:使用 wiejeben/craft-laravel-mix 插件 解决 Craft CMS 中的缓存问题:使用 wiejeben/craft-laravel-mix 插件 Apr 18, 2025 am 09:24 AM

在使用CraftCMS开发网站时,常常会遇到资源文件缓存的问题,特别是当你频繁更新CSS和JavaScript文件时,旧版本的文件可能仍然被浏览器缓存,导致用户无法及时看到最新的更改。这个问题不仅影响用户体验,还会增加开发和调试的难度。最近,我在项目中遇到了类似的困扰,经过一番探索,我找到了wiejeben/craft-laravel-mix这个插件,它完美地解决了我的缓存问题。

laravel用户登录功能 laravel用户登录功能 Apr 18, 2025 pm 12:48 PM

Laravel 提供了一个全面的 Auth 框架,用于实现用户登录功能,包括:定义用户模型(Eloquent 模型)创建登录表单(Blade 模板引擎)编写登录控制器(继承 Auth\LoginController)验证登录请求(Auth::attempt)登录成功后重定向(redirect)考虑安全因素:哈希密码、防 CSRF 保护、速率限制和安全标头。此外,Auth 框架还提供重置密码、注册和验证电子邮件等功能。详情请参阅 Laravel 文档:https://laravel.com/doc

继续使用PHP:耐力的原因 继续使用PHP:耐力的原因 Apr 19, 2025 am 12:23 AM

PHP仍然流行的原因是其易用性、灵活性和强大的生态系统。1)易用性和简单语法使其成为初学者的首选。2)与web开发紧密结合,处理HTTP请求和数据库交互出色。3)庞大的生态系统提供了丰富的工具和库。4)活跃的社区和开源性质使其适应新需求和技术趋势。

laravel框架安装方法 laravel框架安装方法 Apr 18, 2025 pm 12:54 PM

文章摘要:本文提供了详细分步说明,指导读者如何轻松安装 Laravel 框架。Laravel 是一个功能强大的 PHP 框架,它 упростил 和加快了 web 应用程序的开发过程。本教程涵盖了从系统要求到配置数据库和设置路由等各个方面的安装过程。通过遵循这些步骤,读者可以快速高效地为他们的 Laravel 项目打下坚实的基础。

Laravel如何学习 怎么免费学习Laravel Laravel如何学习 怎么免费学习Laravel Apr 18, 2025 pm 12:51 PM

想要学习 Laravel 框架,但苦于没有资源或经济压力?本文为你提供了免费学习 Laravel 的途径,教你如何利用网络平台、文档和社区论坛等资源,从入门到掌握,为你的 PHP 开发之旅奠定坚实基础。

laravel怎么查看版本号 laravel查看版本号方法 laravel怎么查看版本号 laravel查看版本号方法 Apr 18, 2025 pm 01:00 PM

Laravel框架内置了多种方法来方便地查看其版本号,满足开发者的不同需求。本文将探讨这些方法,包括使用Composer命令行工具、访问.env文件或通过PHP代码获取版本信息。这些方法对于维护和管理Laravel应用程序的版本控制至关重要。

laravel有哪些版本 laravel新手版本选择方法 laravel有哪些版本 laravel新手版本选择方法 Apr 18, 2025 pm 01:03 PM

在面向初学者的 Laravel 框架版本选择指南中,本文深入探讨了 Laravel 的版本差异,旨在协助初学者在众多版本之间做出明智的选择。我们将重点介绍每个版本的关键特征、比较它们的优缺点,并提供有用的建议,帮助新手根据他们的技能水准和项目需求挑选最合适的 Laravel 版本。对于初学者来说,选择一个合适的 Laravel 版本至关重要,因为它可以显著影响他们的学习曲线和整体开发体验。

See all articles