Laravel İle Çok Kriterli İleri Düzey Veri Arama Filtresi Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu rehberi uygulayabilmek için sisteminizde Laravel 11 veya üzeri bir sürümün yüklü olması önerilir. Veritabanı işlemleri için Eloquent ORM yapısını kullanacağız. Uygulamamızda performans kaybı yaşamamak için veritabanı indeksleme stratejilerini göz önünde bulundurmalısınız.

  • PHP 8.3+ sürümü
  • Laravel 11.x veya 12.x framework
  • Composer paket yöneticisi
  • Temel SQL bilgisi

Adım 1: Query Builder (Sorgu Oluşturucu) Yapısını Tasarlama

Geleneksel yöntemle controller içerisinde onlarca "if" bloğu yazmak yerine, "Query Scopes" veya "Pipeline" desenini kullanmak en profesyonel yaklaşımdır. Biz burada, Laravel'in sunduğu Pipeline yapısını kullanarak modüler bir arama filtresi oluşturacağız.


namespace App\Filters;

use Closure;
use Illuminate\Database\Eloquent\Builder;

abstract class Filter
{
    public function handle(Builder $query, Closure $next)
    {
        if (!request()->has($this->filterName())) {
            return $next($query);
        }

        $this->apply($query, request($this->filterName()));

        return $next($query);
    }

    abstract protected function filterName(): string;
    abstract protected function apply(Builder $query, $value);
}

Yukarıdaki soyut sınıf, her bir filtre kriterini ayrı bir sınıf haline getirmemizi sağlar. Bu sayede kodunuz "Open-Closed" prensibine uygun, genişletilebilir bir yapıya kavuşur.

Adım 2: Spesifik Filtre Sınıflarının Oluşturulması

Şimdi ürünler tablosunda "fiyat aralığı" ve "kategori" bazlı filtreleme yapacak sınıfları oluşturalım. Bu sınıflar, yukarıdaki soyut sınıfı miras alacaktır.


namespace App\Filters;

class PriceRange extends Filter
{
    protected function filterName(): string { return 'price_max'; }

    protected function apply($query, $value)
    {
        return $query->where('price', 'send(Product::query())
        ->through([
            \App\Filters\PriceRange::class,
            \App\Filters\CategoryFilter::class,
            \App\Filters\SearchQuery::class,
        ])
        ->thenReturn()
        ->paginate(15);

    return view('products.index', compact('products'));
}

Bu kod bloğu, gelen tüm filtreleri sırayla işler. Eğer bir filtre parametresi boşsa, Pipeline onu atlar ve bir sonraki filtreye geçer.

Adım 4: Veritabanı Performansı ve İndeksleme

Çok kriterli aramalarda veritabanı performansı hayati önem taşır. Eğer price, category_id veya name alanlarında arama yapıyorsanız, bu kolonlara indeks eklemelisiniz. Aksi takdirde, veritabanı her sorguda tüm tabloyu tarar (Full Table Scan).

Yöntem Avantaj Dezavantaj
If-Else Yığını Basit, hızlı başlangıç Bakımı zor, spagetti kod
Pipeline Deseni Modüler, test edilebilir Öğrenme eğrisi var
Eloquent Scopes Okunabilir Çok fazla kriterde karmaşıklaşır
Kritik Güvenlik Uyarısı: Kullanıcıdan gelen verileri asla doğrudan sorguya dahil etmeyin. Laravel'in Eloquent ORM yapısı otomatik olarak PDO parametre bağlama (parameter binding) kullandığı için SQL Injection saldırılarına karşı korumalıdır. Ancak ham sorgular (raw queries) kullanıyorsanız mutlaka whereRaw yerine parametreli sorguları tercih edin.

Adım 5: Gelişmiş Arama İçin Full-Text Search Entegrasyonu

Eğer metin tabanlı aramalarda (örneğin ürün ismi veya açıklaması) daha hızlı sonuç almak istiyorsanız, veritabanınızın Full-Text Search özelliğini kullanmalısınız. Laravel'de bunu whereFullText metodu ile kolayca yapabilirsiniz.


// ProductController.php içerisinde
$query->whereFullText(['name', 'description'], $request->search);

Bu yöntem, standart LIKE %...% sorgularına göre çok daha hızlıdır ve daha alakalı sonuçlar döndürür. Ancak, veritabanı motorunuzun (MySQL için InnoDB/MyISAM) bu özelliği desteklediğinden emin olun.

Adım 6: Hata Ayıklama ve Test Etme

Geliştirdiğiniz filtre yapısının doğru çalışıp çalışmadığını doğrulamak için Laravel'in Telescope aracını veya DB::enableQueryLog() fonksiyonunu kullanın. Bu, arka planda hangi SQL sorgularının çalıştığını görmenizi sağlar.


\DB::enableQueryLog();
// ... sorgu işlemleri ...
dd(\DB::getQueryLog());

Bu yöntemle, oluşturulan SQL sorgusunun beklediğiniz gibi olup olmadığını anlık olarak kontrol edebilirsiniz.

Sorumluluk Reddi: Bu makalede paylaşılan kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) kullanmadan önce mutlaka birim testleri (unit tests) yazın ve güvenlik açıklarını tarayın. Yazılım güvenliği sorumluluğu geliştiriciye aittir.

Sıkça Sorulan Sorular

Filtreleme yaparken neden "N+1" sorunu oluşur?

İlişkili modelleri (örneğin ürünün kategorisi) filtreleme sırasında çekmeye çalışırken, her satır için ayrı bir sorgu atılırsa N+1 sorunu oluşur. Bunu önlemek için with('category') metodunu kullanarak "Eager Loading" yapmalısınız.

Çok fazla kriter olduğunda sorgu yavaşlıyor, ne yapmalıyım?

Sorgu yavaşlıyorsa, filtrelediğiniz kolonlara veritabanı indeksleri ekleyin ve EXPLAIN komutu ile sorgu planını inceleyin. Ayrıca, büyük veri setlerinde veritabanı yerine Elasticsearch gibi araçlara geçişi değerlendirebilirsiniz.

Pipeline desenini kullanmak performansı düşürür mü?

Hayır, Pipeline deseni sadece kodun organizasyonunu sağlar. Çalışma zamanında (runtime) oluşturulan SQL sorgusu, elle yazdığınız bir sorgu ile aynıdır.

Kullanıcı boş filtre gönderirse ne olur?

Hazırladığımız Filter sınıfındaki if (!request()->has(...)) kontrolü sayesinde, boş parametreler sorguya dahil edilmez ve performans kaybı yaşanmaz.

Bu yapı API'lerde kullanılabilir mi?

Evet, bu yapı hem Blade şablonları hem de JSON döndüren API endpoint'leri için mükemmel bir şekilde çalışır.

Filtreleme Yapısını "Trait" Kullanarak Ölçeklendirme

Filtreleme mantığını her model için ayrı ayrı tanımlamak yerine, Laravel'in Trait yapısını kullanarak bu işlevselliği tüm modellerinize tek satırda ekleyebilirsiniz. Bu yöntem, kod tekrarını önler ve bakım maliyetini ciddi oranda düşürür.

Öncelikle app/Traits/Filterable.php adında bir dosya oluşturun ve sorgu kapsamını (scope) buraya taşıyın:

namespace App\Traits;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pipeline\Pipeline;

trait Filterable
{
    public function scopeFilter(Builder $query, array $filters, array $pipes)
    {
        return app(Pipeline::class)
            ->send($query)
            ->through($pipes)
            ->thenReturn();
    }
}

Artık bu özelliği kullanmak istediğiniz herhangi bir modelde (örneğin Product veya Order), trait'i dahil etmeniz yeterlidir:

namespace App\Models;

use App\Traits\Filterable;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use Filterable;
}

Controller içerisinde ise kullanımı oldukça temiz ve okunabilir hale gelecektir:

public function index(Request $request)
{
    return Product::filter($request->all(), [
        \App\Filters\CategoryFilter::class,
        \App\Filters\PriceRangeFilter::class,
        \App\Filters\SearchFilter::class,
    ])->paginate(15);
}

Filtreleme Süreçlerinde Debugging ve İzleme

Karmaşık filtreleme sistemlerinde, hangi filtrenin hangi SQL sorgusunu ürettiğini anlamak bazen zor olabilir. Pipeline desenini kullanırken, her bir filtrenin sorguyu nasıl değiştirdiğini izlemek için basit bir Middleware veya Log mekanizması kurabilirsiniz.

Filtre sınıflarınızın içerisine, geliştirme aşamasında sorguyu loglayan bir yapı eklemek hata ayıklama sürecini hızlandırır:

public function handle($query, \Closure $next)
{
    if (request()->has('price_min')) {
        \Log::info('Filtre uygulanıyor: PriceRangeFilter');
        $query->where('price', '>=', request('price_min'));
    }

    return $next($query);
}

Ayrıca, DB::enableQueryLog() kullanarak filtreleme sonrası oluşan nihai sorguyu kontrol etmeyi unutmayın:

\DB::enableQueryLog();

$products = Product::filter($request->all(), $pipes)->get();

dd(\DB::getQueryLog());

Bu yöntemle, özellikle leftJoin veya whereHas gibi karmaşık ilişkisel filtrelerde, Laravel'in arka planda oluşturduğu SQL bloğunu analiz ederek performans darboğazlarını (N+1 veya gereksiz join işlemleri) anında tespit edebilirsiniz.

Sonuç

Laravel ile çok kriterli ileri düzey veri arama filtresi oluşturmak, başlangıçta karmaşık görünse de Pipeline deseni gibi modern yaklaşımlarla oldukça yönetilebilir bir süreçtir. Bu rehberde öğrendiğiniz modüler yapı sayesinde, projeniz büyüdükçe yeni filtreler eklemek sadece yeni bir sınıf oluşturmak kadar kolay olacaktır. Bir sonraki adım olarak, bu filtreleme mantığını bir "Trait" içerisine alarak farklı modellerde de yeniden kullanılabilir hale getirmeyi deneyebilirsiniz.

Bu yazıya tepkinizi paylaşın:
Deniz Aydın

On yıllık içerik üretimi deneyimiyle karmaşık süreçleri herkesin anlayabileceği basit adımlara dönüştürüyorum. Pratik ev çözümleri ve hayatı kolaylaştıran teknikler konusunda uzmanım.

Yorumlar (0)

Yorum Yaz