Composable Eloquent Query Scopes in Laravel | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Eloquent Query Scopes as First-Class Objects: Reusable, Testable, and Composable        On this page       1. [  The Problem With Inline Query Logic ](#the-problem-with-inline-query-logic)
2. [  Anatomy of a Scope Class ](#anatomy-of-a-scope-class)
3. [  Applying Scopes Without Polluting the Model ](#applying-scopes-without-polluting-the-model)
4. [  Composing Scopes With a Pipeline ](#composing-scopes-with-a-pipeline)
5. [  Testing Scopes in Isolation ](#testing-scopes-in-isolation)
6. [  Global Scopes as Scope Classes ](#global-scopes-as-scope-classes)
7. [  Takeaways ](#takeaways)

  ![Eloquent Query Scopes as First-Class Objects: Reusable, Testable, and Composable](https://cdn.msaied.com/201/4de012f75c1e4a61467844d2babacdfb.png)

  #laravel   #eloquent   #clean-code   #testing   #php  

 Eloquent Query Scopes as First-Class Objects: Reusable, Testable, and Composable 
==================================================================================

     15 Jun 2026      1 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   The Problem With Inline Query Logic  ](#the-problem-with-inline-query-logic)
2. [  02   Anatomy of a Scope Class  ](#anatomy-of-a-scope-class)
3. [  03   Applying Scopes Without Polluting the Model  ](#applying-scopes-without-polluting-the-model)
4. [  04   Composing Scopes With a Pipeline  ](#composing-scopes-with-a-pipeline)
5. [  05   Testing Scopes in Isolation  ](#testing-scopes-in-isolation)
6. [  06   Global Scopes as Scope Classes  ](#global-scopes-as-scope-classes)
7. [  07   Takeaways  ](#takeaways)

 The Problem With Inline Query Logic
-----------------------------------

Most Laravel codebases start with local scopes on models — `scopeActive`, `scopeForTenant`, `scopePublished`. They work fine until the same logic needs to appear on three different models, or until a scope grows complex enough that you want to unit-test it in isolation. At that point, the model becomes a dumping ground.

The fix is treating query scopes as first-class objects: plain PHP classes that receive a builder, apply constraints, and can be composed, reused, and tested without booting a model.

Anatomy of a Scope Class
------------------------

Start with a simple contract:

```php
namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;

interface Scope
{
    public function apply(Builder $builder): void;
}

```

A concrete implementation stays focused:

```php
namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;

final class PublishedScope implements Scope
{
    public function __construct(
        private readonly \DateTimeInterface $before,
    ) {}

    public function apply(Builder $builder): void
    {
        $builder
            ->whereNotNull('published_at')
            ->where('published_at', '
