Collections offer a lot of functionality out of the box. Sometimes you just need that little bit of extra functionality that works best in your case. For those moments, custom collections are brilliant. The first step is to create the actual collection, extending the base Collection class.

namespace App\Collections;

use Illuminate\Database\Eloquent\Collection;

class PostsCollection extends Collection
{
    // Add your own custom methods here
}

The class here extends Illuminate\Database\Eloquent\Collection. This class has extra methods available for Eloquent like load, toQuery, and many more. This class itself extends the base collection class Illuminate\Support\Collection. You can decide which class you want to extend here.

You can find all available methods here:

Luckily, we can do a lot more cool things other than just creating a class with some methods. Since we can create a collection class that extends the Illuminate\Database\Eloquent\Collection with all the extra Eloquent methods, it would be really nice if we could connect this to a model as well. And of course, we can.

namespace App\Models;

use App\Collections\PostsCollection;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function newCollection(array $models = [])
    {
        return new PostsCollection($models);
    }
}

Every time you query a post, it will now return an instance of App\Collections\PostsCollection instead of Illuminate\Database\Eloquent\Collection.

You can now add your own methods to then call inside your code. This new collection will be returned for all queries and relationships where multiple posts are returned. For example Post::all(), User::posts()->get() and even User::with('posts')->get(). They all return a PostsCollection.

In the above snippet, we created a custom PostsCollection class and also made it available for the model. Let's see how a custom method inside a collection class might look like. Remember that this custom collection class has all the available methods of a normal collection class.

namespace App\Collections;

use App\Models\Post;
use Carbon\Carbon;

class PostsCollection extends Collection
{
    public function markAsPublished(Carbon $publishedAt): self
    {
        Post::query()
            ->whereKey($this->modelKeys())
            ->update(['published_at', $publishedAt]);

        $this->each(function (Post $post) use ($publishedAt) {
            $post->setAttribute('published_at', $publishedAt);
        });

        return $this;
    }
}

Post::all()->markAsPublished(Carbon::now());

In the markAsPublished method we perform a database query to update the published_at field. We also update the models inside the collection. This way, we don't have to retrieve the models again from the database to get the same data. This is a perfect use case for a custom collection class method.