Laravel

Lesson 05

Eloquent query scopes

Put repeated Eloquent filters into query scopes and keep collection filtering out of request paths.

Good Code

app/Models/Review.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

final class Review extends Model
{
    public function scopePublished(Builder $query): Builder
    {
        // The database owns the repeated published-review filter.
        return $query->where('status', 'published')
            ->whereNotNull('published_at');
    }
}

Bad Code

app/Http/Controllers/ReviewFeedController.php
<?php

public function __invoke()
{
    // Pulling every row first makes filtering and lazy loading happen in PHP.
    return Review::all()
        ->filter(fn (Review $review) => $review->status === 'published')
        ->map(fn (Review $review) => [$review->title, $review->author->name]);
}

Review Notes

What to review

Good Code

The good version keeps a repeated filter close to the model and lets callers compose it with eager loading, ordering, or pagination.

Bad Code

The bad version fetches all rows and filters in memory. It also risks hidden N+1 queries through lazy-loaded relations.

Takeaways

  • Use database queries for filtering and eager loading so request handlers do not pull more rows than they need.