Customize paging information when using the Laravel Resource class
Tags: laravel
Published: 2021-11-27 12:34:32

Recently submitted an [idea](https://github.com/laravel/framework/pull/39600/files) to Laravel framework - add a custom paging information in `PaginatedResourceResponse` detection method, in order to use the `Resource` class output information, able to custom paging information is very convenient.

## Why do I need this?

I was basically developing APIs. In the early days, I used to return data directly, but this method sometimes had some problems and was not easy to maintain, and often needed to add custom fields and give different data for different ends, So I have been using `Resource` to define the response data.

Using `Resource` is convenient and logical. The downside is that there is too much paging information. For API projects, in most cases, many fields in the default output of paging information are not needed, and because some old projects often need to use the old data format or do compatibility, the paging information fields are very different, you cannot directly use the default return of paging information.

I don't know how you handle paging information in a situation like this, but in order to achieve this goal, I usually do two ways: one is to customize `Response`, in which the data information is redefined, and the other is to customize all the `Resource` related classes.

I didn't know much about Laravel internal, and I wasn't good at abstract framework development, but after going through this, I found that things will be a lot easier, As I describe in [PR](https://github.com/laravel/framework/pull/39600), If can be in ` Http/Resources/Json/SRC/Illuminate/PaginatedResourceResponse.php ` formed the paging information, to be able to use its corresponding ` Resource ` class paging information, There's no need to customize a lot of classes every time. So I just submit the idea to [Laravel](https://github.com/laravel/framework). This submission has not been directly in the first place accepted, but after being adjusted by Taylor and it was merged finally, and published in [v8.73.2](https://github.com/laravel/framework/releases/tag/v8.73.2).

This was the first time I had contributed to Laravel, and the first time I had submitted a merge request to such a large codebase, and while it wasn't directly adopted, the results were encouraging.

## Use Case

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

### Default Output


```json
{  
    "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": "&laquo; 上一页",
                "active": false
            },
            {
                "url": "http://cooman.cootab-v4.test/api/favicons?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": null,
                "label": "下一页 &raquo;",
                "active": false
            }
        ],
        "path": "http://cooman.cootab-v4.test/api/favicons",
        "per_page": 15,
        "to": 5,
        "total": 5
    }
}
```

This is the default page information output by Laravel, which is a lot of fields, but of course, it is enough for many scenarios. But sometimes it can be difficult. We need some flexibility.

### When using the `ResourceCollection`  

Let's look at the underlying logic first.

When the controller returns a `ResourceCollection`, its ` toResponse ` method is eventually called in response. Then we can directly find the method to look at:

```php
   /**
     * 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);
    }
```

See, if the current resource is a paginate object, it shifts the task to processing paging responses. Then look at:

```php
    /**
     * 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 the ` PaginatedResourceResponse `, this is our final class that needs to be modified. Because ` toResponse ` content is too long, not posted here, anyway, it is here that start the response data, paging information and do the processing on the inside, of course, But it has a separate method. This method is called `paginationInformation`, which is the logic before submitting the PR:

```php
/**
     * 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 can see that `$this->resource` is an instance of `ResourceCollection`, and its ` resource ` is our list data, i.e. paging information instance. In this case, why can't we handle paging information in `ResourceCollection`? 
 Sure, but we need to add something, and that's the idea I submitted.

After merging PR, its logic looks like this:

```php
/**
     * 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;
    }
```

A simple way to do this is to use your own custom method of building paging information in the corresponding resource class, which is a good idea for now.

At this moment, it should be clear how to customize the paging information. Add ` paginationInformation ` to your own ` ResourceCollection ` class, for example:

```php
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'],
        ];
    }
```

Here is the output of the custom data:

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

It turned out as I had hoped.

### When using the ` Resource `

I usually only like to define a ` Resource ` class for a single object and a list, but here I focus on how to handle paging customization of list data.

This is how I use it in controllers:

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

Let's look at what the ` collection ` method does:

```php
   /**
     * 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;
            }
        });
    }
```

Originally it transfer data to the ` ResourceCollection `, so only need to put this ` AnonymousResourceCollection ` do a custom.

## Conclusion

This is a small optimization, but useful.

Previously, returning custom paging information along with `Resource` was a bit of a hassle and required a lot of customization, which was a breeze for veteran users but could be tricky for newbies. After that, it will be a piece of cake for both old and new users. Just add the ` paginationInformation ` method to the corresponding ` ResourceCollection ` class, something like the following:

```php
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'],
        ];
    }
```

However, if you are using ` Resource::collection($pageData) `, you will need to define an additional ` ResourceCollection ` class and rewrite the ` collection ` methods of the corresponding ` Resource ` class.

I usually define a corresponding base class, and everything else inherits from it. You can also make a ` trait ` and share it.

## In the End

In fact, I wanted to submit this idea for a long time, but I have been hesitant about whether it is a popular demand. But I finally figured it out, since doing so can save a lot of repetitive and dangerous work, there are so many developers, someone will always need, so I submitted, But also to see if my idea works, whether I did it the best way. Of course, I learned a lot as a result, like writing slightly more complex test cases.

Also, I want to know if you have any other methods, or how you treat paging information in different cases.

Finally, if you have a good idea, submit it as soon as possible!