一、介绍
文本将从PHP原生错误/异常 讲解到 Laravel 错误/异常,耐心看完,相信你会有所收获。
1.1 什么是错误
在 PHP 中,最常见错误的级别有
错误类型 | 解释 |
---|
Deprecated | 比如 API 过期 ,属于低级错误 | Notice | 变量未定义 | Warning | 结果不符合逻辑,比如函数里面 $num + 100,但 $num 传递进来的是 ‘ab’ | Fetal | 致命错误,直接终止运行后面的运行,比如调用不存在的函数 | Prase | 最高级别错误,以上的都是运行期间报错,这里直接在语法解析期间报错。 |
给上面对应的错误类型举几个例子
<?php
echo($abc);
function foo($num){
echo $num + 100;
}
foo('hello');
abc();
echo "这句话将不会被打印出来";
另外,PHP 也提供了 set_error_handler ,此函数将会把所有把错误交给你接管,而 Laravel 正是利用了此函数进行重写,比如格式化输出、将所有错误以异常抛出去等等,具体怎么使用这里不在讲述,可参考文档。
1.2 什么是异常
PHP 是在 PHP5 后才引入异常的,每个语言对异常的理解各有不同,比如在 PHP 中 除 0 它是一个 warning 级别错误行为而不是异常,而在 JAVA 中它是属于异常行为。
$abc = 10 / 0;
echo $abc;
所以很多时候异常都是由我们通过 if else 来手动控制抛出的,比如用户手机未输入
<?php
try {
if (empty($_GET['phone'])) {
throw new Exception('请输入手机号');
}
} catch (Exception $e) {
echo $e->getMessage();
}
注意:捕获异常需要用到 try / catch,如果直接在外部使用 throw new Exception ,它将会是一个错误行为
throw new Exception('请输入手机号');
其实异常更多的作用是用来进行一些补救措施,比如数据库事务的回滚, 举个简单例子:用户提交订单
beginTransaction();
try {
DB::update('从数据库中扣掉商品库存');
if (empty($_POST['phone'])) {
throw new Exceptions('请输入手机');
}
commit();
} catch(Exception $e) {
rollback();
}
值得注意的是,在 PHP 7 以前,try/catch 是捕获不了错误的,比如调用不存在的函数
try {
abc();
} catch(Exception $e) {
echo '函数不存在' . $e->getMessage();
}
后来为了让部分错误 能够捕获到,在 PHP 7 中引入了 Throwable ,可以说是 Exception 的升级版,但它只能对部分错误 进行捕获,像下面的调用未知函数就可以捕获到。
try {
abc();
} catch (Throwable $e) {
echo "函数不存在:" . $e->getMessage();
}
而像下面的访问未定义变量,依然还是捕获不了,直接一个报错行为。
try {
echo $abc;
} catch (Throwable $e){
echo "变量不存在" . $e->getMessage();
}
另外,PHP 也提供了 set_exception_handler ,次函数将所有异常都交给你接管, 显然 Laravel 也不会放过这个函数。
好了,接下来我们开始进入 Laravel
二、Laravel 中的错误与异常
理解了 PHP 中的基本错误/异常后,现在我们来开始使用 Laravel 中的错误/异常。
本文假设你已创建 Laravel 项目。
2.1 错误与异常的默认处理
我们首先抛出一个错误,看看在 Laravel 中会发生什么,假设我们有个 test 路由。
use Illuminate\Support\Facades\Route;
Route::get('test', function(){
echo $abc;
});
接下来访问 http://localhost:8000/test ,输出如下: 可以看到,Laravel 展示的是一个美化过的错误页面。
另外我们也知道原生 try/catch 是无法捕获到未知变量这种错误行为的,但 Laravel 为我们做到了
use Illuminate\Support\Facades\Route;
Route::get('/test', function(){
try {
echo $abc;
} catch(Exception $e) {
echo '变量不存在:'.$e->getMessage();
}
});
很明显 Laravel 利用 set_error_handler 对错误进行了重写并以异常的形式抛出,这点值得称赞。
2.2 关于错误/异常发生后,Laravel 的日志记录问题
每次发生异常或者报错 Laravel 都会为我们写入日志文件里,下面是针对 try 内外的情况进行分析。
try 外
- 两者都被记录在
app/storage/logs 里面
echo $abc;
throw new Exception("抛出异常");
try 内
- 异常不会被记录,但可以手动调用
report() 进行记录,而错误会自动被记录。
try {
throw new Exception("抛出异常");
} catch (Exception $e) {
report();
}
我们只需记得:使用 try/catch 后,权限交给用户,不使用则交给 Laravel
2.3 给 log 日志添加额外数据
每次发生异常或错误时我们想要给日志增加额外数据方便后续排查,需要怎么做?很简单,只需在 app/Exceptions/Handler.php 里面新增 context 方法进行添加即可,比如
public function context() {
return array_merge(parent::context(), [
'user' => 'Cookcyq',
]);
}
2.4 自定义错误/异常页面
如上所知,默认情况下 Laravel 会自动返回一个美化过的页面,如果你不喜欢这种风格,Laravel 还提供了 $this->renderable 让你重写,我们来改造一下,依然是在 app/Exceptions/Handler.php 里面修改。
public function register(){
$this->renderable(function(Throwable $e) {
return response([
'msg' => $e->getMessage(),
'line' => $e->getLine(),
]);
});
}
use Illuminate\Support\Facades\Route;
Route::get('/test', function(){
echo $abc;
});
现在我们来访问 http://localhost:8000/test
可以看到页面变成我们自定义的了,想要更多日志操作可参考官方文档:https://laravel.com/docs/9.x/errors
三、Laravel 自定义异常
由于上面的 $this->renderable 是全局性的,每个异常/错误都会触发,有时我们并不像这样做,而是仅针对某个功能,其它则按照 Laravel 默认模板正常显示,比如我们有个注册,如果注册有问题,就抛出注册异常,正好 Laravel 也为我们提供了这种能力。
- 首先在
app/Exceptions/ 目录下新增 RegisterException.php 文件,代码如下:
<?php
namespace App\Exceptions;
use Exception;
class RegisterException extends Exception
{
public function context() {
return ['myException' => 'Cookcyq'];
}
public function render($request) {
return response([
'msg' => $this->getMessage()
]);
}
}
route/web.php 代码如下
use Illuminate\Support\Facades\Route;
use App\Exceptions\RegisterException;
Route::get('/test', function(){
throw new RegisterException("Please make sure your account or password are correct.");
});
效果
四、业务案例
在实际业务中,假如我们是 API 提供者,我们需要对传递进来的参数不正确、残缺等情况进行响应处理,这个时候我们就可以利用自定义 Exception。
- 还是老样子,首先在
app/Exceptions/ 新建 MyException.php 文件,代码如下
<?php
namespace App\Exceptions;
use Exception;
class MyException extends Exception
{
public function render($request) {
$data = [
'msg' => $this->getMessage(),
'status' => $this->code
];
return response()->json($data)->setEncodingOptions(JSON_UNESCAPED_UNICODE);
}
}
- 定义
/login 路由,代码如下
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Exceptions\MyException;
Route::get('/login', function(Request $request){
if (empty($request['phone'])) {
throw new MyException("请输入手机", -10000);
}
if (empty($request['password'])) {
throw new MyException("请输入密码", -10000);
}
return response([
'msg' => '登录成功',
'status' => 10000,
]);
});
此时我们访问:http://localhost:8000/login
访问 http://localhost:8000/login?phone=10086&password=secret 怎么样,异常的处理是不是很简单?
总结
Laravel 利用了 set_error_handler / set_exception_handler 对错误/异常进行了重写,给开发者带来了许多便利之处。
希望本文能让你对 PHP 原生和 Laravel 中错误/异常的区别有一定的认识。
|