问题
我注意到,大多数用于API响应的库都是通过特性实现,其余的是大型库。这些特性实现了所有可能的方法(响应、接受、创建、禁止...)。
结果,如果我的控制器有1-2个方法,包括这样一个特性会让类中有很多不必要的杂乱。在几个拥有超过700颗星的大型库中,我看到在UX层面的过度设计(对我来说,作为库的用户)。
解决方案
编写自己的库!
我决定创建一种数据处理逻辑,它需要
- 用户级别的最小操作
- 使用简单
- 可读性
也就是说,要得到标准化的响应,我们只需要通过库对象返回一个响应
composer require pepperfm/api-responder-for-laravel
因此,我们在安装库后得到的基本最小功能集包括
成功响应
{
"response": {
"data": {
"entities": []|{},
"meta": []|{},
"message": "Success"
}
}
}
错误响应
{
"response": {
"data": {
"errors": null,
"message": "Error"
}
}
}
public function __construct(public ResponseContract $json)
{
}
public function index(Request $request)
{
$users = User::query()->get();
return $this->json->response($users);
}
public function store(UserService $service)
{
try {
app('db')->beginTransaction();
$service->update(request()->input());
app('db')->commit();
} catch (\Exception $e) {
app('db')->rollback();
logger()->debug($e->getMessage());
return $this->json->error(
message: $e->getMessage(),
httpStatusCode: $e->getCode()
);
}
return $this->json->response($users);
}
结果,对于成功响应,其结构已解包为:response.data.entities
。默认情况下,该格式适用于REST上下文,因此对于show()
和update()
方法,响应将采用以下格式:response.data.entity
。
深入研究
当然,对于喜欢定制和探索配置的开发者,我还创建了一个代码沙盒以进行尝试。
功能
我们的最爱糖
针对response()
方法的分页包装器
/*
* Generate response.data.meta.pagination from first argument of `paginated()` method
*/
public function index(Request $request)
{
$users = User::query()->paginate();
return $this->json->paginated($users);
}
《Paginated compression table》方法接受两个主要参数
array|\Illuminate\Pagination\LengthAwarePaginator $data,
array|\Illuminate\Pagination\LengthAwarePaginator $meta = [],
在它的逻辑中,它会解决这些问题,并将它们添加到响应的meta
键下 —— 分页键中。
响应接口根据Laravel返回的格式
export interface IPaginatedResponse<T> {
current_page: number
per_page: number
last_page: number
data: T[]
from: number
to: number
total: number
prev_page_url?: any
next_page_url: string
links: IPaginatedResponseLinks[]
}
export interface IPaginatedResponseLinks {
url?: any
label: string
active: boolean
}
因此,响应看起来像
{
"response": {
"data": {
"entities": []|{},
"meta": {
"pagination": IPaginatedResponse<T>
},
"message": "Success"
}
}
}
针对状态码的response()
方法的包装器
public function store(UserService $service)
{
// message: 'Stored', httpStatusCode: JsonResponse::HTTP_CREATED
return $this->json->stored();
}
public function destroy()
{
// message: 'Deleted', httpStatusCode: JsonResponse::HTTP_NO_CONTENT
return $this->json->deleted();
}
处理不同参数类型
response()
方法的第一个参数可以是数组或Arrayable类型,因此在这些类型中,可以在传递给方法之前映射数据。例如
public function index()
{
$users = User::query()->paginate();
$dtoCollection = $users->getCollection()->mapInto(UserDto::class);
return resolve(ResponseContract::class)->paginated(
data: $dtoCollection,
meta: $users
);
}
public function index()
{
$users = SpatieUserData::collect(User::query()->get());
return \ApiBaseResponder::response($users);
}
通过配置进行定制
该配置本身
return [
'plural_data_key' => 'entities',
'singular_data_key' => 'entity',
'using_for_rest' => true,
'methods_for_singular_key' => ['show', 'update'],
'force_json_response_header' => true,
];
禁用using_for_rest
将始终将返回的格式保留为response.data.entities
(复数),无论调用来自哪个方法。
使用methods_for_singular_key
,可以向返回单个键的方法列表中添加。实际上,force_json_response_header
以经典方式添加头:$request->headers->set('Accept', 'application/json');
通过属性进行定制
在配置中阻止using_for_rest
和methods_for_singular_key
的值,以根据singular_data_key
设置响应键
#[ResponseDataKey]
public function attributeWithoutParam(): JsonResponse
{
// response.data.entity
return BaseResponse::response($this->user);
}
同样,您可以传递自己的键名
#[ResponseDataKey('random_key')]
public function attributeWithParam(): JsonResponse
{
// response.data.random_key
return BaseResponse::response($this->user);
}
总结
主要需求得到满足:我想简单地安装库,并有一个简明的基础来标准化响应格式。无需额外的移动。
当然,还有许多定制的可能性和额外的好处,所以这个库的未来发展仍将进行下去),但主要思想一定会得到保留。
diessvints, pepperfm喜欢这篇文章