Découvrez le fonctionnement des scopes locaux et globaux. Décryptez l’implémentation interne (SoftDeletingScope, etc.) et explorez des cas d’usage pratiques comme le multi-tenant et les filtres publié/privé.
Les scopes définis s’invoquent comme des méthodes. Le chaînage est possible.
use App\Models\Post;// Récupérer uniquement les posts publiés$posts = Post::published()->get();// Récupérer les posts publiés et populaires, triés par date décroissante$posts = Post::published()->popular()->latest()->get();
Combiner des scopes avec orWhere peut nécessiter un groupe logique.
// Méthode avec closure (fiable mais verbeuse)$users = User::popular()->orWhere(function (Builder $query) { $query->active();})->get();// L'utilisation d'une méthode d'ordre supérieur permet d'écrire plus simplement$users = User::popular()->orWhere->active()->get();
Un scope global est une classe qui implémente l’interface Illuminate\Database\Eloquent\Scope. Cette interface ne demande qu’une seule méthode apply.
// Définition de l'interface dans le framework// src/Illuminate/Database/Eloquent/Scope.phpinterface Scope{ public function apply(Builder $builder, Model $model);}
Ajoutez les contraintes au query builder dans la méthode apply.
Il y a des situations où vous voulez désactiver un scope pour certaines requêtes.
use App\Models\Scopes\ActiveScope;// Exclure un scope particulierUser::withoutGlobalScope(ActiveScope::class)->get();// Exclure un scope défini via closureUser::withoutGlobalScope('active')->get();// Exclure tous les scopes globauxUser::withoutGlobalScopes()->get();// Exclure plusieurs scopesUser::withoutGlobalScopes([ActiveScope::class, TenantScope::class])->get();// Exclure tout sauf les scopes spécifiésUser::withoutGlobalScopesExcept([TenantScope::class])->get();
Voir comment le trait standard SoftDeletes de Laravel utilise un scope global vous éclaire sur le pattern d’implémentation.SoftDeletingScope implémente l’interface Scope.
// src/Illuminate/Database/Eloquent/SoftDeletingScope.phpclass SoftDeletingScope implements Scope{ protected $extensions = [ 'Restore', 'RestoreOrCreate', 'CreateOrRestore', 'WithTrashed', 'WithoutTrashed', 'OnlyTrashed', ]; /** * Applique le scope au query builder * Ajoute la contrainte pour ne récupérer que les lignes où deleted_at est NULL */ public function apply(Builder $builder, Model $model) { $builder->whereNull($model->getQualifiedDeletedAtColumn()); } /** * Étend le query builder avec des macros * Rend disponibles withTrashed() / onlyTrashed(), etc. */ public function extend(Builder $builder) { foreach ($this->extensions as $extension) { $this->{"add{$extension}"}($builder); } // Surcharger l'opération delete() pour qu'elle mette à jour deleted_at à la place $builder->onDelete(function (Builder $builder) { $column = $this->getDeletedAtColumn($builder); return $builder->update([ $column => $builder->getModel()->freshTimestampString(), ]); }); }}
Voir l'implémentation de withTrashed()
withTrashed() appelle en réalité withoutGlobalScope($this). Autrement dit, il exclut SoftDeletingScope lui-même pour permettre la récupération des enregistrements supprimés.
protected function addWithTrashed(Builder $builder){ $builder->macro('withTrashed', function (Builder $builder, $withTrashed = true) { if (! $withTrashed) { return $builder->withoutTrashed(); } return $builder->withoutGlobalScope($this); });}
De même, onlyTrashed() exclut le scope puis ajoute whereNotNull('deleted_at').
protected function addOnlyTrashed(Builder $builder){ $builder->macro('onlyTrashed', function (Builder $builder) { $model = $builder->getModel(); $builder->withoutGlobalScope($this)->whereNotNull( $model->getQualifiedDeletedAtColumn() ); return $builder; });}
Bien que la méthode extend ne soit pas définie dans l’interface Scope, le builder Eloquent l’appelle automatiquement si le scope la possède. C’est utile pour ajouter des macros personnalisées.
Pour ajouter une colonne dans un scope global, utilisez addSelect et non select. Utiliser select écrasera les colonnes que l’appelant a définies dans son select.
// Mauvais exemple : écrase le select de l'appelantpublic function apply(Builder $builder, Model $model): void{ $builder->select('id', 'tenant_id', 'name');}// Bon exemple : ajoute au select existantpublic function apply(Builder $builder, Model $model): void{ $builder->addSelect('tenant_id');}