支持 Laravel.io 持续开发 →

不使用特性标准化 API 响应

19 Jul, 2024 4 min read

问题

我注意到,大多数用于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_restmethods_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);
}

总结

主要需求得到满足:我想简单地安装库,并有一个简明的基础来标准化响应格式。无需额外的移动。

当然,还有许多定制的可能性和额外的好处,所以这个库的未来发展仍将进行下去),但主要思想一定会得到保留。

最后更新1周前。

diessvints, pepperfm喜欢这篇文章

2
喜欢这篇文章吗?让作者知道,并给他们点赞!

你可能还喜欢这些文章

2024年3月11日

如何使用Larastan从0到9打造你的Laravel应用

感谢Larastan,可以在应用执行之前就发现Laravel应用中的错误,它...

阅读文章
2024年7月17日

在Laravel项目中通过Discord通知收集反馈

如何在Laravel项目中创建反馈模块,并在收到消息时收到Discord通知...

阅读文章
2024年7月12日

Laravel高级:你可能不知道存在的10个顶级验证规则

您知道所有可用的Laravel验证规则吗?再想想!Laravel有许多现成可用的...

阅读文章

感谢这些 令人难以置信的公司 支持我们

您的标志在这里?

Laravel.io

Laravel解决问题的门户、知识共享和社区建设。

© 2024 Laravel.io - 版权所有。