上一篇是使用後置中介軟體自定義介面成功返回格式
這篇來寫處理異常,自定義介面錯誤返回格式
基礎小知識
laravel處理異常的位置在app/Exceptions
這個目錄,如果新建異常類,就在這個目錄
這個目錄中,最重要的是Handler.php
這個檔案,如何處理渲染異常,是這個類的rander方法。如果你需要自定義錯誤輸出,其實就是重寫這個rander方法。
leo老師在電商實戰那本書裡第5.7節-優雅地處理異常 裡面教過,
Laravel 5.5 之後支援在異常類中定義 render() 方法,該異常被觸發時系統會呼叫 render() 方法來輸出,我們在 render() 裡判斷如果是 AJAX 請求則返回 JSON 格式的資料,否則就返回一個錯誤頁面。
這就是說,我們只要新建一個 xxxException
類,讓它繼承自 Exception
,
在這個 xxxException
類中,定義一個建構函式,用來接收引數(如$message, $code),
再定義一個render方法,處理接收的引數,組裝成你想要的格式(如$content = ['message' => $this->message, 'code' => $this->code]
),最後返回return response()->json($content, $status);
,$content就是你自定義的異常輸出內容,$status是http狀態碼。
然後在控制器方法中,throw new xxxException('message',500)
。試一下,就是你自定義的錯誤格式了。
可以新建多個異常類,用來輸出不同的異常。
最簡單的異常類:
namespace App\Exceptions;
use Exception;
class SimpleException extends Exception
{
const HTTP_OK = 200;
const HTTP_ERROR = 500;
protected $data;
public function __construct($message, int $code = self::HTTP_OK, array $data = [])
{
$this->data = $data;
parent::__construct($message, $code);
}
public function render()
{
$content = [
'message' => $this->message,
'code' => $this->code,
'data' => $this->data ?? [],
'timestamp' => time()
];
$status = self::HTTP_ERROR;
return response()->json($content, $status);
}
}
如果不想新建異常類,只需要統一異常錯誤輸出,就修改app/Exceptions/Handler中的render方法就好了。
public function render($request, Exception $exception)
{
// 這裡通常要用到 instanceof 這個運算子,先判斷再處理
// return parent::render($request, $exception); 註釋掉這行
return response()->json($exception->getMessage(), 500);
}
做個小總結,自定義錯誤處理有兩個方法(其實都一樣)
1.新建一個異常類,在app/Exceptions/Handler中的render方法將所有異常都指向你新建的異常類。
2.直接修改app/Exceptions/Handler中的render方法,自定義異常格式。
第一種:BaseException
BaseException是一個很靈活的異常類,可以自定義多種引數。
- 在app/Exceptions下新建BaseException
namespace App\Exceptions;
use Exception;
class BaseException extends Exception
{
const HTTP_OK = 200;
protected $data;
protected $code;
public function __construct($data, int $code = self::HTTP_OK, array $meta = [])
{
// 第一個引數是data,是因為想相容string和array兩種資料結構
// 第二個引數預設取200,是因為公司前端框架限制,所以是status取200,錯誤碼用code表示
// 如果第二個引數是任意httpStatus(如200,201,204,301,422,500),就只返回httpStatus,如果是自定義錯誤編碼,(如600101,600102),就返回httpstatus為200,返回體中包含message和code。
// 第三個引數預設為空陣列,如果在message和code之外,還需要返回陣列,就傳遞第三個引數
$this->data = $data;
$this->code = $code;
$this->meta = $meta;
// parent::__construct($data, $code);
}
public function render()
{
$httpStatus = getHttpStatus();
$status = in_array($this->code, $httpStatus) ? $this->code : self::HTTP_OK;
$content = [];
if (is_array($this->data)) {
$content = $this->data;
}
if (is_string($this->data)) {
$content = in_array($this->code, $httpStatus)
? [
'message' => $this->data
]
: [
'message' => $this->data,
'code' => $this->code,
// 'timestamp' => time()
];
}
if ($this->meta) {
$content = array_add($content, 'meta', $this->meta);
}
return response($content, $status);
}
}
- 在helpers中增加函式:(或者直接寫在這個異常類中,私有呼叫)
該函式是獲取Symfony定義的所有Http狀態碼。比如200=HTTP_OK。function getHttpStatus() { $objClass = new \ReflectionClass(\Symfony\Component\HttpFoundation\Response::class); // 此處獲取類中定義的全部常量 返回的是 [key=>value,...] 的陣列,key是常量名,value是常量值 return array_values($objClass->getConstants()); }
3.基礎使用
baseException($data, int $code=200, array $meta=[]);
第1個引數可以為string 或 array.
第2個引數預設為200,如果傳的code是任意一個httpStatus,表示返回的http狀態碼(如404、500等),
如果是自定義錯誤碼(非任意一個httpStatus,如1001、1002),則http狀態碼返回200,code碼在json內容中返回
第3個引數預設為空陣列。如果傳第3個引數,將一起返回。
3.1 引數傳string
throw new BaseException('都是好孩子');
Status: 200 OK
{
"message": "都是好孩子"
}
3.2 引數傳string,code(自定義錯誤碼,非httpStatus)
throw new BaseException('都是好孩子',1001);
Status: 200 OK
{
"message": "都是好孩子",
"code": 1001
}
3.3 引數傳string,code(httpStatus)
throw new BaseException('都是好孩子', 404);
Status: 404 Not Found
{
"message": "都是好孩子"
}
3.4 引數傳array
throw new BaseException(['msg' => '都是好孩子', 'code' => '123']);
Status: 200 OK
{
"msg": "都是好孩子",
"code": "123"
}
3.5 引數傳array,code(httpStatus)
throw new BaseException(['msg' => '都是好孩子', 'code' => '123'], 403);
Status: 403 Forbidden
{
"msg": "都是好孩子",
"code": "123"
}
3.6 引數傳string,code(httpStatus),array
throw new BaseException('都是好孩子', 422, ['msg' => '天是藍的', 'code' => '24678']);
Status: 422 Unprocessable Entity
{
"message": "都是好孩子",
"meta": {
"msg": "天是藍的",
"code": "24678"
}
}
3.7 引數傳string,code(自定義錯誤碼,非httpStatus),array
throw new BaseException('都是好孩子', 4567, ['msg' => '天是藍的', 'code' => '24678']);
Status: 200 OK
{
"message": "都是好孩子",
"code": 4567,
"meta": {
"msg": "天是藍的",
"code": “24678"
}
}
3.8 引數傳array,code(自定義錯誤碼,非httpStatus),array
throw new BaseException(['msg' => '都是好孩子', 'code' => '123'], 1234, ['msg' => '天是藍的', 'code' => '24678']);
Status: 200 OK
{
"msg": "都是好孩子",
"code": "123",
"meta": {
"msg": "天是藍的",
"code": "24678"
}
}
3.9 引數傳array,code(自定義錯誤碼,非httpStatus),array
throw new BaseException(['msg' => '都是好孩子', 'code' => '123'], 500, ['msg' => '天是藍的', 'code' => '24678']);
Status: 500 Internal Server Error
{
"msg": "都是好孩子",
"code": "123",
"meta": {
"msg": "天是藍的",
"code": "24678"
}
}
引數校驗異常
使用laravel內建的ValidationException
1.在app/Exceptions/Handler中新增
public function render($request, Exception $exception)
{
//-------新加這4行---------
if ($exception instanceof ValidationException) {
$message = current(current(array_values($exception->errors())));
throw new BaseException($message, 4022); // 不加4022,會返回httpStatus=422;加4022是因為返回前端統一httpStatus為200,就在422中加了0
}
//------------------------
return parent::render($request, $exception);
}
2.基礎使用
//控制器中,不需要額外丟擲異常
public function index(Request $request)
{
Validator::make($request->all(), [
'file' => 'bail|required|file'
], [
'file.required' => '請上傳檔案'
])->validate();
}
輸出:
//handler中不加4022
Status: 422 Unprocessable Entity
{
"message": "請上傳檔案"
}
//handler中加4022
Status: 200 OK
{
"message": "請上傳檔案",
"code": 4022,
}
處理其他異常
同引數校驗異常,如處理FatalThrowableError(定義錯誤碼為5678),然後故意寫個語法錯誤。
同樣不需要自己拋錯,也不會出現報錯大黑框
Status: 200 OK
{
"message": "Parse error: syntax error, unexpected 'dd' (T_STRING)",
"code": 5678
}
CodeException
為了專案統一規範,需統一管理code錯誤碼,所以建立CodeException。
- 在app/Exceptions下新建error.php。返回錯誤資訊陣列。
<?php return [ 1001 =>'門前大橋下', 1002 =>'遊過一群鴨' ];
2 . 在helpers中增加函式:
該函式是獲取該errorCode相對應的errorMessage。
function getErrorMessage($code)
{
$err = require_once __DIR__.'/../app/Exceptions/error.php';
return $err[$code];
}
3 . 在app/Exceptions下新建CodeException
4 . 基礎使用
throw new CodeException(1001);
// 返回
{
"message": "門前大橋下",
"code": 1001
}
有附加錯誤資訊陣列
throw new CodeException(1001,['info'=>'門前大橋下','text'=>'遊過一群鴨']);
//返回
{
"message": "門前大橋下",
"code": 1001,
"data": {
"info": "門前大橋下",
"text": "遊過一群鴨"
}
}
第二種 修改Handler
public function render($request, Exception $exception)
{
if ($exception instanceof BaseException
|| $exception instanceof HttpException
|| $exception instanceof ValidationException
|| $exception instanceof QueryException
|| $exception instanceof ModelNotFoundException
|| $exception instanceof ErrorException
) {
return $this->error($exception);
}
return parent::render($request, $exception);
}
/**
* 自定義錯誤輸出
*
* @param $exception
*
* @return \Illuminate\Http\JsonResponse
*/
private function error($exception)
{
$statusCode = 500;
if ($exception instanceof ValidationException) {
$code = $exception->status;
$message = current(current(array_values($exception->errors())));
} else {
$code = $exception->getCode() ?: ($exception->statusCode ?? 404);
$message = $exception->getMessage();
}
$response = [
'id' => md5(uniqid()),
'code' => $code,
'status' => $statusCode,
'message' => $message,
'error' => 'ERROR',
];
if ($exception instanceof BaseException && $exception->getData()) {
$response['data'] = $exception->getData();
}
iuLog('error', 'Response Error: ', $response);
iuLog(PHP_EOL);
return response()->json($response, $statusCode, [], 320);
}
這裡也保留了BaseException,但是寫的要簡單些,因為大部分邏輯寫在Handler裡面,這裡只留了獲取StatusCode 和 獲取陣列資訊。使用和上面一樣。
namespace App\Exceptions;
use Exception;
class BaseException extends Exception
{
protected $data;
/**
* Error constructor.
*
* @param string $message
* @param int $code
* @param array $data
*/
public function __construct($message = "", $code = 500, $data = [])
{
$this->data = $data;
parent::__construct($message, $code);
}
public function getStatusCode()
{
$objClass = new \ReflectionClass(\Symfony\Component\HttpFoundation\Response::class);
// 此處獲取類中定義的全部常量 返回的是 [key=>value,...] 的陣列
// key是常量名 value是常量值
// dd($objClass->getConstants());
$httpStatus = array_values($objClass->getConstants());
return in_array($this->code, $httpStatus) ? $this->code : 500;
}
public function getData()
{
return $this->data;
}
}