$ lexprog.com

// notes from an old coder -- php, databases, and the occasional rant

[June 23, 2025] Eloquent ORM

Eloquent Dynamic Scopes: Reusable Queries

Eloquent Dynamic Scopes: Reusable Queries

────────────────────────────────────────────────────────

Eloquent Dynamic Scopes: Reusable Queries

Tip: Scopes with Parameters

public function scopeOfType(Builder $query, string $type): Builder
{
    return $query->where('type', $type);
}

Post::ofType('article')->get();

Gotcha: First Parameter is Always Builder

public function scopeActive(Builder $query, $status = true): Builder
{
    return $query->where('active', $status);
}

The builder is injected automatically.

Tip: Chain Multiple Scopes

Post::published()->featured()->recent()->get();

Each scope returns the builder, enabling fluent chaining.

Gotcha: Scopes in Relationships

public function publishedPosts(): HasMany
{
    return $this->hasMany(Post::class)->published();
}

Scopes work on relationship queries too.

Tip: Global Scopes for Tenancy

static::addGlobalScope('tenant', function (Builder $builder) {
    $builder->where('tenant_id', auth()->user()->tenant_id);
});

Every query automatically filters by tenant.

Gotcha: Removing Global Scopes

Post::withoutGlobalScope('tenant')->get();
Post::withoutGlobalScopes()->get(); // Remove all

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

Dynamic scopes are a clean way to parameterize query filters, but I've seen them abused as a substitute for proper query builder usage. A scope that accepts five optional parameters with complex default logic is a maintenance burden. If your scope needs more than two parameters, it's probably a method on a repository or a query builder macro. Scopes should make queries more readable, not less.

Source: Laravel Docs (https://laravel.com/docs/eloquent), Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/eloquent)

────────────────────────────────────────────────────────
<-- back to posts