使用 Dingo API 快速构建 RESTful API(七)—— 错误及异常处理
错误响应
在 Dingo API 中,你可以使用响应构建器支持的多种错误方法生成错误响应:
// 通用的错误响应,包含错误消息和状态码
return $this->response->error('404 Not Found', 404);
// 返回 404 Not Found 响应,等同于上述返回 404 状态码的错误响应
return $this->response->errorNotFound();
// 返回 401 Bad Request 响应,等同于调用 error('Bad Request', 401)
return $this->response->errorBadRequest();
// 返回 403 Forbidden 响应,等同于调用 error('Forbidden', 403)
return $this->response->errorForbidden();
// 返回 500 Internal Error 响应,等同于调用 error('Internal Error', 500)
return $this->response->errorInternal();
// 返回 401 Unauthorized 响应,等同于调用 error('Unauthorized', 401)
return $this->response->errorUnauthorized();
// 返回 405 Method Not Allowed 响应,比如在 GET 路由上发起 POST 请求就会报这个错
return $this->response->errorMethodNotAllowed();
异常处理
异常响应
在构建 API 的时候处理异常是一件痛苦的事,在 Dingo API 中,你不需要手动构建异常响应,只需要抛出一个继承自 Symfony\Component\HttpKernel\Exception\HttpException
的异常,Dingo API 就会自动为你处理这个响应。
下面是 Dingo API 内置的 Symfony 异常:
异常 | 状态码 |
---|---|
Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException | 403 |
Symfony\Component\HttpKernel\Exception\BadRequestHttpException | 400 |
Symfony\Component\HttpKernel\Exception\ConflictHttpException | 409 |
Symfony\Component\HttpKernel\Exception\GoneHttpException | 410 |
Symfony\Component\HttpKernel\Exception\HttpException | 500 |
Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException | 411 |
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException | 405 |
Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException | 406 |
Symfony\Component\HttpKernel\Exception\NotFoundHttpException | 404 |
Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException | 412 |
Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException | 428 |
Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException | 503 |
Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException | 429 |
Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException | 401 |
Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException | 415 |
下面是一个示例,当我们尝试更新一条已经被别人更新过的记录时抛出一个 ConflictHttpException
异常:
$api->version('v1', function ($api) {
$api->put('task/{id}', function ($id) {
$task = \App\Task::find($id);
if ($task->updated_at > app('request')->get('last_updated')) {
throw new \Symfony\Component\HttpKernel\Exception\ConflictHttpException('Task was updated prior to your request.');
}
// No error, we can continue to update the user as per usual.
});
});
我们在 Postman 中模拟发起这个 PUT 请求,并设置 last_updated
字段为小于指定任务最后更新时间的值,Dingo API 就会返回一个错误响应:
Dingo API 会自动捕获抛出的异常并将其转化为 JSON 格式,响应的 HTTP 状态码也会相应更改以匹配这个异常,ConflictHttpException
对应的 HTTP 状态码是 409。
资源异常
以下是资源异常,每个异常都会返回 422 状态码:
Dingo\Api\Exception\DeleteResourceFailedException
Dingo\Api\Exception\ResourceException
Dingo\Api\Exception\StoreResourceFailedException
Dingo\Api\Exception\UpdateResourceFailedException
这些异常的特殊之处在于你可以将创建、更新或者删除资源时遇到的验证错误传递到这些异常中。
下面我们就来看一个创建新用户验证失败抛出 StoreResourceFailedException
异常的例子:
$api->version('v1', function ($api) {
$api->post('tasks', function () {
$rules = [
'text' => ['required', 'string'],
'is_completed' => ['required', 'boolean']
];
$payload = app('request')->only('text', 'is_completed');
$validator = app('validator')->make($payload, $rules);
if ($validator->fails()) {
throw new \Dingo\Api\Exception\StoreResourceFailedException('Could not create new task.', $validator->errors());
}
// Create user as per usual.
});
});
Dingo API 会自动捕获抛出的异常并将其转化为 JSON 格式,响应的 HTTP 状态码也会更改为与异常相匹配的值,资源异常会返回 422 状态码以及如下 JSON 格式错误信息:
自定义 HTTP 异常
除了 Dingo 内置支持的异常类之外,你可以创建自定义的 HTTP 异常,前提是它们继承自Symfony\Component\HttpKernel\Exception\HttpException
基类或者实现了Symfony\Component\HttpKernel\Exception\HttpExceptionInterface
接口。
自定义异常响应
如果你需要自定义异常返回的响应可以注册一个异常处理器(你可以在某个服务提供者如 AppServiceProvider
的 boot
方法中添加这段代码):
app(\Dingo\Api\Exception\Handler::class)->register(function (\Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException $exception) {
return \Illuminate\Support\Facades\Response::make(['error' => 'Hey, what do you think you are doing!?'], 401);
});
现在如果认证失败(抛出 UnauthorizedHttpException
异常)就会返回如下 JSON 格式信息:
{
"error": "Hey, what do you think you are doing!?"
}
表单请求类
如果你要使用表单请求类,那么需要继承 Dingo API 表单请求基类 Dingo\Api\Http\FormRequest
或者完全自己实现。表单请求类会检查输入请求是否是针对当前 API 的,如果是的话,当验证失败会抛出 Dingo\Api\Exception\ValidationHttpException
异常。这个异常会被 Dingo API 渲染并返回相应的错误响应。
下面我们在 app/Http/Requests
目录下创建一个继承自 Dingo API 请求基类的表单请求类 CreateTaskRequest
,并编写其实现代码如下:
<?php
namespace App\Http\Requests;
use Dingo\Api\Http\FormRequest;
class CreateTaskRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'text' => 'required|string',
'is_completed' => 'required|boolean'
];
}
}
这样一来,我们就可以在控制器方法中声明这个表单请求类来进行表单字段验证了,编写 Api\TaskController
的 store
方法如下:
public function store(CreateTaskRequest $request)
{
// 表单验证成功,继续后续处理
return $this->response->errorUnauthorized();
}
在 Postman 中访问对应的路由,如果验证失败,返回错误响应如下:
如果你想要完全实现自己的表单请求类,则必须重写 failedValidation
和 failedAuthorization
方法,这些方法必须抛出上述介绍的其中一种异常而不是 Laravel 框架抛出的 HTTP 异常,感兴趣的同学可以自己去实现下,这里就不再介绍了。
1 Comment
自定义错误响应存在疑问。 对于已知要抛出的异常,例如$this->response->errorUnauthorized();可以得到响应。 如果是其他未能定义的异常,被Dingo捕获,会抛出固定的响应格式。 举例例如{ "message": "Class 'Modules\System\Http\Controllers\Usera' not found", "status_code": 500 }这样的。 我想自定义错误message和status_code,直接抛异常,但是laravel框架的异常机制被Dingo Api接管了。。。 有解决思路吗