Laravel 异常与错误处理机制

Hello,我是Rocket

这是我参与更文挑战的第2天,活动详情查看:更文挑战

引言

  • Laravel框架是处理错误和异常的机制是什么
  • 工作中如何自定义异常响应
  • 如何修改异常响应页面
  • 接下来带着大家去深入理解

1、什么时候注册自定义的异常和错误处理程序

内核启动的时候加载参考我的上一篇文章

\Illuminate\Foundation\Bootstrap\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']);//会在php中止时执行的函数
        if (! $app->environment('testing')) {
            ini_set('display_errors', 'Off');
        }
}

复制代码

把符合的error转换成异常类

public function handleError($level, $message, $file = '', $line = 0, $context = [])
{
        if (error_reporting() & $level) {
            throw new ErrorException($message, 0, $level, $file, $line);
        }
}
复制代码

异常处理 报告+输出页面

public function handleException($e)
{
        if (! $e instanceof Exception) {
            //非异常类要转换成FatalThrowableError 
            $e = new FatalThrowableError($e);
        }

        try {
            
            $this->getExceptionHandler()->report($e);
        } catch (Exception $e) {
            //
        }

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


protected function renderForConsole(Exception $e)
{
        $this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
}
    
protected function renderHttpResponse(Exception $e)
{
        $this->getExceptionHandler()->render($this->app['request'], $e)->send();
}
复制代码

监听php脚本中断如果有错误并且错误类型包含就转换从FatalErrorException并且处理异常

public function handleShutdown()
{
        if (! is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
            $this->handleException($this->fatalExceptionFromError($error, 0));
        }
}

protected function isFatal($type)
{
        return in_array($type, [E_COMPILE_ERROR, E_CORE_ERROR, E_ERROR, E_PARSE]);
}

protected function fatalExceptionFromError(array $error, $traceOffset = null)
{
        return new FatalErrorException(
            $error['message'], $error['type'], 0, $error['file'], $error['line'], $traceOffset
       );
}
复制代码

获取异常处理程序

protected function getExceptionHandler()
{
    //获得异常处理类   app/Exceptions/Handler.php
        return $this->app->make(ExceptionHandler::class);
}
复制代码

app/Exceptions/Handler.php 框架扩展的异常类

class Handler extends ExceptionHandler
{

    public function report(Exception $exception)
    {
        parent::report($exception);
    }
    
    public function render($request, Exception $exception)
    {
        return parent::render($request, $exception);
    }
    
}
复制代码

Illuminate\Foundation\Exceptions\Handler 框架基本异常类
实现了接口 Illuminate\Contracts\Debug\ExceptionHandler

public function report(Exception $e)
{
     //是否报告  在dontReport和internalDontReport里的就可以忽略异常
    if ($this->shouldntReport($e)) {
        return;
    }
    
    //如果异常类有自定义报告方法则自定义执行
    if (method_exists($e, 'report')) {
        return $e->report();
    }

    try {
        $logger = $this->container->make(LoggerInterface::class);
    } catch (Exception $ex) {
        throw $e;
    }

    //记录日志(可以考虑专门抽一个错误日志)
    $logger->error(
        $e->getMessage(),
        array_merge($this->context(), ['exception' => $e]
    ));
}


protected function shouldntReport(Exception $e)
{
    $dontReport = array_merge($this->dontReport, $this->internalDontReport);

    return ! is_null(Arr::first($dontReport, function ($type) use ($e) {
        return $e instanceof $type;
    }));
}

protected function context()
{
        try {
            return array_filter([
                'userId' => Auth::id(),
                'email' => Auth::user() ? Auth::user()->email : null,
            ]);
        } catch (Throwable $e) {
            return [];
        }
}
复制代码

错误输出

public function render($request, Exception $e)
{
        //异常类如果存在render就输出自定义的
        if (method_exists($e, 'render') && $response = $e->render($request)) {
            return Router::toResponse($request, $response);
        } elseif ($e instanceof Responsable) {//如果实现了Responsable接口就直接输出toResponse方法
            return $e->toResponse($request);
        }
        //解析异常并转换
        $e = $this->prepareException($e);

        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            //未登录
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof ValidationException) {
            //验证异常
            return $this->convertValidationExceptionToResponse($e, $request);
        }

        return $request->expectsJson()
                        ? $this->prepareJsonResponse($request, $e)
                        : $this->prepareResponse($request, $e);
}


 protected function prepareException(Exception $e)
{
        if ($e instanceof ModelNotFoundException) {
            $e = new NotFoundHttpException($e->getMessage(), $e);
        } elseif ($e instanceof AuthorizationException) {
            $e = new AccessDeniedHttpException($e->getMessage(), $e);
        } elseif ($e instanceof TokenMismatchException) {//csrf token
            $e = new HttpException(419, $e->getMessage(), $e);
        }

        return $e;
}
复制代码

prepareJsonResponse


//返回json响应体    
protected function prepareJsonResponse($request, Exception $e)
  {
        return new JsonResponse(
            $this->convertExceptionToArray($e),
            $this->isHttpException($e) ? $e->getStatusCode() : 500,
            $this->isHttpException($e) ? $e->getHeaders() : [],
            JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
        );
}


protected function convertExceptionToArray(Exception $e)
    {
        return config('app.debug') ? [
            'message' => $e->getMessage(),
            'exception' => get_class($e),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => collect($e->getTrace())->map(function ($trace) {
                return Arr::except($trace, ['args']);
            })->all(),
        ] : [
            'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
        ];
    }
复制代码

prepareResponse


//返回响应体
protected function prepareResponse($request, Exception $e)
{
         
        if (! $this->isHttpException($e) && config('app.debug')) {
            //这块是我们本地调试的时候错误页面
            return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
        }

        if (! $this->isHttpException($e)) {
            $e = new HttpException(500, $e->getMessage());
        }

        return $this->toIlluminateResponse(
            $this->renderHttpException($e), $e
        );
}


//主要是debug = true的时候
protected function convertExceptionToResponse(Exception $e)
{
        return SymfonyResponse::create(
            $this->renderExceptionContent($e),
            $this->isHttpException($e) ? $e->getStatusCode() : 500,
            $this->isHttpException($e) ? $e->getHeaders() : []
        );
}

protected function renderExceptionContent(Exception $e)
{
        try {
            //debug为true的时候返回
            return config('app.debug') && class_exists(Whoops::class)
                        ? $this->renderExceptionWithWhoops($e)
                        : $this->renderExceptionWithSymfony($e, config('app.debug'));
        } catch (Exception $e) {
            return $this->renderExceptionWithSymfony($e, config('app.debug'));
        }
}


//如果是HttpException异常或者非调试环境
protected function renderHttpException(HttpException $e)
    {
        //注册错误页面
        $this->registerErrorViewPaths();

        //如果存在对应的错误码的错误页就直接视图输出
        if (view()->exists($view = "errors::{$e->getStatusCode()}")) {
            return response()->view($view, [
                'errors' => new ViewErrorBag,
                'exception' => $e,
            ], $e->getStatusCode(), $e->getHeaders());
        }

        return $this->convertExceptionToResponse($e);
    }


复制代码

2、流程图

流程.png

3、如何忽略异常

加到dontReport

4、将错误写入日志

是在report报告的时候写入日志的

5、自定义异常响应

实现了php的Exception即可,可以自己定义report和render

6、修改错误页面

参考第7点注册错误页面到视图

7、可以自定义方法 在app/Exceptions/Handler.php

unauthenticated 未授权异常处理

protected function unauthenticated($request, AuthenticationException $exception)
{
        你的逻辑
}
复制代码

convertValidationExceptionToResponse 验证类异常处理

protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
        你的逻辑
}
复制代码

registerErrorViewPaths 注册错误页面到视图

 protected function registerErrorViewPaths()
{   
       你的逻辑这块要看下视图处理
}
复制代码

8、如何优雅的线上调试(不用去调错误日志并且筛选)


设置类成员

protected $debug=false;

public function __construct(Container $container)
{
        $this->container = $container;
        //调试
        $this->debug = isset($_GET['debug'])? $_GET['debug'] == 'h5debug':config('app.debug');

}


protected function prepareResponse($request, Exception $e)
{
         
        if (! $this->isHttpException($e) && $this->debug) {
            //这块是我们本地调试的时候错误页面
            return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
        }

        if (! $this->isHttpException($e)) {
            $e = new HttpException(500, $e->getMessage());
        }

        return $this->toIlluminateResponse(
            $this->renderHttpException($e), $e
        );
}



protected function renderExceptionContent(Exception $e)
{
        try {
            //debug为true的时候返回
            return $this->debug && class_exists(Whoops::class)
                        ? $this->renderExceptionWithWhoops($e)
                        : $this->renderExceptionWithSymfony($e, config('app.debug'));
        } catch (Exception $e) {
            return $this->renderExceptionWithSymfony($e, config('app.debug'));
        }
}



//json请求返回
protected function convertExceptionToArray(Exception $e)
{
        return $this->debug? [
            'message' => $e->getMessage(),
            'exception' => get_class($e),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => collect($e->getTrace())->map(function ($trace) {
                return Arr::except($trace, ['args']);
            })->all(),
        ] : [
            'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
        ];
}


复制代码

结尾

与诸君共勉之,希望对您有所帮助

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享