Poznajemy lokalne i globalne query scopes w Laravelu. Kontynuacja lekcji poprzednich, kolumna hidden się przyda. Do dzieła.

Ok, zasięg globalny tworzymy komendą:

php artisan make:scope HiddenScope 

Teraz tam wejdziemy i napiszemy, że ma zwracać tylko te, które mają hidden na false (w zasadzie tinyint(1) na 0):

namespace App\Models\Scopes;

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

class HiddenScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('hidden', false);
    }
}

Ok, to jeszcze nie wszystko. Musimy ten zasięg nadać jakiemuś modelowi. Robimy to w nowy dla Laravela sposób, poprzez atrybut:

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Person;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Scopes\HiddenScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;


#[ScopedBy([HiddenScope::class])]
class Note extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = ['title', 'content', ];

    public function author(){
            return $this->belongsTo(Person::class, "person_id");
    }

}

Zasięg globalny, czyli domyślnie ten scope będzie aplikowany, chyba że ustalimy inaczej. Pierwsza komenda:

Artisan::command('notes-all', function () {

    $cnt = Note::count();
    $this->comment($cnt);
   
});

//88

No popatrz, a SELECT count(*) FROM `notes`; daje mi 89 i jakimś dziwnym trafem jedną notatkę mam ustawioną na hidden. Ok, teraz wszystkie:

Artisan::command('notes-all-with-hidden', function () {

    $cnt = Note::withoutGlobalScope(HiddenScope::class)->count();
    $this->comment($cnt);
   
});

//89

Teraz się zgadza. To znaczy „globalny” – zawsze aplikowany, chyba że określimy, że chcemy bez niego (jak z trashed trochę). Oczywiście musimy go zaimportować w console.php.

Ok, a gdybyśmy tak chcieli pokazać tylko ukryte? To właśnie dobry materiał na local query scope, czyli taki, który musimy sami wywołać, inaczej nie będzie aplikowany.

Robimy je w modelu, zaczynając od słowa scope:

use Illuminate\Database\Eloquent\Builder;

#[ScopedBy([HiddenScope::class])]
class Note extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = ['title', 'content', ];

    public function author(){
            return $this->belongsTo(Person::class, "person_id");
    }

    public function scopeHiddenOnly(Builder $query): void
    {
        $query->withoutGlobalScope(HiddenScope::class)->where('hidden', true);
    }

}

Buildera musieliśmy doimportować, choć od jakiegoś czasu DI type-hintem na Builder jest opcjonalne. I co my tu robimy?

Po pierwsze, wyłączamy zasięg globalny, który podłączyliśmy atrybutem (jak chcemy pokazywać ukryte to nie możemy mieć zasięgu, który ukryte ukrywa), po drugie bierzemy tylko te, które mają hidden na true (tak w zasadzie tinyint(1) na 1).

Teraz komenda:

Artisan::command('notes-hidden-only', function () {

    $cnt = Note::hiddenOnly()->count();
    $this->comment($cnt);
   
});
//1

Query scopes to obszerny temat, ale myślę, że nieco już poznaliśmy. Będziemy z tego często korzystać.