Eloquent Custom Collections
Eloquent Custom Collections: Tips & Tricks
Eloquent Custom Collections: Tips & Tricks
Tip: Override newCollection()
class Post extends Model
{
public function newCollection(array $models = []): PostCollection
{
return new PostCollection($models);
}
}
Now Post::all() returns a PostCollection instead of a standard Collection.
Gotcha: Custom Collections Must Extend the Right Class
use Illuminate\Database\Eloquent\Collection;
class PostCollection extends Collection
{
// Not the base Collection class
}
Tip: Add Domain-Specific Methods
class PostCollection extends Collection
{
public function published(): static
{
return $this->filter(fn($p) => $p->published);
}
public function totalViews(): int
{
return $this->sum('views');
}
}
Gotcha: Pagination Returns Standard Collections
Paginated results use LengthAwarePaginator, not your custom collection.
Tip: Collection Macros for Global Methods
Collection::macro('toUpper', function () {
return $this->map(fn($v) => strtoupper($v));
});
Available on ALL collections, not just Eloquent.
Gotcha: map() Returns Base Collection
Even with a custom collection, map() returns a standard Collection. Override map() if needed.
Tip: High-Order Messages
$posts->each->publish(); // Calls publish() on each
$posts->pluck->title; // Gets title from each
$users->sum->age; // Sums age from each
Tip: Pipeline Pattern with Collections
$result = collect($data)
->filter(fn($i) => $i > 0)
->map(fn($i) => $i * 2)
->reduce(fn($carry, $i) => $carry + $i, 0);
Tip: Use cursor() for Memory-Neutral Iteration
When exporting 100K rows, get() loads everything into memory. cursor() uses yield and keeps memory flat regardless of row count. Perfect for artisan commands.
Tip: whereHas() vs load() — Two Different Things
whereHas() filters the parent query by relationship existence. load() eager-loads relationships AFTER the query. Mixing them up is a common source of logic bugs.
Gotcha: withCount() Adds a Subquery
withCount('comments') runs a correlated subquery on every row. On large tables, this can be slower than a separate query. Profile before relying on it.
Senior Insight
Custom collection methods via Macroable are a great way to add domain-specific logic to collections. I've built collections with ->toCsv(), ->toTree(), and ->partitionByStatus() methods that eliminated hundreds of lines of procedural code. The key: keep custom methods stateless and predictable. A collection method should return a new collection, not modify internal state. This preserves the chainability that makes collections powerful in the first place.
Source: Laravel Docs (https://laravel.com/docs/eloquent), Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/eloquent)