> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Scopes in Eloquent

> Uitleg over de werking van lokale en globale scopes. We lezen mee met interne framework-implementaties zoals SoftDeletingScope en behandelen praktische use cases zoals multitenancy en openbaar/privé-filters.

## Wat zijn scopes?

Scopes in Eloquent zijn een mechanisme om querybeperkingen te bundelen en te hergebruiken. Er zijn twee soorten scopes.

| Soort              | Toepassingsmoment             | Toepassing                                                       |
| ------------------ | ----------------------------- | ---------------------------------------------------------------- |
| **Globale scopes** | Altijd automatisch toegepast  | Soft deletes, multitenancy, publicatiefilters                    |
| **Lokale scopes**  | Alleen bij expliciete aanroep | Gedeelde filters zoals "populaire posts" of "actieve gebruikers" |

## Lokale scopes

### Definitie

Een lokale scope definieer je door het `#[Scope]`-attribute op een modelmethode te zetten.

```php theme={null}
<?php

namespace App\Models;

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

class Post extends Model
{
    /**
     * Beperken tot gepubliceerde posts
     */
    #[Scope]
    protected function published(Builder $query): void
    {
        $query->where('status', 'published');
    }

    /**
     * Beperken tot populaire posts
     */
    #[Scope]
    protected function popular(Builder $query): void
    {
        $query->where('views', '>', 1000);
    }
}
```

<Info>
  Het `#[Scope]`-attribute staat in de namespace `Illuminate\Database\Eloquent\Attributes`. Het is native syntaxis van PHP 8.0 en later.
</Info>

### Gebruik

Gedefinieerde scopes roep je aan als methodes. Chainen kan ook.

```php theme={null}
use App\Models\Post;

// Alleen gepubliceerde posts ophalen
$posts = Post::published()->get();

// Gepubliceerde én populaire posts ophalen, nieuwste eerst
$posts = Post::published()->popular()->latest()->get();
```

### Parameters doorgeven

Vanaf het tweede argument van een scopemethode kun je extra parameters definiëren.

```php theme={null}
#[Scope]
protected function ofStatus(Builder $query, string $status): void
{
    $query->where('status', $status);
}
```

Bij het aanroepen geef je de argumenten direct door.

```php theme={null}
$posts = Post::ofStatus('draft')->get();
$posts = Post::ofStatus('published')->get();
```

### Combineren met `orWhere`

Wanneer je scopes met `orWhere` aan elkaar knoopt, is soms een logische groepering nodig.

```php theme={null}
// Met een closure (betrouwbaar maar omslachtig)
$users = User::popular()->orWhere(function (Builder $query) {
    $query->active();
})->get();

// Met de higher order methode wordt het simpeler
$users = User::popular()->orWhere->active()->get();
```

## Globale scopes

### Werking

Een globale scope is een klasse die de interface `Illuminate\Database\Eloquent\Scope` implementeert. Deze interface vereist slechts één methode: `apply`.

```php theme={null}
// De interfacedefinitie in het framework zelf
// src/Illuminate/Database/Eloquent/Scope.php

interface Scope
{
    public function apply(Builder $builder, Model $model);
}
```

In de `apply`-methode voeg je beperkingen toe aan de query builder.

### Een globale scopeklasse maken

Genereer een sjabloon met het commando `make:scope`.

```bash theme={null}
php artisan make:scope ActiveScope
```

Er wordt een `app/Models/Scopes/ActiveScope.php` gegenereerd.

```php theme={null}
<?php

namespace App\Models\Scopes;

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

class ActiveScope implements Scope
{
    /**
     * Past de scope toe op de query builder
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('is_active', true);
    }
}
```

### Toepassen op een model

<Steps>
  <Step title="Registreren met het #[ScopedBy]-attribute (aanbevolen)">
    In Laravel 13 is het `#[ScopedBy]`-attribute het eenvoudigst.

    ```php theme={null}
    <?php

    namespace App\Models;

    use App\Models\Scopes\ActiveScope;
    use Illuminate\Database\Eloquent\Attributes\ScopedBy;
    use Illuminate\Database\Eloquent\Model;

    #[ScopedBy([ActiveScope::class])]
    class User extends Model
    {
        //
    }
    ```

    Meerdere scopes geef je op als array.

    ```php theme={null}
    #[ScopedBy([ActiveScope::class, TenantScope::class])]
    class User extends Model
    {
        //
    }
    ```
  </Step>

  <Step title="Handmatig registreren in de booted()-methode">
    Je kunt ook de `booted`-methode overriden en `addGlobalScope` aanroepen.

    ```php theme={null}
    <?php

    namespace App\Models;

    use App\Models\Scopes\ActiveScope;
    use Illuminate\Database\Eloquent\Model;

    class User extends Model
    {
        protected static function booted(): void
        {
            static::addGlobalScope(new ActiveScope);
        }
    }
    ```
  </Step>
</Steps>

Voeg je een globale scope toe, dan krijgen alle query's, zoals `User::all()`, automatisch `WHERE is_active = 1`.

### Globale scopes met anonieme closures

Simpele scopes waarvoor een aparte klasse te veel is, kun je met een closure definiëren.

```php theme={null}
protected static function booted(): void
{
    static::addGlobalScope('active', function (Builder $builder) {
        $builder->where('is_active', true);
    });
}
```

<Warning>
  Wil je een met een closure gedefinieerde scope later uitsluiten, dan moet je de scopenaam (string) gebruiken in plaats van een klassenaam.
</Warning>

### Globale scopes uitsluiten

Soms wil je een scope voor een specifieke query uitschakelen.

```php theme={null}
use App\Models\Scopes\ActiveScope;

// Een specifieke scope uitsluiten
User::withoutGlobalScope(ActiveScope::class)->get();

// Een met een closure gedefinieerde scope uitsluiten
User::withoutGlobalScope('active')->get();

// Alle globale scopes uitsluiten
User::withoutGlobalScopes()->get();

// Meerdere scopes uitsluiten
User::withoutGlobalScopes([ActiveScope::class, TenantScope::class])->get();

// Alles uitsluiten behalve de opgegeven scopes
User::withoutGlobalScopesExcept([TenantScope::class])->get();
```

## Framework-intern: SoftDeletingScope

Kijk je hoe de standaard `SoftDeletes`-trait van Laravel globale scopes benut, dan zie je het implementatiepatroon.

`SoftDeletingScope` implementeert de `Scope`-interface.

```php theme={null}
// src/Illuminate/Database/Eloquent/SoftDeletingScope.php

class SoftDeletingScope implements Scope
{
    protected $extensions = [
        'Restore', 'RestoreOrCreate', 'CreateOrRestore',
        'WithTrashed', 'WithoutTrashed', 'OnlyTrashed',
    ];

    /**
     * Past de scope toe op de query builder
     * Voegt een beperking toe zodat alleen records met deleted_at = NULL worden opgehaald
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->whereNull($model->getQualifiedDeletedAtColumn());
    }

    /**
     * Breidt de query builder uit met macro's
     * Hierdoor worden withTrashed() / onlyTrashed() e.d. beschikbaar
     */
    public function extend(Builder $builder)
    {
        foreach ($this->extensions as $extension) {
            $this->{"add{$extension}"}($builder);
        }

        // Overridet de delete()-operatie zodat deleted_at wordt bijgewerkt
        $builder->onDelete(function (Builder $builder) {
            $column = $this->getDeletedAtColumn($builder);
            return $builder->update([
                $column => $builder->getModel()->freshTimestampString(),
            ]);
        });
    }
}
```

<Accordion title="De implementatie van withTrashed() bekijken">
  `withTrashed()` roept in werkelijkheid `withoutGlobalScope($this)` aan. Door de `SoftDeletingScope` zelf uit te sluiten, worden ook verwijderde records opgehaald.

  ```php theme={null}
  protected function addWithTrashed(Builder $builder)
  {
      $builder->macro('withTrashed', function (Builder $builder, $withTrashed = true) {
          if (! $withTrashed) {
              return $builder->withoutTrashed();
          }

          return $builder->withoutGlobalScope($this);
      });
  }
  ```

  Ook `onlyTrashed()` sluit de scope zelf uit en voegt daarbovenop `whereNotNull('deleted_at')` toe.

  ```php theme={null}
  protected function addOnlyTrashed(Builder $builder)
  {
      $builder->macro('onlyTrashed', function (Builder $builder) {
          $model = $builder->getModel();

          $builder->withoutGlobalScope($this)->whereNotNull(
              $model->getQualifiedDeletedAtColumn()
          );

          return $builder;
      });
  }
  ```
</Accordion>

<Tip>
  De `Scope`-interface definieert geen `extend`-methode, maar de builder van Eloquent roept `extend` automatisch aan als de scope die methode heeft. Handig wanneer je eigen macro's wilt toevoegen.
</Tip>

## Praktische use cases

### Multitenancy: automatisch filteren op tenant-ID

In SaaS-applicaties is het belangrijk om op alle query's automatisch een filter op tenant-ID toe te passen.

```php theme={null}
<?php

namespace App\Models\Scopes;

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

class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        if ($tenantId = auth()->user()?->tenant_id) {
            $builder->where('tenant_id', $tenantId);
        }
    }
}
```

```php theme={null}
use App\Models\Scopes\TenantScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;

#[ScopedBy([TenantScope::class])]
class Post extends Model
{
    //
}
```

Nu geeft alleen al het aanroepen van `Post::all()` uitsluitend de tenantdata van de ingelogde gebruiker terug.

### Openbaar/privé-filter

Voor als je in het beheerpaneel ook niet-gepubliceerde posts wilt tonen, maar in de frontend alleen gepubliceerde.

```php theme={null}
<?php

namespace App\Models\Scopes;

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

class PublishedScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('status', 'published')
                ->where('published_at', '<=', now());
    }
}
```

In het beheerpaneel sluit je de scope uit met `withoutGlobalScope`.

```php theme={null}
// Frontend: alleen gepubliceerd (PublishedScope wordt automatisch toegepast)
$posts = Post::latest()->get();

// Beheerpaneel: alle posts tonen
$posts = Post::withoutGlobalScope(PublishedScope::class)->latest()->get();
```

### Gebruik `addSelect` in plaats van `select`

<Warning>
  Wil je in een globale scope kolommen toevoegen, gebruik dan `addSelect` in plaats van `select`. Met `select` overschrijf je de kolommen die de aanroepende query al selecteert.

  ```php theme={null}
  // Slecht voorbeeld: overschrijft de select van de aanroeper
  public function apply(Builder $builder, Model $model): void
  {
      $builder->select('id', 'tenant_id', 'name');
  }

  // Goed voorbeeld: voegt toe aan de bestaande select
  public function apply(Builder $builder, Model $model): void
  {
      $builder->addSelect('tenant_id');
  }
  ```
</Warning>

## Volgende stap

<Card title="Custom casts van Eloquent" icon="wand-magic-sparkles" href="/nl/advanced/eloquent-casts">
  Leer hoe je omzettingslogica voor attributen implementeert als custom cast en het value-objectpatroon toepast.
</Card>


## Related topics

- [Eloquent bootable traits](/nl/advanced/eloquent-bootable-traits.md)
- [PHP-attributes](/nl/advanced/php-attributes.md)
- [Uitgestelde service providers](/nl/advanced/deferred-provider.md)
- [进阶主题](/zh-CN/advanced/index.md)
- [Laravel Socialite (sociale authenticatie)](/nl/socialite.md)
