Home >PHP Framework >Laravel >Laravel implements returning custom paging information with Resource!

Laravel implements returning custom paging information with Resource!

藏色散人
藏色散人forward
2022-02-03 04:00:303242browse

Laravel implements returning custom paging information with Resource!


Recently submitted an idea to the Laravel framework - add detection of a custom paginated information method in PaginatedResourceResponse so that it can be used Resource When the class outputs information, it is very convenient to customize the paging information.

Why you need it

I basically develop APIs. In the early days, I always returned directly, but this method sometimes caused some problems and was inconvenient to maintain. In addition, it was often necessary to add custom fields and provide different data for different ends. I have been using it since thenResource to define the returned data. [Recommended: laravel video tutorial]

Using Resource is very convenient and can make the logic clear. But it has a disadvantage, that is, there is too much paginated information. For API projects, in most cases, many fields in the default output paging information are not needed, and because they are often connected to some old projects, the old data format needs to be used or made compatible. The fields of the paging information are quite different. , there is no way to directly use the paging information returned by default.

I don’t know how everyone handles paging information in similar situations, but before that, in order to achieve the goal, I usually have two methods, one is to customize Response, The data information is redefined here, and the second is to customize all the related classes of Resource.

I don’t know much about the bottom layer of Laravel, and I’m not good at abstract framework development, but after going through this, I found that things can become much simpler. As I explained in the PR, if you can When building pagination information in src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php, you can use its component pagination information corresponding to the Resource class, so you don’t need to do it every time Have you gone to a lot of trouble to customize many categories? So I submitted this idea to the Laravel framework. This commit was not accepted directly at first, but was merged after Taylor's adjustments and released in v8.73.2.

This is the first time I have contributed code to Laravel, and it is also the first time I have submitted a merge request to such a large code base. Although it has not been directly adopted, the results are exciting enough.

Usage Example

So, let me give a simple example of how to use it.

Default output

{  
    "data": [],
    "links": {
        "first": "http://cooman.cootab-v4.test/api/favicons?page=1",
        "last": "http://cooman.cootab-v4.test/api/favicons?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "« 上一页",
                "active": false
            },
            {
                "url": "http://cooman.cootab-v4.test/api/favicons?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": null,
                "label": "下一页 »",
                "active": false
            }
        ],
        "path": "http://cooman.cootab-v4.test/api/favicons",
        "per_page": 15,
        "to": 5,
        "total": 5
    }}

This is the paging information output by Laravel by default. There are many fields. Of course, this is enough to cope with many scenarios. But sometimes we get into trouble because of it. We need a little flexibility.

When using the ResourceCollection class

Let’s take a look at the underlying logic first!

When a ResourceCollection is returned from the controller, its toResponse method will eventually be called to respond. Then you can directly find this method and take a look:

   /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function toResponse($request)
    {
        if ($this->resource instanceof AbstractPaginator || $this->resource instanceof AbstractCursorPaginator) {
            return $this->preparePaginatedResponse($request);
        }

        return parent::toResponse($request);
    }

Do you see that if the current resource is a paging object, it will shift the task to processing the paging response. Next look:

    /**
     * Create a paginate-aware HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\JsonResponse
     */
    protected function preparePaginatedResponse($request)
    {
        if ($this->preserveAllQueryParameters) {
            $this->resource->appends($request->query());
        } elseif (! is_null($this->queryParameters)) {
            $this->resource->appends($this->queryParameters);
        }

        return (new PaginatedResourceResponse($this))->toResponse($request);
    }

Oh, it is transferred to PaginatedResourceResponse again. This is the class we ultimately need to modify. Since the content of toResponse is too long, it is not here. Posting, anyway, the response data is started here. Of course, the paging information is also processed here, but it has an independent method. This method is paginationInformation, which is the logic before submitting the PR:

/**
     * Add the pagination information to the response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function paginationInformation($request)
    {
        $paginated = $this->resource->resource->toArray();

        return [
            'links' => $this->paginationLinks($paginated),
            'meta' => $this->meta($paginated),
        ];
    }

If you are careful, you should be able to think of $this->resource In fact, it is an instance of the ResourceCollection above, then its resource is our list data, which is the paging information instance. That being the case, why can't we process paging information in ResourceCollection? Sure, but we needed to add something, and that's the idea I submitted.

After merging PR, its logic is as follows:

/**
     * Add the pagination information to the response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function paginationInformation($request)
    {
        $paginated = $this->resource->resource->toArray();

        $default = [
            'links' => $this->paginationLinks($paginated),
            'meta' => $this->meta($paginated),
        ];

        if (method_exists($this->resource, 'paginationInformation')) {
            return $this->resource->paginationInformation($request, $paginated, $default);
        }

        return $default;
    }

Very simple processing method. If there is a custom paging information construction method in the corresponding resource class, then use its own , for now, this is indeed a good idea.

At this point, it should be clear how to customize the paging information. That is to add the paginationInformation method to your corresponding ResourceCollection class, for example:

public function paginationInformation($request, $paginated, $default): array
    {
        return [
            'page' => $paginated['current_page'],
            'per_page' => $paginated['per_page'],
            'total' => $paginated['total'],
            'total_page' => $paginated['last_page'],
        ];
    }

这是自定义后的数据输出情况:

{
    "data": [],
    "page": 1,
    "per_page": 15,
    "total": 5,
    "total_page": 1}

结果如我所愿。

使用 Resource 类时

我通常只喜欢定义一个 Resource 类来应对单个对象和列表的情况,这里主要关注如何处理列表数据的分页自定义。

在控制器中,我一般都是这样使用:

public function Index(){
    // ....
    return  SomeResource::collection($paginatedData);}

再来看看 collection 方法里做了什么:

   /**
     * Create a new anonymous resource collection.
     *
     * @param  mixed  $resource
     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
     */
    public static function collection($resource)
    {
        return tap(new AnonymousResourceCollection($resource, static::class), function ($collection) {
            if (property_exists(static::class, 'preserveKeys')) {
                $collection->preserveKeys = (new static([]))->preserveKeys === true;
            }
        });
    }

原来它把数据转给了 ResourceCollection,那么只需要将这个  AnonymousResourceCollection 做个自定义不就可以了。

总结

这是一个很小优化,但是很有用。

在此之前,如果想要随着 Resource 返回自定义分页信息,会比较麻烦,需要自定义很多东西,这样的方式,对老用户而言小菜一碟,但是对新手就可能是件棘手的问题。那么自此之后,无论是老用户还是新手这件事将变得易如反掌。只需要在对应的 ResourceCollection 类中添加 paginationInformation 方法,类似下面这样:

public function paginationInformation($request, $paginated, $default): array
    {
        return [
            'page' => $paginated['current_page'],
            'per_page' => $paginated['per_page'],
            'total' => $paginated['total'],
            'total_page' => $paginated['last_page'],
        ];
    }

不过,如果你使用的是 Resource::collection($pageData) 方式,那么还需要额外自定义一个 ResourceCollection 类,并重写对应 Resource 类的 collection 方法。

我通常会定义一个对应的基类,然后其它的都继承它。也可以做个 trait,然后共用。

最后

其实,这个想法我很早就想提交的,但是我一直比较犹豫,这到底是不是一个很大众的需求。不过我最后想明白了,这样做既然能为我节省大量重复且危险的工作,有那么多的开发者,总会有人需要的,所以我提交了,同时也是验证下我的想法到底是否可行,我的做法是否最优,结果当然是我学到了很多,比如写稍微复杂的测试用例。

另外,我想知道大家有没其它方法,或你们是怎么对待不同情况的分页信息的。

最后的最后,你如果也有好的想法,那么尽快提交吧!                                       

The above is the detailed content of Laravel implements returning custom paging information with Resource!. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:learnku.com. If there is any infringement, please contact admin@php.cn delete