Poznajemy loklane zasięgi dynamiczne, coś dużo łatwiejszego niż jego nazwa. Do dzieła.
Ok, oto przykład zasięgu dynamicznego:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include users of a given type.
*/
public function scopeOfType(Builder $query, string $type): void
{
$query->where('type', $type);
}
}
Dynamiczny, czyli przyjmuje jakiś argument. Tylko lokalny może być dynamiczny. Ten argument przekazujemy w nawiasach:
$admins = User::ofType('admin')->get();
Tak byśmy użyli dynamicznego. A statyczny?
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include users of a given type.
*/
public function scopeOfAdminType(Builder $query): void
{
$query->where('type', 'admin');
}
}
Ten jest statyczny, tylko buildera przyjmuje. Tak byśmy go użyli:
$admins = User::ofAdminType()->get();
I oczywiście tylko lokalne mogą być statyczne, globalne chodzą pod każdym query i nic im przekazać się nie da.
To kilka przykładów z fajnego repo na githubie, tych samych funkcji jako statyczne i dynamiczne zasięgi lokalne:
public function scopePopular(Builder $query): Builder
{
return $query->withCount('reviews')
->orderBy('reviews_count', 'desc');
}
public function scopeHighestRated(Builder $query): Builder
{
return $query->withAvg('reviews', 'rating')
->orderBy('reviews_avg_rating', 'desc');
}
Teraz wersja dynamiczna:
public function scopePopular(Builder $query, $from = null, $to = null): Builder|QueryBuilder
{
return $query->withCount([
'reviews' => fn(Builder $q) => $this->dateRangeFilter($q, $from, $to)
])
->orderBy('reviews_count', 'desc');
}
I wersja dynamiczna drugiego:
public function scopeHighestRated(Builder $query, $from = null, $to = null): Builder|QueryBuilder
{
return $query->withAvg([
'reviews' => fn(Builder $q) => $this->dateRangeFilter($q, $from, $to)
], 'rating')
->orderBy('reviews_avg_rating', 'desc');
}
private function dateRangeFilter(Builder $query, $from = null, $to = null)
{
return $query->withRecentReviews(fn($date) => $date->subWeek());
if ($from && !$to) {
$query->where('created_at', '>=', $from);
} elseif (!$from && $to) {
$query->where('created_at', '<=', $to);
} elseif ($from && $to) {
$query->whereBetween('created_at', [$from, $to]);
}
}
Bardziej chodzi mi o pokazanie idei czym jest dynamiczny zasięg lokalny, bo te przykłady mogą być odrobinę zbyt trudne, plus musimy przyjąć, że autor gdzieś stworzył inny zasięg lokalny withRecentReviews, do którego odwołuje się w metodzie pomocniczej dateRangeFilter, do której odwołuje się w swoim zasięgu.
Bardziej to pokazuję po to, aby uświadomić, że „tak można”, bo bardzo często nie chodzi o to, aby przedstawić komuś techniczne możliwości (możesz stworzyć klasę, metodę, metodę statyczną bla bla bla) ani kazać komuś zrozumieć jakiś kod (masz tu bryłę kodu i powiedz mi co jak działa i dlaczego) tylko chodzi o to, aby przedstawić pewne możliwości konceptualnego podejścia do rozwiązywania problemów.
Zresztą, niedługo poznamy bardziej zaawansowane rzeczy w Laravelu i takie scopes też będziemy sobie spokojnie pisali.