CamKem's avatar
Level 10

Changing pagination data struction

How can I change the way paginate works? I want to do what Laracasts does & wrap all the meta items in a meta object, like this:

    Conversations
        |----data
        |----links
        |----meta (first page, last page, current page, etc..)

Rather than the standard way the pagination data is returned which is like this:

		Conversations
				|
				|---- Data (array)
				|---- Links (array)
				|---- current_page: 1
				|---- last_page: 9
				|---- etc....

My query looks like this:

        $threads = Thread::query()
            ->select('*')
            ->with('creator:id,username,email')
            ->with(['replies' => function ($query) {
                $query->select('replies.id', 'replies.thread_id', 'replies.body', 'replies.created_at', 'replies.updated_at', 'replies.user_id')
                    ->with('owner:id,username,email')
                    ->leftJoin('users', 'users.id', '=', 'replies.user_id');
            }])
            ->latest()
            ->paginate(20)
            ->through(function ($thread) {
                $firstReply = $thread->replies->first();
                return [
                    'title' => $thread->title,
                    'body' => $thread->body,
                    'slug' => $thread->slug,
                    'views' => $thread->views,
                    'solved' => $thread->solved,
                    'created_at' => $thread->created_at->diffForHumans(null),
                    'replies' => $thread->replies->count() ? [
                        'count' => $thread->replies->count(),
                        'first' => [
                            'body' => $firstReply->body,
                            'created_at' => $firstReply->created_at->diffForHumans(null),
                            'updated_at' => $firstReply->updated_at->diffForHumans(null),
                            'owner' => [
                                'username' => $firstReply->owner->username,
                                'email' => $firstReply->owner->email,
                            ]
                        ],
                    ] : null,
                    'creator' => [
                        'username' => $thread->creator->username,
                        'email' => $thread->creator->email,
                    ],
                ];
            });
0 likes
2 replies
LaryAI's avatar
Level 58

To change the way pagination works, you can create a custom paginator class that extends the default Laravel paginator. In this custom class, you can override the toArray method to modify the structure of the pagination data.

Here's an example of how you can create a custom paginator class:

use Illuminate\Pagination\LengthAwarePaginator;

class CustomPaginator extends LengthAwarePaginator
{
    public function toArray()
    {
        $data = parent::toArray();

        return [
            'data' => $data['data'],
            'links' => $data['links'],
            'meta' => [
                'current_page' => $data['current_page'],
                'last_page' => $data['last_page'],
                'per_page' => $data['per_page'],
                'total' => $data['total'],
            ],
        ];
    }
}

In this example, the toArray method returns an array with a data key that contains the paginated data, a links key that contains the pagination links, and a meta key that contains the pagination metadata.

To use this custom paginator class, you can pass it as the second argument to the paginate method:

$threads = Thread::query()
    ->select('*')
    ->with('creator:id,username,email')
    ->with(['replies' => function ($query) {
        $query->select('replies.id', 'replies.thread_id', 'replies.body', 'replies.created_at', 'replies.updated_at', 'replies.user_id')
            ->with('owner:id,username,email')
            ->leftJoin('users', 'users.id', '=', 'replies.user_id');
    }])
    ->latest()
    ->paginate(20, ['*'], 'page', 1, [
        'paginator' => new CustomPaginator,
    ])
    ->through(function ($thread) {
        $firstReply = $thread->replies->first();
        return [
            'title' => $thread->title,
            'body' => $thread->body,
            'slug' => $thread->slug,
            'views' => $thread->views,
            'solved' => $thread->solved,
            'created_at' => $thread->created_at->diffForHumans(null),
            'replies' => $thread->replies->count() ? [
                'count' => $thread->replies->count(),
                'first' => [
                    'body' => $firstReply->body,
                    'created_at' => $firstReply->created_at->diffForHumans(null),
                    'updated_at' => $firstReply->updated_at->diffForHumans(null),
                    'owner' => [
                        'username' => $firstReply->owner->username,
                        'email' => $firstReply->owner->email,
                    ]
                ],
            ] : null,
            'creator' => [
                'username' => $thread->creator->username,
                'email' => $thread->creator->email,
            ],
        ];
    });

In this example, the paginate method is called with an additional fifth argument that contains an array with a paginator key that specifies the custom paginator class to use.

With this custom paginator class, the pagination data will be returned in the desired structure with a data key that contains the paginated data, a links key that contains the pagination links, and a meta key that contains the pagination metadata.

CamKem's avatar
CamKem
OP
Best Answer
Level 10

I figured it out so I don't have to overwrite the class in the paginate() function.

  1. create a class that extends LengthAwarePaginator, example:
<?php

namespace App\Pagination;

use Illuminate\Pagination\LengthAwarePaginator;

class CustomPaginator extends LengthAwarePaginator
{

    public function toArray(): array
    {
        return [
            'data' => $this->items->toArray(),
            'meta' => [
                'current_page' => $this->currentPage(),
                'first_page_url' => $this->url(1),
                'from' => $this->firstItem(),
                'last_page' => $this->lastPage(),
                'last_page_url' => $this->url($this->lastPage()),
                'next_page_url' => $this->nextPageUrl(),
                'path' => $this->path(),
                'per_page' => $this->perPage(),
                'prev_page_url' => $this->previousPageUrl(),
                'to' => $this->lastItem(),
                'total' => $this->total(),
            ],
            'links' => $this->linkCollection(),
        ];
    }

}
  1. Swap out the paginator instances on in the AppServiceProvider class boot() method.
        $this->app->bind(
            \Illuminate\Pagination\LengthAwarePaginator::class,
            \App\Pagination\CustomPaginator::class
        );

Please or to participate in this conversation.