Cześć. W tym wpisie jak pokażę, jak używać niestandardowe query scope w eloquent we Frameworku Laravel. Przyznam się, że pierwszy wpis pisałem w języku angielskim, i można ten wpis zobaczyć, jeśli kliknąć "EN" u góry strony.
Ten angielski wpis jest dosyć długi, i też częściowo pokrywa temat migracji, relacji. W tym nie będę opisywać ustawienia baz danych oraz ustawienia relacji, natomiast pokaże któtko pewne zagadnienia, które dotyczą właśnie niestandardowych scope'ów w Laravelu.
Pierszę co warto zapamiętać, to jest to, że Eloquent ( ORM używany w Laravelu ) ma tzw. scopy.
Co to są query scopes Odpowiem swoimi słowy: scope to jest zestaw zapytań do bazy danych, który można potem łatwo wykorzystać. Pokaże to na przykładzie
Mamy aplikację gdzie jest blog. I w admin panelu możemy ustawiać czy wpis jest opublikowany czy nie. Podobne podejście można zobaczyć np. w CMS Wordpress. Żeby to realizować najprawdopodobniej w bazie danych w tabeli z wpisami będziemy mieli tabele is_published, która może mieć wartość true czy false. Jeśli potrzebujemy pokazać tyko opublikowane posty to nasz kod będzie wyglądać tak:
namespace App\Services;
use App\Models\Post;
class PostService
{
public function getPublishedPosts()
{
$posts = Post::where('is_published', true)->get();
return $posts;
}
}
Jeśli potrzebujemy pobrać tylko opublikowane wpisy to napiszemy taki sam kod w innym miejscu. Na razie problemu nie ma.
A wyobraźmy sobie, że mamy w naszym blogu też lajki, i na przykład uważamy że wpis jest popularny jeśli ma więcej niż 100 lajków.
Teraz pobierzemy posty które są opublikowane i popularne i też sortujemy te posty od najpopularniejszego do najmniej popularnego.
namespace App\Services;
use App\Models\Post;
class PostService
{
public function getPublishedPosts()
{
$posts = Post::where('is_published', true)->where('likes', '>', 100)->orderByDesc('likes')->get();
return $posts;
}
}
Zrobiliśmy to, czego żyśmy chcieli, ale teraz, jeśli będziemy chcieli pobrać dokładnie takie same najpopularniejsze posty w innym miesjcu to będziemy musieli przepisywać cały kod jeszcze raz, a to nie jest dobrze.
I tutaj nam się przydadzą query scope'y czyli te same zestawy zapytań, o których pisałem wcześniej. Jak zaimplementować to wszytko. Już pokazuję:
Idziemy do Modelu Post i dopisujemy jedną metodę:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
public function scopePublishedPopular(Builder $query): void
{
$query->where('is_published', true)->where('likes', '>', 100)->orderByDesc('likes');
}
}
I teraz w naszym serwisie robimy tak:
namespace App\Services;
use App\Models\Post;
class PostService
{
public function getPublishedPosts()
{
$posts = Post::publishedPopular()->get();
return $posts;
}
}
W ten sposób uzyskamy te same wpisy, ale kod w serwisie jest krótszy i możemy go wykorzystać w innym miejscu.
Pełna dokumentacja o scopach ( Lokalnych i też Globalnych ) w Laravelu jest na stronie dokumentacji:
https://laravel.com/docs/10.x/eloquent#local-scopes
Co to oznacza? Macro to to jest technika, za pomocą której możemy dodać dodatkowe metody do klas. W danym przypadku nas interesuje dodanie subQuery do Eloquent Builder. Dodamy taki kod do naszego AppServiceProvider:
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Builder::macro('addSubSelect', function ($column, $query) {
if (is_null($this->columns)) {
$this->select($this->from . '.*');
}
return $this->selectSub($query, $column);
});
}
Teraz nasza klasa Builder może tworzyć tzw. wirtulna kolumnę. Cała logika pracuje po stronie bazy danych, i dlatego aplikacje będzie szybka.
Przykład:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
public function scopePublishedPopular(Builder $query): void
{
$query->where('is_published', true)->where('likes', '>', 100)->orderByDesc('likes');
}
public function scopeWithCopyOfTitle(Builder $query): void
{
$query->addSubSelect('copy_of_title', function ($query) {
$query->select('title')
->limit(1);
});
}
}
I wtedy nasz wpis będzie miał wirtualną kolumnę, która będzie wyliczana na podstawie mysql, a nie php. I dzięki temu będziemy mieli czystszy kod i większą wydajność aplikacji.