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ć.