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出来,给系统捕获!我们在项目中,应该直接走公共的异常处理!