laravel异常分析

本文详细剖析了Laravel框架中的异常处理机制,包括PHP错误与异常处理的基础概念、命名空间的使用注意事项、Laravel异常处理流程及源码解读。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.起因:
	代码中经常出现各种错误,还是代码写的不健壮!打算通过 'try{}catch(){}' 来捕获异常,起码避免程序出错!

	在laravel的控制器使用了如下代码,一直不生效...
	<?php
	namespace App\Http\Controllers;
	class TestController extends Controller
	{
		function test(){
			try {
				echo $name;
			} catch (Exception $e) {
				die('变量未定义!');
			}
		}
	}
	怎么都想不通,然后决定重新深究下源码,看下laravel的机制!

	/*
		后来才意识到:
			使用了 'namspace' 命名空间的原因
			1.要么使用 \Exception
			2.要么开始 use Exception;
	 */

2.起因说完了,不管是因为什么,正好又复习了下PHP的异常处理!下面开始分析:
	1>先去看PHP手册,怎么定义异常和错误的!
	http://php.net/manual/zh/index.php
	语言参考:
		1)Errors
			basics
				1.PHP错误报告,反应的是程序内部的不同错误情况。有许多不同的错误情况,这些错误可以展示或者记录到日志里。
				PHP产生的每个错误,都包含一个类型。可用的错误类型有:
					预定义的常量,作为PHP核心的一部分。

					注意:
						在 php.ini 里,可以使用 '常量名';但在PHP之外,例如:httpd.conf,必须使用 '二进制位掩码'(是不是 常量的值 ??) 来替代

					1 - E_ERROR
					2 - E_WARNING
					4 - E_PARSE
					8 - E_NOTICE
					16 - E_CORE_ERROR
					32 - E_CORE_WARNING
					64 - E_COMPILE_ERROR
					128 - E_COMPILE_WARNING
					256 - E_USER_ERROR
					512 - E_USER_WARNING
					1024 - E_USER_NOTICE
					2048 - E_STRICT
					4096 - E_RECOVERABLE_ERROR
					8192 - E_DEPRECATED
					16384 - E_USER_DEPRECATED
					30719 - E_ALL

					上面的值,用于建立一个 '二进制位掩码',来指定要报告的错误信息。
					可以使用 '按位运算符' 来 '组合' 或 '屏蔽' 这些值,来表示是展示还是屏蔽某些错误!
					php.ini 中,只有 '|', '~', '^', '&' 会正确解析
				/*
					注意:
						以前一直都没细看过,现在才明白,php手册中的错误配置,为什么要使用 '位运算符',这里都解释了!
				 */

				2.handling errors with PHP
					如果没有设置错误处理器,PHP将会根据它的配置,来处理错误:
						error_reporting		// 或者运行时,使用 error_reporting()。
						1>建议配置中设置,因为,有些错误,可能是脚本执行前就发生了。
						2>开发环境中,应该将 error_reporting 设置为 'E_ALL'
						  生产环境中,可能希望展示较少的错误,例如:E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
						  但是建议都使用 E_ALL,早期就提示警告,或许可以避免后期潜在的大的问题
						display_errors		// 控制的是错误的输出
						1>开发环境,开启,便于调试
						  生产环境,关闭,可能包含数据等私密信息
						log_errors			// 除了可以输出错误,PHP可以将错误记录到日志中。
						1>生产环境,开启以记录错误!
						2>错误日志文件,通过 'error_log' 来配置

				3.user error handlers
					PHP默认的错误处理不满足我们的要求,我们可以自定义错误处理,通过 set_error_handlers()。

			PHP7错误处理
				1.不同于PHP5的错误报告机制,PHP7的大多数错误,作为 'Error异常' 抛出

				'Error异常' 可以像 'Exception异常' 一样,被 try/catch 来捕获。如果没有匹配的 catch,则调用 '异常处理函数'(set_exception_handler()注册) 进行处理。如果没有注册异常处理函数,则按照传统方式处理:PHP错误报告一个 '致命错误'(Fatal Error)
				/*
					解释下错误异常顺序:
						1.try / catch
						2.set_exception_handler()		// 注意:不是上面basic的 'set_error_handler()'
						3.PHP 错误报告
				 */

				Error类,并非继承 'Exception',不能使用 'catch(Exception $e)',而应该使用 'catch(Error $e)'。或者,通过 'set_exception_handler()' 来捕获 Error

				2.Error的层次结构

					Throwable
						Error
							ArithmeticError
								DivisionByZeroError
							AssertionError
							ParseError
							TypeError
						Exception

					/*
						可以看到:
							'Error' 和 'Exception',都继承 'Throwable'
					 */

				3.手册上,有人推荐了一篇文章,该文章介绍了 exceptions, throwables的区别,以及PHP7如何处理的。
					/*
						有时间看,看了2句,感觉确实很不错!
					 */

				4.兼容PHP5.x和PHP7.x的异常和错误捕获
					try {

					} catch (Exception $e) {		// PHP5.x执行

					} catch (Throwable $e) { 		// PHP7.x执行

					}



		2)异常处理
			1.扩展PHP内置的异常处理类

			2.讲述了捕获异常的结构
				try {

				} catch (Exception $e) {		// PHP5.x执行

				} catch (Throwable $e) { 		// PHP7.x执行

				} finally {			// 不管有没有捕获到异常,都会执行!PHP5.5+

				}

				catch 允许多个

				/*
					切记:
						当代码块中,有异常抛出,但并未被catch捕获,会转变为一个 'Fatal Error',并显示 'Uncaught Exception'
						除非,定义过 'set_exception_handler()'
						------
						这也是我们上面提到的异常处理的顺序
				 */

				注释:
					PHP内部函数,主要使用 'Error Reporting';仅有面向对象扩展,使用 '异常'。然后,通过 'ErrorException',错误可以很简单的被转换为异常!(不太懂。。。)

			3.手册上的评论,2个例子不错:
				1>当使用了 namespace(命名空间),一定不要忘记 '\' 反斜线,表示 '全局范围'(这也正是我开头犯得错误!)
					<?php
						namespace test;
						class Test {
							function test(){
								try {
									throw new \Exception('异常');		// 添加 '\'
								} catch (\Exception $e) {			// 添加 '\'
									var_dump($e->getMessage());
								}
							}	
						}

				2>关于try / catch /finally 里的 return

					<?php
						namespace test;
						class Test {
							public function test1()
							{
						        $bar = 1;
						        try{
						            throw new \Exception('I am Wu Xiancheng.');
						        }catch(\Exception $e){
						            return $bar;
						            $bar--; // this line will be ignored
						        }finally{
						            $bar++;
						            return $bar;
						        }
							}

							public function test2()
							{
						        $bar = 1;
						        try{
						            throw new \Exception('I am Wu Xiancheng.');
						        }catch(\Exception $e){
						            return $bar;
						            $bar--; // this line will be ignored
						        }finally{
						            $bar++;
						        }
							}

							public function dd()
							{
								echo $this->test1();		// 输出2。finally有return,try/catch返回的是finally里return的值
								echo $this->test2();		// 输出1。finally没有return,返回的是 catch里return的值
							}
						}

				3>预定义异常
					Exception 		// 所有异常的基类
					ErrorException	// 错误异常,ErrorException extends Exception


				4>SPL异常处理
					LogicException
						BadFunctionCallException
							BadMethodCallException
						DomainException
						InvalidArgumentException
						LengthException
						OutOfRangeException
					RuntimeException
						OutOfBoundsException
						OverflowException
						RangeException
						UnderflowException
						UnexpectedValueException

		异常和错误的几个相关函数:
			set_error_handler()
			set_exception_handler()
			restore_error_handler()		// 恢复之前定义过的错误处理函数(上一个错误处理函数)
			restore_exception_handler()	// 恢复之前定义过的异常处理函数(上一个异常处理函数)

	2>开始分析laravel底层的Exception
		查看文档:http://laravelacademy.org/post/3154.html 解释的很简单。

		从入口文件开始分析:
			public/index.php
		引入文件:
			bootstrap/autoload.php
			bootstrap/app.php
				1.实例化了 Illuminate\Foundation\Application,laravel应用实例,也就是所谓的 IoC 容器。
				2.绑定了3个核心类
					App\Http\Kernel::class				// 处理http请求
					App\Console\Kernel::class 			// 处理cli请求
					App\Exceptions\Handler::class		// 异常处理(也就是我们需要的!)
		接着回到入口文件:
			$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);		// 实例化了http内核

			// 经 handle() 处理后,返回响应
			$response = $kernel->handle(
			    $request = Illuminate\Http\Request::capture()		// 捕获HTTP请求,交给 http内核 handle() 来处理
			);

			// 将响应返回
			$response->send();	

			// 执行 http内核 的terminate()方法
			$kernel->terminate($request, $reponse);
			/*
				这里解释下:
					这个$kernel->terminate(),其实就是我们文档中,提到的 '可终止的中间件'。每个中间件,都可定义一个:
						terminate($request, $response);
					注意:
						1.包含 terminate() 的中间件,必须是 HTTP kernel 的 '全局中间件'!!
						2.当调用中间件上的 terminate() 时,Laravel 将会从服务容器中取出该中间件的新的实例,如果你想要在调用 handle 和 terminate 方法时使用同一个中间件实例,则需要使用容器的 singleton 方法将该中间件注册到容器中
					一直不明白文档中是什么意思,看下源码就知道了

					Illuminate\Foundation\Http\Kernel
					    public function terminate($request, $response)
					    {
					        $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
					            $this->gatherRouteMiddlewares($request),		// 路由中的中间件
					            $this->middleware 								// 全局中间件
					        );

					        foreach ($middlewares as $middleware) {
					            list($name, $parameters) = $this->parseMiddleware($middleware);

					            $instance = $this->app->make($name);			// 重新实例化了 '中间件',就是文档里说的,handle() 和 terminate() 并非同一个 '中间件类' 的实例

					            if (method_exists($instance, 'terminate')) {
					                $instance->terminate($request, $response);	// 调用中间件的 'terminate()'
					            }
					        }

					        $this->app->terminate();
					    }

			 */
		查看 http内核 文件:
			vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php

			继续我们上面的分析,handle()来处理 http请求
		    public function handle($request)
		    {
		        try {
		            $request->enableHttpMethodParameterOverride();

		            $response = $this->sendRequestThroughRouter($request);		// 处理请求,得到响应
		        } catch (Exception $e) {										// PHP5.x异常catch
		            $this->reportException($e);									// 异常报告

		            $response = $this->renderException($request, $e);			// 异常渲染
		        } catch (Throwable $e) {										// PHP7.x异常catch
		            $this->reportException($e = new FatalThrowableError($e));	// 异常报告

		            $response = $this->renderException($request, $e);			// 异常渲染
		        }

		        // 触发 'kernel.handled'(内核请求处理完毕事件)
		        $this->app['events']->fire('kernel.handled', [$request, $response]);

		        // 返回响应对象
		        return $response;
		    }

		    /*
			    总算是出现了我们的主题 "异常",可以看到,这里也使用到了 我们上面总结的PHP异常:
				    try {

				    } catch (Exception $e) {

				    } catch (Throwable $e) {

				    }
		     */ 

		    具体的代码,需要我们自己去好好理解Kernel.php的源码!
		    我们这里不要偏离主题,只分析异常:
		   	对于catch到异常后,最终处理的2个方法,都
			    protected function reportException(Exception $e)
			    {
			        $this->app[ExceptionHandler::class]->report($e);		// 调用我们最开始绑定的 'ExceptionHandler::class' 里的 report()
			    }

			    protected function renderException($request, Exception $e)
			    {
			        return $this->app[ExceptionHandler::class]->render($request, $e);		// 调用我们最开始绑定的 'ExceptionHandler::class' 里的 render();
			    }
			ExceptionHandler::class 即 'App\Exceptions\Handler::class',给我们开放的上层处理类!

		查看 app/Exceptions/Handler.php
			<?php

			namespace App\Exceptions;

			use Exception;
			use Illuminate\Session\TokenMismatchException;
			use Illuminate\Validation\ValidationException;
			use Illuminate\Auth\Access\AuthorizationException;
			use Illuminate\Database\Eloquent\ModelNotFoundException;
			use Symfony\Component\HttpKernel\Exception\HttpException;
			use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

			class Handler extends ExceptionHandler
			{
			    /**
			     * A list of the exception types that should not be reported.
			     *
			     * @var array
			     */
			    protected $dontReport = [
			        AuthorizationException::class,
			        HttpException::class,
			        ModelNotFoundException::class,
			        TokenMismatchException::class,
			        ValidationException::class,
			    ];

			    /**
			     * Report or log an exception.
			     *
			     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
			     *
			     * @param  \Exception  $e
			     * @return void
			     */
			    public function report(Exception $e)
			    {
			        parent::report($e);			// 又调用的是 'Illuminate\Foundation\Exceptions\Handler' 的report()
			    }

			    /**
			     * Render an exception into an HTTP response.
			     *
			     * @param  \Illuminate\Http\Request  $request
			     * @param  \Exception  $e
			     * @return \Illuminate\Http\Response
			     */
			    public function render($request, Exception $e)
			    {
			        return parent::render($request, $e);	// 又调用的是 'Illuminate\Foundation\Exceptions\Handler' 的render()
			    }
			}	
			'Illuminate\Foundation\Exceptions\Handler',就是我们目前看到的 laravel 的默认异常处理类
			觉得不喜欢,我们可在 app/Exceptions/Handler.php,不调用 'parent::report()' 和 'parent::render()',我们自己来定义!

至此,laravel的异常的整个流程,我们就走了一遍,写一篇分析好痛苦,自己总结下,也希望对跟我一样的小白有所帮助!

分享一篇laravel异常文章:
	https://laravel-china.org/topics/2460/embrace-exceptions-with-laravel
自我感觉这种写法不见的好...

新增:
	今天重新思考项目中的异常问题,是否自己应该在控制器中使用 try catch,发现:
		如果自己使用了,捕获到异常后,直接return 响应,发现这样虽然避免了错误发生,但是并不会走laravel的异常机制,以后我们的sentry日常提醒等,也都不行!
		如果使用 try catch,除非是我们自定义了更详细的异常,而且还得throw出来,给系统捕获!我们在项目中,应该直接走公共的异常处理!




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值