February 13, 2024
Laravel v10.44 just shipped with a new approach to global scopes: #[ScopedBy]
. Using my codebase as an example: Products have a global scope that hides them if their Company is not visible.
1<?php 2 3namespace App\Models; 4 5use App\Models\Scopes\CompanyVisibility; 6use Illuminate\Database\Eloquent\Attributes\ScopedBy; 7 8#[ScopedBy([CompanyVisibility::class])] 9class Product10{11 ...12}
I like this syntax a lot, and I also like to use a scope to remove global scopes. It wasn't immediately obvious how to do that but after a little source diving I found the answer via the SoftDelete trait: an extend
method that registers a macro.
1artisan make:scope CompanyVisibility
1<?php 2 3namespace App\Models\Scopes; 4 5use Illuminate\Database\Eloquent\Builder; 6use Illuminate\Database\Eloquent\Model; 7use Illuminate\Database\Eloquent\Scope; 8 9class CompanyVisibility implements Scope10{11 public function apply(Builder $builder, Model $model): void12 {13 $builder->whereHas('company', fn ($builder) => $builder->active())14 }15 16 public function extend(Builder $builder): void17 {18 $builder->macro('withHidden', fn (Builder $builder) => $builder->withoutGlobalScope($this));19 }20}
That new builder macro means you can fluently remove the global scope:
1$product = Product::withHidden()->first();2 3$companyProducts = $company->products()->withHidden()->get();
You could achieve the same thing using withoutGlobalScope(CompanyVisibility::scope)
but I think writing it once and using a fluent method is much nicer.