orest's avatar
Level 13

append attribute to pagination

I'm trying to append an attribute to paginated data during runtime.

More specifically, i have a Reply model and i want to append the following attribute

 public function getIsLikedAttribute()
    {
        return $this->likes()->exists();
    }

In the controller i have the following line

$replies = $thread->replies()->paginate(Reply::PER_PAGE);

Then the $replies data are passed in a VUE component.

To each reply, i want to append the is_liked attribute, without using

$appends = ['is_liked']

Because in other views i eager load the Reply model and i don't need the is_liked attribute.

0 likes
12 replies
orest's avatar
Level 13

@michaloravec

I did check that but in my case i don't have a collection of data but paginated data, therefore i can't do


$thread->replies()->paginate(Reply::PER_PAGE)->append('is_liked');

or anything like that

MichalOravec's avatar
Level 75

@orest Try it like this

$replies = $thread->replies()->paginate(Reply::PER_PAGE);

$replies->each(function ($reply) {
    $reply->append('is_liked');
});
1 like
orest's avatar
Level 13

@michaloravec

I currently have

$replies = $thread->replies()
            ->with('likes')
	    ->withCount('likes')
            ->paginate(Reply::PER_PAGE);

        $replies->each(function ($reply) {
            $reply->append('is_liked');
        });

Is it overkill if I create a separate class that will hide all these lines and will just return the replies like

$replies = (new ReplyWithLikes)->forThread($thread)

Or can I create a static method in the Reply model that will do the same thing ( I see that people tend to avoid static methods though )

1 like
Čamo's avatar

This is sad. Laravel can not append attributes for paginated collections in 2024.

1 like
iinmass's avatar

@Snapey this is actually appending to the request query, not to the collection itself

iinmass's avatar

all i could do as a solution (which i dont like very much) is this in the blade inside the foreach :)) : @php $job->attribute = 'attribute'; @endphp

davidnknight's avatar

Using Laravel 10+, it is possible to transform items in a paginated response by using ->through($callback).

For example, you can append a model attribute to each paginated item:

class ThreadRepliesController
{
	public function index(Thread $thread) {
		$replies = $thread->replies()
			->paginate()
			->through(function($reply) {
				$reply->append('is_liked');
			});

		return response()->json($replies);
	}
}

This can be written a little cleaner using arrow functions if you're using PHP 7.4+:

class ThreadRepliesController
{
	public function index(Thread $thread) {
		$replies = $thread->replies()
			->paginate()
			->through(fn ($reply) => $reply->append('is_liked'));

		return response()->json($replies);
	}
}

Using the ->through($callback), you can do anything you want to the items within the paginator, such as adding additional attributes to each item:

class ThreadRepliesController
{
	public function index(Thread $thread) {
		$replies = $thread->replies()
			->paginate()
			->through(fn ($reply) => [
				...$reply->toArray(),
				'custom_attribute' => '...'
			]);

		return response()->json($replies);
	}
}

Or even only returning specific attributes:

class ThreadRepliesController
{
	public function index(Thread $thread) {
		$replies = $thread->replies()
			->paginate()
			->through(fn ($reply) => $reply->only(['id', 'name']));

		return response()->json($replies);
	}
}

These were just a couple of examples just to illustrate that using the ->through($callback) you have full access to the model/item and can transform it, so it's very flexible.

You can learn more about $paginator->through($callback) and other available methods on paginators in the Laravel Docs - Paginator / LengthAwarePaginator Instance Methods

1 like
cristian_dkb's avatar

For future reference. I use this:

$replies = $thread->replies()->paginate(Reply::PER_PAGE);
$replies->getCollection()->append('is_liked');

Please or to participate in this conversation.