Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A pageable Collection implementation for Laravel

Paginated Collections in Laravel

Use Case

Laravel provides pagination out of the box for Eloquent Collections, but you can't use that by default on ordinary Collections.

Collections do have the forPage() method, but it's more low-level, so it doesn't generate pagination links.

So you have to create a LengthAwarePaginator instance. But what if you want the behaviour to be the same as an Eloquent collection? Then use this macro!

The benefit of this is that the syntax and output is almost identical to the Eloquent Collection paginate() method and so it can (relatively) easily be swapped out for an Eloquent Collection when testing.

Installation

Feel free to copy the most relevant code into your project. You're free to use and adapt as you need.

Usage

There are 2 approaches below. Which one you use is up to you, but you don't need both. I personally prefer the macro method as I feel it's cleaner and works well with minimal effort, but it's not so good at working with your IDE (code hints etc) and can feel a little detached in some cases.

The macro way

If you prefer, add the Collection macro to a Service Provider. That way you can call paginate() on any collection:

collect([ ... ])->paginate( 20 );

See AppServiceProvider.php for a sample implementation.

The subclass way

Where you want a "pageable" collection that is distinct from the standard Illuminate\Support\Collection, implement a copy of Collection.php in your application and simply replace your use Illuminate\Support\Collection statements at the top of your dependent files with use App\Support\Collection:

// use Illuminate\Support\Collection
use App\Support\Collection;

$items = [];
$collection = (new Collection($items))->paginate(20);

Note that this method doesn't work with the collect() helper function.

<?php
namespace App\Providers;
use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
/**
* Paginate a standard Laravel Collection.
*
* @param int $perPage
* @param int $total
* @param int $page
* @param string $pageName
* @return array
*/
Collection::macro('paginate', function($perPage, $total = null, $page = null, $pageName = 'page') {
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
});
}
}
<?php
namespace App\Support;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection as BaseCollection;
class Collection extends BaseCollection
{
public function paginate($perPage, $total = null, $page = null, $pageName = 'page')
{
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
}
}
@defaye

This comment has been minimized.

Copy link

defaye commented May 31, 2017

Fantastic solution simon, thanks.

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented May 31, 2017

@defaye Thanks 👍

@vladyslavstartsev

This comment has been minimized.

Copy link

vladyslavstartsev commented Jun 2, 2017

@simonhamp you made my day. You should definitely make PR of this thing into Laravel Support. Or I will do it for you

@elkinff

This comment has been minimized.

Copy link

elkinff commented Jun 30, 2017

Thanks!

@alanTorresManci

This comment has been minimized.

Copy link

alanTorresManci commented Jul 19, 2017

Worked perfectly for me, thanks!

@DanielDanaee

This comment has been minimized.

Copy link

DanielDanaee commented Jul 28, 2017

Thanks man you saved me lots

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Aug 11, 2017

@vladyslavstartsev @elkinff @alanTorresManci @DanielDanaee glad this has helped a few people 😃

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Aug 11, 2017

@vladyslavstartsev I tried to get it added today, but it was rejected: laravel/framework#20512

@mattnewark

This comment has been minimized.

Copy link

mattnewark commented Aug 15, 2017

This is marvellous @simonhamp. Thanks

@alexlamazing

This comment has been minimized.

Copy link

alexlamazing commented Aug 16, 2017

Thanks man!
I tried the first method and it works, but I don't know how to use the Macro solution. Can anyone explain a bit? Sorry that I'm a Laravel newbie.

@Lotuashvili

This comment has been minimized.

Copy link

Lotuashvili commented Aug 21, 2017

Works perfectly! Thank you!

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Aug 30, 2017

@mattnewark @Lotuashvili great to hear :) thanks!

@alexlamazing still in need of some help?

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Aug 30, 2017

In case you're looking for even more Collection macros, I just came across this package, which included a version of the macro I present here as of a couple months ago: https://github.com/spatie/laravel-collection-macros/

@drehimself

This comment has been minimized.

Copy link

drehimself commented Sep 9, 2017

Very useful, just made use of this. Thanks!

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Sep 11, 2017

@amreddys

This comment has been minimized.

Copy link

amreddys commented Sep 14, 2017

@simonhamp You're the best

@westag

This comment has been minimized.

Copy link

westag commented Sep 18, 2017

thanks dude,
very good !

@nickybu

This comment has been minimized.

Copy link

nickybu commented Sep 20, 2017

@simonhamp Thanks for sharing this! I've ran into a slight problem that I cannot seem to figure out, and I'm hoping you (or anyone else) would be able to help me out.

I've got a collection, and I call this macro on it. All the arguments are correct, and when instantiating a variable to $this->forPage($page, $perPage);, the expected items are found, relevant to the current page. However, when passing this variable as the first parameter in Paginator (using Paginator as opposed to LengthAwarePaginator since I want simplePagination), the pagination buttons do not show up. I've narrowed this down to the fact that the Paginator instance's 'hasMore' is set to false, even though the collection does indeed have more items.

I'm attaching a screenshot below.

  • Expected items: 15

  • Items on first page: 10

  • Expected items on next page: 5

image

When changing the page query string value directly within the URL, the Paginator displays the correct items. My issue is basically the lack of pagination buttons when using a custom paginator.

The pagination code in my view is as follows: {{ $tickets->appends(Request::except('page'))->links() }}

Any help would be really appreciated!

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Nov 3, 2017

@amreddys @westag oh you guys 😊

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Nov 7, 2017

Sorry for the delay @nickybu. Did you manage to get it solved? If not, can you share your full macro code? I have a feeling that what you need isn't quite what's happening.

@kopiaman

This comment has been minimized.

Copy link

kopiaman commented Dec 16, 2017

hi simon, first page return result as expected.

{ "current_page": 1, "data": [ { "ID": 451, "post_author": 3, "post_date": "2017-11-12 11:39:22" },

but The data field return objects instead of array for page >1

{ "current_page": 2, "data": { "3": { "ID": 196, "post_author": 3, "post_date": "2017-11-03 08:45:17", "post_date_gmt": "2017-11-03 08:45:17",

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Dec 22, 2017

@kopiaman it's a bit too tricky to tell from just the output samples you've provided exactly why that would be the case. If you can put your code into another Gist, I'd be happy to take a look at it for you.

@ivanzhujunwei

This comment has been minimized.

Copy link

ivanzhujunwei commented Dec 26, 2017

Thanks for this, it's really simple and good! @simonhamp
I got another question for this, here is the result I got by using this Gist,
wx20171226-111336 2x
However, as the pagination should be consistent for all frontend pages, so what if I want to have this structure (see below picture) after pagination, just like the Pagination which Laravel has :
wx20171226-111517 2x
Not sure how we can achieve for this?
Thank you very much!

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Dec 27, 2017

Thanks @ivanzhujunwei, I'm glad it's helped some people :)

I'm going to go ahead and assume that your second example is of a model or model collection. And I'm also going to assume that you're returning the model/collection directly from your controller to be used in an AJAX call or a JSON API of some sort (i.e. the client has set the Accept: application/json header).

If my assumptions are correct, your model includes some cool logic that turns it into a more useful structure when being sent as a Response. That's what formats it in this way with the links and meta.

You should be able to achieve a similar result if you pass your Collection through a Resource which you can then use as your response.

@adnanqurais

This comment has been minimized.

Copy link

adnanqurais commented Mar 7, 2018

hi simon,

i use your code and it helped me to paginate my merged collection. But i got a problem when i combine the paginate with infinite scroll. The loading icon keep running even when the page is reaching the last page. Perhaps you or anyone here had the same problem with me so it would be nice to share with me.. hahahaha..

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Mar 7, 2018

@adnanqurais pleased that this has helped you, even though it sounds like you’re having other issues now.

Infinite scroll is usually made up of many moving parts. If you can share your code with me somehow, I’d be happy to take a look and try to help you solve your problem.

@Mandrizzy

This comment has been minimized.

Copy link

Mandrizzy commented Apr 15, 2018

This has helped me out a lot i must say a tremendous thanks for this it has saved me a lot of work much appreciated.

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Apr 23, 2018

@Mandrizzy I'm pleased :)

@benxmy

This comment has been minimized.

Copy link

benxmy commented Jun 14, 2018

Great snippet - thanks for sharing. Saved me a ton of time! Another great little win in the Laravel community. Thanks Simon.

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Jul 2, 2018

@benxmy My pleasure 👍

@lexcodeworld

This comment has been minimized.

Copy link

lexcodeworld commented Jul 12, 2018

Excellent!

@zakigatez

This comment has been minimized.

Copy link

zakigatez commented Jul 17, 2018

@simonhamp
i wanna just ask what should we add in the .blade part i cant find the links and {{ $var->links()}} does not work ??

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Aug 9, 2018

@lexcodeworld 🙌

@zakigatez it should work exactly the same as any other paginator. Are you definitely addressing the correct variable?

@ice-waves

This comment has been minimized.

Copy link

ice-waves commented Sep 4, 2018

Wow , it's so useful !
thx a lot

@t-k-c

This comment has been minimized.

Copy link

t-k-c commented Oct 4, 2018

Wow Simon

@bradley-varol

This comment has been minimized.

Copy link

bradley-varol commented Oct 7, 2018

Thanks for this! I wanted simplePaginate and found that here, courtesy of Spatie.

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Oct 16, 2018

@WingF

This comment has been minimized.

Copy link

WingF commented Nov 12, 2018

wonderful. thanks so much.

@hrzrahimi

This comment has been minimized.

Copy link

hrzrahimi commented Dec 24, 2018

Thank you Simon.
That's wonderful.

@usamamuneerchaudhary

This comment has been minimized.

Copy link

usamamuneerchaudhary commented Dec 26, 2018

Much thanks for this. Helped alot. 🍡

@venux92

This comment has been minimized.

Copy link

venux92 commented Jan 10, 2019

Great job, works perfectly!

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Jan 12, 2019

@hrzrahimi @usamamuneerchaudhary @venux92 you’re all most welcome!

@addien10haniefardy

This comment has been minimized.

Copy link

addien10haniefardy commented Feb 5, 2019

What a great solution ...
Thanks @simonhamp

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Feb 25, 2019

No problem @addien10haniefardy

@MouadZIANI

This comment has been minimized.

Copy link

MouadZIANI commented Feb 26, 2019

Awesome solution
Thaanks @simonhamp

@ghostd93

This comment has been minimized.

Copy link

ghostd93 commented Feb 27, 2019

hi simon, first page return result as expected.

{ "current_page": 1, "data": [ { "ID": 451, "post_author": 3, "post_date": "2017-11-12 11:39:22" },

but The data field return objects instead of array for page >1

{ "current_page": 2, "data": { "3": { "ID": 196, "post_author": 3, "post_date": "2017-11-03 08:45:17", "post_date_gmt": "2017-11-03 08:45:17",

Change
$this->forPage($page, $perPage)
to
$this->forPage($page, $perPage)->values()

@shashankapporio

This comment has been minimized.

Copy link

shashankapporio commented Mar 1, 2019

@ghostd93
Thanks alot, your solution worked perfectly for issue

{ "current_page": 2, "data": { "3": { "ID": 196, "post_author": 3, "post_date": "2017-11-03 08:45:17", "post_date_gmt": "2017-11-03 08:45:17" } } }

@vickymessii

This comment has been minimized.

Copy link

vickymessii commented Mar 12, 2019

Thanks alot, your solution worked perfectly

@binsoktheara

This comment has been minimized.

Copy link

binsoktheara commented Apr 2, 2019

i have problem like that too
{ "current_page": 1, "data": [ { "ID": 451, "post_author": 3, "post_date": "2017-11-12 11:39:22" },

but The data field return objects instead of array for page >1

{ "current_page": 2, "data": { "3": { "ID": 196, "post_author": 3, "post_date": "2017-11-03 08:45:17", "post_date_gmt": "2017-11-03 08:45:17"}

can give me another solution for me.

@othmanemessaoud

This comment has been minimized.

Copy link

othmanemessaoud commented Apr 11, 2019

hello simon, the pagination works at least it doesnt shows "pagination doesnt exist" , but the problem is they dont show the navigation bar for the pagination i mean the pages buttons ( i wish you answer me quickly)

@natanrch

This comment has been minimized.

Copy link

natanrch commented Apr 22, 2019

It worked exactly as I expected, thank you!

@Jaspur

This comment has been minimized.

Copy link

Jaspur commented Apr 27, 2019

Why isn’t this in the Laravel core?

@eleazarbr

This comment has been minimized.

Copy link

eleazarbr commented May 5, 2019

Same question, why isn't this in the Laravel core? This is so useful. Now I can sort a collection using a custom attribute, ie. sortBy->('customAttribute'), instead of a database column and then paginate :thumbs-up:

@williamjulianvicary

This comment has been minimized.

Copy link

williamjulianvicary commented May 11, 2019

This works perfectly - thank you!

@andcl

This comment has been minimized.

Copy link

andcl commented May 18, 2019

hi simon, first page return result as expected.
{ "current_page": 1, "data": [ { "ID": 451, "post_author": 3, "post_date": "2017-11-12 11:39:22" },
but The data field return objects instead of array for page >1
{ "current_page": 2, "data": { "3": { "ID": 196, "post_author": 3, "post_date": "2017-11-03 08:45:17", "post_date_gmt": "2017-11-03 08:45:17",

Change
$this->forPage($page, $perPage)
to
$this->forPage($page, $perPage)->values()

Thanks!

@ManojKiranA

This comment has been minimized.

Copy link

ManojKiranA commented May 31, 2019

Thanks man!
I tried the first method and it works, but I don't know how to use the Macro solution. Can anyone explain a bit? Sorry that I'm a Laravel newbie.

https://tighten.co/blog/the-magic-of-laravel-macros

@Faran123

This comment has been minimized.

Copy link

Faran123 commented Jun 18, 2019

Awesome!

Thanks :)

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Jun 25, 2019

@Jaspur @elazarbr It was proposed but it was rejected because it would create a cross-dependency between two Illuminate libraries that are best left separate for now.

I actually think it's fine to add this manually or via a separate package, it's not too painful for it to not be part of the core Laravel experience. Would still be nice tho :)

@simonhamp

This comment has been minimized.

Copy link
Owner Author

simonhamp commented Jun 25, 2019

@natanrch @williamjulianvicary @Faran123 glad to see this is still useful

@vahiiiid

This comment has been minimized.

Copy link

vahiiiid commented Jun 26, 2019

Thanks man!
Tahnks
Tahnks
Tahnks

@vahiiiid

This comment has been minimized.

Copy link

vahiiiid commented Jun 27, 2019

->values()

it helps:) thanks

@twizzlerz04

This comment has been minimized.

Copy link

twizzlerz04 commented Jul 1, 2019

How can I use the pagination on javascript?

@GuilhermeGehring

This comment has been minimized.

Copy link

GuilhermeGehring commented Aug 5, 2019

oi simon, resultado de retorno da primeira página como esperado.
{ "current_page": 1, "data": [ { "ID": 451, "post_author": 3, "post_date": "2017-11-12 11:39:22" },
mas o campo de dados retorna objetos em vez de array para a página> 1
{ "current_page": 2, "data": { "3": { "ID": 196, "post_author": 3, "post_date": "2017-11-03 08:45:17", "post_date_gmt": "2017-11-03 08:45:17",

Mudar
$this->forPage($page, $perPage)
para
$this->forPage($page, $perPage)->values()

Thanks!

@Babaktrad

This comment has been minimized.

Copy link

Babaktrad commented Aug 11, 2019

Great! This saved me.

@jghasan

This comment has been minimized.

Copy link

jghasan commented Aug 15, 2019

I have been agonizing over this off and on for a few months - - the ONLY solution I've found. Extreme kudos

@jghasan

This comment has been minimized.

Copy link

jghasan commented Aug 16, 2019

Hey FYI- it turns out I ran into trouble with the output format changing on pages AFTER page #1. Found a perfect solution applying 'data' => $lap ->values() as follows:

   $lap = new LengthAwarePaginator(. . . . 

    return [
            'total' => $lap->total(),
            'per_page' => $lap->perPage(),
            'current_page' => $lap->currentPage(),
            'last_page' => $lap->lastPage(),
            'next_page_url' => $lap->nextPageUrl(),
            'prev_page_url' => $lap->previousPageUrl(),
            'first_page_url' => $lap ->url(1),
            'last_page_url' => $lap->url($lap->lastPage()),
            'from' => $lap->firstItem(),
            'to' => $lap->lastItem(),
            'data' => $lap ->values(),
    ];

Courtesy of:
https://gist.github.com/wuiler/9fd3ca8fa5d58265b49ecfc45dd1e095

@maha269

This comment has been minimized.

Copy link

maha269 commented Sep 8, 2019

thanks a lot , i just used it

@diegobcorrea

This comment has been minimized.

Copy link

diegobcorrea commented Sep 27, 2019

Thanks bro!! I used it now, works like a charm!

remember add this line
$this->forPage($page, $perPage)->values()

@fingersandmind

This comment has been minimized.

Copy link

fingersandmind commented Nov 28, 2019

Save my ass a couple more hours of sitting tryna figure this out.

But found out that If I do paginate(1), first array is okay but in next page it will have an index of 1 and will ruin the loops. But if paginated 2 - up, everythings fine.

@LeviXIII

This comment has been minimized.

Copy link

LeviXIII commented Nov 29, 2019

Thanks Simon! Worked as a solution to my problem!

@AnastasiaDolgopolova

This comment has been minimized.

Copy link

AnastasiaDolgopolova commented Jan 3, 2020

Thanks, Simon, your decision really helped me!

@maegnes

This comment has been minimized.

Copy link

maegnes commented Jan 13, 2020

Simon. You are the man who made my day. THANKS!!! :)

@1isten

This comment has been minimized.

Copy link

1isten commented Jan 14, 2020

Thank you!

$this->forPage($page, $perPage) + ->values() works like a charm 🎉

@Ayguid

This comment has been minimized.

Copy link

Ayguid commented Feb 26, 2020

GREEEAT! So many thanks!!!

@minhhieu08

This comment has been minimized.

Copy link

minhhieu08 commented Mar 11, 2020

Thanks, You made my day!!

@laertehz

This comment has been minimized.

Copy link

laertehz commented Apr 2, 2020

Great! Thank you!

@DeveloperRijan

This comment has been minimized.

Copy link

DeveloperRijan commented Apr 6, 2020

Great one... working fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.