Laravel’in Spatie/QueryBuilder Paketi ile İlişkili Tablolarda Custom Sorting İşlemi

Turan Karatuğ
3 min readApr 20, 2020

Öncelikle bu paket ne işe yarıyor kısaca ondan bahsedelim. Bir model’e ait verileri tablo halinde listelediğimiz bir sayfamız olduğunu varsayalım. Bu tabloyu URL’deki querystring parametrelerine göre filtreleyebilmek, verileri sıralayabilmek ve hatta ilişkili olduğu diğer modellerden de verilerle doldurmak istiyoruz. Bu noktada laravel-query-builder paketi yardımımıza yetişiyor.

Kullanıcıların blog postlarını aşağıdaki gibi bir tabloda listelediğimizi düşünelim;

Bu tabloyu, içerisindeki herhangi bir/birkaç kolona göre filtreleyebiliyor ve/veya sıralayabiliyoruz.

use App\Post;
use App\Resources\PostResource;
use App\Http\Controllers\Controller;
use Spatie\QueryBuilder\QueryBuilder;
class PostController extends Controller
{
public function index()
{
$posts = QueryBuilder::for(Post::class)
->allowedFilters('status')
->allowedIncludes('user')
->allowedSorts('title')
->get();
return PostResource::collection($posts);
}
}

Yukarıdaki kullanım sonucunda blog post’larımız, status kolonuna göre filtrelenebilir, ilişkili olduğu User modeli üzerinden post yazarına ait verileri içerebilir ve post başlığına göre sıralanabilir durumdadır.

// Yalnızca status'u published olan blog postlarını getirir:
http://domain.com/posts?filter[status]=published
// Blog postlarını status kolonuna göre artan şekilde sıralar:
http://domain.com/posts?sort=status
// Blog postlarını status kolonuna göre azalan şekilde sıralar:
http://domain.com/posts?sort=-status

Paket hakkında data detaylı bilgiye ulaşmak için github reposunu ziyaret edebilirsiniz.

Konumuza geri dönecek olursak, geçtiğimiz günlerde, tablo halinde listelediğim verileri, modelin ilişkili olduğu başka bir modeldeki bir kolona göre sıralamam gerekti. Bu durumu, yukarıda verdiğim örneğe uyarlarsak, tablodaki verileri, postu yazan kullanıcıya göre sıralama olarak düşünebiliriz.

Normal şartlarda sıralama işlemine izin verilen kolonları, paketin allowedSorts() methodu içerisinde belirtebiliyoruz. Ancak bu kolonların hepsi de ana modele ait olmak zorunda. İlişkili bir modelde bulunan herhangi bir kolonu burada kullanamıyoruz. Paketin custom sorting özelliği var, ancak o da ilişkili bir modeldeki kolona göre sıralama yapmıyor.

Çözüm

Öncelikle sıralama sorgumuzu yazacağımız bir custom sort sınıfı oluşturuyoruz. Paketin dökümantasyon sayfasında bunun nasıl yapılacağı ile ilgili ayrıntılı bilgi mevcut.

Projemizin /app dizini altına CustomSorts adında yeni bir dizin oluşturup, sınıfımızı buraya koyabiliriz.

<?phpnamespace App\CustomSorts;use App\User;
use Illuminate\Database\Eloquent\Builder;
use Spatie\QueryBuilder\Sorts\Sort;
class UserCustomSort implements Sort
{
public function __invoke(Builder $query, $descending, string $property): Builder
{
return $query
->addSelect(['user_name' => User::select('name')->whereColumn('id', 'posts.user_id')])
->orderBy('user_name', $descending ? 'desc' : 'asc');
}
}

Şimdi yukarıda neler olduğuna bir bakalım. Öncelikle bu sınıf, QueryBuilder paketinin Sort interface’inden implement edildiği için invoke magic methoduna sahip olmalı ve bu method, interface’de belirtilen parametreleri içermelidir. Methodun içerisinde ise, sorgumuza addSelect() ile bir adet sub select sorgusu ekleyerek kullanıcı adlarını çekiyoruz. Burada users tablosundaki id kolonu, posts tablosundaki user_id kolonu ile ilişkili. Bu sub select sorgusuna bir alias verip sonuç olarak bu alias’a göre tüm kayıtları sıralıyoruz.

Daha sonra oluşturduğumuz bu custom sort sınıfını, sorgumuzdaki allowedSorts() methoduna eklememiz yeterli oluyor.

use App\Post;
use App\Resources\PostResource;
use Spatie\QueryBuilder\AllowedSort;
use Spatie\QueryBuilder\QueryBuilder;
class PostController
{
public function index()
{
$posts = QueryBuilder::for(Post::class)
->allowedSorts(
AllowedSort::custom('user', new UserCustomSort())
)
->get();
return PostResource::collection($posts);
}
}

Artık sayfamıza ?sort=user query string’i ile eriştiğimizde, blog postlarının, yazarlarının adına göre artan şekilde sıralandığını göreceğiz.

Yukarıdaki yöntem ile oluşturduğumuz UserCustomSort sınıfı, yalnızca blog postlarını kullanıcı adına göre sıralamamızı sağlamaktadır. Fakat projemizin başka bir yerinde, kullanıcılar ile ilişkili olan başka bir tabloya ait verileri de kullanıcı adına göre sıralamak isteyebiliriz. Bu durumda tekrar bir custom sort sınıfı oluşturmamız gerekecek, bu sınıf içerisinde de sadece ilişkili olan tablodaki kolon adı değişecek. Dolayısıyla kod tekrarı yapmış olacağız.

Bundan kaçınmak için UserCustomSort sınıfını biraz daha esnek hale getirmemiz gerekiyor. Şöyle ki, ilişki kurulacak tablo ve kolon bilgisini dışarıdan parametre olarak alacağız. Sonuç olarak sınıfımız aşağıdaki gibi olacaktır.

use App\User;
use Illuminate\Database\Eloquent\Builder;
use Spatie\QueryBuilder\Sorts\Sort;
class UserCustomSort implements Sort
{
protected string $relatedColumn;
public function __construct(string $relatedColumn)
{
$this->relatedColumn = $relatedColumn;
}
public function __invoke(Builder $query, $descending, string $property): Builder
{
return $query
->addSelect(['user_name' => User::select('name')->whereColumn('id', $this->relatedColumn)])
->orderBy('user_name', $descending ? 'desc' : 'asc');
}
}

Controller içerisinde kullanımı da şöyle değiştiriyoruz;

use App\Post;
use App\Resources\PostResource;
use Spatie\QueryBuilder\AllowedSort;
use Spatie\QueryBuilder\QueryBuilder;
class PostController
{
public function index()
{
$posts = QueryBuilder::for(Post::class)
->allowedSorts(
AllowedSort::custom('user', new UserCustomSort('posts.user_id'))
)
->get();
return PostResource::collection($posts);
}
}

Yukarıda kurduğumuz esnek yapı sayesinde, User modelindeki kullanıcılarla ilişkili olan tüm kayıtları, kullanıcı tablosundaki kolonlara göre sıralayabiliyoruz.

--

--