laravel异常分析

来源:互联网 发布:数据挖掘的工具 编辑:程序博客网 时间:2024/05/21 11:04
1.起因:代码中经常出现各种错误,还是代码写的不健壮!打算通过 'try{}catch(){}' 来捕获异常,起码避免程序出错!在laravel的控制器使用了如下代码,一直不生效...<?phpnamespace App\Http\Controllers;class TestController extends Controller{function test(){try {echo $name;} catch (Exception $e) {die('变量未定义!');}}}怎么都想不通,然后决定重新深究下源码,看下laravel的机制!/*后来才意识到:使用了 'namspace' 命名空间的原因1.要么使用 \Exception2.要么开始 use Exception; */2.起因说完了,不管是因为什么,正好又复习了下PHP的异常处理!下面开始分析:1>先去看PHP手册,怎么定义异常和错误的!http://php.net/manual/zh/index.php语言参考:1)Errorsbasics1.PHP错误报告,反应的是程序内部的不同错误情况。有许多不同的错误情况,这些错误可以展示或者记录到日志里。PHP产生的每个错误,都包含一个类型。可用的错误类型有:预定义的常量,作为PHP核心的一部分。注意:在 php.ini 里,可以使用 '常量名';但在PHP之外,例如:httpd.conf,必须使用 '二进制位掩码'(是不是 常量的值 ??) 来替代1 - E_ERROR2 - E_WARNING4 - E_PARSE8 - E_NOTICE16 - E_CORE_ERROR32 - E_CORE_WARNING64 - E_COMPILE_ERROR128 - E_COMPILE_WARNING256 - E_USER_ERROR512 - E_USER_WARNING1024 - E_USER_NOTICE2048 - E_STRICT4096 - E_RECOVERABLE_ERROR8192 - E_DEPRECATED16384 - E_USER_DEPRECATED30719 - 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 handlersPHP默认的错误处理不满足我们的要求,我们可以自定义错误处理,通过 set_error_handlers()。PHP7错误处理1.不同于PHP5的错误报告机制,PHP7的大多数错误,作为 'Error异常' 抛出'Error异常' 可以像 'Exception异常' 一样,被 try/catch 来捕获。如果没有匹配的 catch,则调用 '异常处理函数'(set_exception_handler()注册) 进行处理。如果没有注册异常处理函数,则按照传统方式处理:PHP错误报告一个 '致命错误'(Fatal Error)/*解释下错误异常顺序:1.try / catch2.set_exception_handler()// 注意:不是上面basic的 'set_error_handler()'3.PHP 错误报告 */Error类,并非继承 'Exception',不能使用 'catch(Exception $e)',而应该使用 'catch(Error $e)'。或者,通过 'set_exception_handler()' 来捕获 Error2.Error的层次结构ThrowableErrorArithmeticErrorDivisionByZeroErrorAssertionErrorParseErrorTypeErrorException/*可以看到:'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(命名空间),一定不要忘记 '\' 反斜线,表示 '全局范围'(这也正是我开头犯得错误!)<?phpnamespace test;class Test {function test(){try {throw new \Exception('异常');// 添加 '\'} catch (\Exception $e) {// 添加 '\'var_dump($e->getMessage());}}}2>关于try / catch /finally 里的 return<?phpnamespace 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 Exception4>SPL异常处理LogicExceptionBadFunctionCallExceptionBadMethodCallExceptionDomainExceptionInvalidArgumentExceptionLengthExceptionOutOfRangeExceptionRuntimeExceptionOutOfBoundsExceptionOverflowExceptionRangeExceptionUnderflowExceptionUnexpectedValueException异常和错误的几个相关函数: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.phpbootstrap/app.php1.实例化了 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<?phpnamespace 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出来,给系统捕获!我们在项目中,应该直接走公共的异常处理!


原创粉丝点击