Skip to content

Instantly share code, notes, and snippets.

@simonhamp
Last active November 1, 2024 18:28
Show Gist options
  • Save simonhamp/549e8821946e2c40a617c85d2cf5af5e to your computer and use it in GitHub Desktop.
Save simonhamp/549e8821946e2c40a617c85d2cf5af5e to your computer and use it in GitHub Desktop.
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;

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

Note that this approach won't work with the collect() helper function.

More Collection goodies

Spatie have create an awesome Composer package of loads of useful Collection macros. Go check it out!

<?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'): LengthAwarePaginator {
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage)->values(),
$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'): LengthAwarePaginator
{
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage)->values(),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
}
}
@mnaviddoost
Copy link

its AMAZING 🚀🚀🚀

@kkloster7
Copy link

Gracias barrilete cosmico!!!!!!, ídolo crack genio de la vida, acabas de salvar una institución en argentina.

@Pedro-HR
Copy link

So, $total arg is useful only if you pass entire collection to Paginator (No call ->forPage), it suppose you already have slice your collection. Also, ->values() is important because when slice collection, keys are preserved, we don't want that for paginator.

Thanks !!!!

@edwardalgorist
Copy link

This is great, man!
Thank you.

@seyionifade
Copy link

Thank you for this!

For anyone seeing this, you could also add it as helper class.

@B-Carcamo
Copy link

B-Carcamo commented Nov 26, 2022

I have a problem with livewire when using links

//Error
Undefined variable $data

public function render()
    {
        $data = (new Collection($this->managers))->paginate(6);

        return view('livewire.admin.create-student',compact('data'));
    }

//livewire component

 <div class="pt-5">
            {{ $data->links() }}
 </div>

@terremoth
Copy link

terremoth commented Nov 26, 2022

I have a problem with livewire when using links

//Error Undefined variable $data

public function render()
{
    $data = (new Collection($this->managers))->paginate(6);

    return view('livewire.admin.create-student',compact('data'));
}

//livewire component

 <div class="pt-5">
     {{ $data->links() }}
 </div>

@B-Carcamo DId you try:

return view('livewire.admin.create-student', ['data' => $data]);

instead of compact('data') ?

@gilcecler
Copy link

I do not know why

image
page 2 goes from 95 to 250 m2
image
if I put more than 10 in $collection = (new Collection($data))->paginate(15);
does not show the pagination bar

@yaddly
Copy link

yaddly commented Jan 15, 2023

Good day, I tried all the above and nothing worked. I stumbled across this article on Stack Overflow and it used a method called toQuery(). This method, returns an Eloquent query builder instance containing a whereIn constraint on the collection model's primary keys. Invoke paginate(10) method from it.

$movies= Movie::get()->toQuery()->paginate(20);

@patrykszady
Copy link

patrykszady commented Jan 29, 2023

Hi, works great with Livewire. How can I use this with simplePaginate so only next and previous buttons are shown in the links() ?

Edit: looks like simplePaginate works at the mobile breakpoint. Using Livewire and Tailwind.

@yaddly
Copy link

yaddly commented Jan 31, 2023

Hi, works great with Livewire. How can I use this with simplePaginate so only next and previous buttons are shown in the links() ?

Edit: looks like simplePaginate works at the mobile breakpoint. Using Livewire and Tailwind.

@patrykszady, In order to use simplePaginate() method, kindly change the above sample code to the following:

$movies= Movie::get()->toQuery()->simplePaginate(20);

To customize your links please read the documentation here first and also look at a tutorial here.

@mrtorks
Copy link

mrtorks commented Feb 17, 2023

An update to this Anyone with multiple Paginators on a page running into an error should go with this approach. In my case i have Livewire components and trying to paginate a custom collection was a pain until I implemented the solution below.
Use the macro function with all the corrections up until this point. If possible, rename the function to avoid conflicts.
Do this to set a custom page name which would allow you reset and not affect other paginators
return view('foo.bar', ['foo' => ,(your-custom-collection)->paginateCustom(3, null,null, 'barPage')]);

@simonhamp
Copy link
Author

I've updated the code to reflect some of the comments and adjustments that others have shared in earlier comments here.

@jd-bowling
Copy link

Just what I've been looking for. This will allow me to search custom model attributes in my paginated table.

@devmount
Copy link

Wow, thank you @simonhamp! This really helped me out on collections containing different eloquent models 😍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment