ThinkPHP6.0自定义异常操作实践

目录
  1. 1. render方法
    1. 1.1. 用户端异常
  2. 2. report方法
  3. 3. 最后一步

在app目录下创建Exception文件夹,在该文件夹中创建一个名为exceptionHandle的类,继承于think\exception下的Handle父类

1
2
3
4
5
6
7
<?php
namespace app\Exception;
use think\exception\Handle;

class ExceptionHandle extends Handle
{
}

在TP6中,你可能已经发现在app文件夹下已经有一个ExceptionHandle类了,这个类重写了父类Handle中的两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 记录异常信息(包括日志或者其它方式记录)
*
* @access public
* @param Throwable $exception
* @return void
*/
public function report(Throwable $exception): void
{
// 使用内置的方式记录异常日志
parent::report($exception);
}

/**
* Render an exception into an HTTP response.
*
* @access public
* @param \think\Request $request
* @param Throwable $e
* @return Response
*/
public function render($request, Throwable $e): Response
{
// 添加自定义异常处理机制

// 其他错误交给系统处理
return parent::render($request, $e);
}

下面的步骤就是对这两个方法就行重写。

render方法

render方法是TP6中的异常处理方法,它将TP6中的异常反馈给HTTP请求。
通常将异常分为用户端异常和服务端异常,用户端异常就是用户在请求数据时产生的一些异常,如Token过期、密码错误等;服务端异常可能是服务器、数据库等和用户无关的异常。在对TP6的异常进行自定义时,主要区分异常是来自用户端还是来自服务端。

用户端异常

为了区分两种不同的异常,在Exception目录下面创建一个BaseException子类,继承于think\下的Exception父类。在BaseException中创建三个成员变量,并赋初值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
namespace app\exception;

use think\Exception;

class BaseException extends Exception
{
public $code = 0;
public $msg = '请求错误';
public $httpCode = 200;

public function __construct($msg = null, $code = null)
{
if ($msg) {
$this->msg = $msg;
}
if ($code) {
$this->code = $code;
}
}
}

think\Exception类继承了\ExceptionException是所有异常的基类。BaseException中的变量$httpCode表示HTTP状态码,$msg表示返回信息,$code表示错误码。请注意,HTTP状态码和错误码并不是等价的,HTTP状态码是是用以表示网页服务器超文本传输协议响应状态的3位数字代码,它是国际通用的,而错误码是我们自定义的一套数字代码,没有比较成熟或者通用的设计方法,可以用于对应错误信息、判断是否返回了正确的信息等。
下面所有继承于BaseException的类并且带有都是自定义异常,比如说我定义了一个参数异常的类ParameterException:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace app\exception;

/**
* Class ParameterException
* 通用参数类异常错误
*/
class ParameterException extends BaseException
{
public function __construct($msg = null, $code = 400)
{
parent::__construct($msg, $code);
}
}

回到ExceptionHandle类中,下面对render方法进行重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
namespace app\exception;

use Throwable;
use think\Response;
use think\exception\Handle;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;

class ExceptionHandle extends Handle
{
/**
* 不需要记录信息(日志)的异常类列表
* @var array
*/
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
];

// 自定义异常处理机制
public function render($request, Throwable $e): Response
{
if ($e instanceof BaseException) {
$code = $e->code;
$msg = $e->msg;
$httpCode = $e->httpCode;
} else {
// 错误交给系统处理
if (env('APP_DEBUG') == true) {
return parent::render($request, $e);
}
$httpCode = 500;
$msg = '服务器异常';
$code = 0;

// 排除 不需要记录日志的异常类列表
if (!$this->isIgnoreReport($e)) {
$this->reportException($request, $e);
}
}
$data = [
'msg' => $msg,
'code' => $code,
// 'url' => $request->url()
];
return json($data, $httpCode);
}

//记录exception到日志
private function reportException($request, Throwable $e): void
{
$errorStr = "url:" . $request->host() . $request->url() . "\n";
// $errorStr .= "code:" . $e->getCode() . "\n";
$errorStr .= "file:" . $e->getFile() . "\n";
$errorStr .= "line:" . $e->getLine() . "\n";
$errorStr .= "message:" . $e->getMessage() . "\n";
// $errorStr .= $e->getTraceAsString();

trace($errorStr, 'error');
}
}

前面说到render方法将TP6中的异常反馈给HTTP请求,$e是异常的一个实例,通过类型运算符instanceof判断该异常是否继承于BaseException。在开发环境下,通常需要详细的TP6异常信息,所以还需判断APP_DEBUG是否为TRUE

report方法

report方法是TP6记录日志的一个方法,在异常发生时记录异常信息,对于用户异常和服务端异常,通常需要记录的是服务端异常,所以对report方法重写如下:

1
2
3
4
5
6
7
8
public function report(Throwable $exception): void
{
//系统异常才记录日志
if (!($exception instanceof BaseException)) {
// 使用内置的方式记录异常日志
parent::report($exception);
}
}

最后一步

app目录下有一个provider.php文件,它是容器Provider的定义文件。需要在app目录下面的provider.php文件中绑定异常处理类,即上面实现的ExceptionHandle类。

1
2
3
4
5
6
7
8
<?php
// 容器Provider定义文件
return [
'think\Request' => Request::class,
// 'think\exception\Handle' => ExceptionHandle::class,
// 绑定自定义异常处理handle类
'think\exception\Handle' => '\\app\\exception\\ExceptionHandle',
];

本文是对TP6异常处理的一些见解,其实在不同的框架或者编程语言中都能按照这种思路来编写自定义异常处理。
语言和框架只是一个工具,重要的是工具背后的思想。