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、流程图
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