Laravel İle Dinamik Bir Etiket Tabanlı Arama Filtresi Nasıl Yapılır?

Laravel İle Dinamik Bir Etiket Tabanlı Arama Filtresi Nasıl Yapılır?
Laravel İle Dinamik Bir Etiket Tabanlı Arama Filtresi Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu uygulamayı hayata geçirmek için sisteminizde Laravel 11 veya üzeri bir sürümün kurulu olması gerekmektedir. Veritabanı işlemleri için Eloquent ORM (Object-Relational Mapping) kullanılacaktır. Çalışma ortamınızda PHP 8.2+ sürümünün bulunduğundan emin olun.

  • Laravel 11.x kurulu bir proje
  • MySQL veya PostgreSQL veritabanı bağlantısı
  • Temel düzeyde Eloquent ilişkileri bilgisi

Veritabanı Yapısını ve İlişkileri Kurma

Etiket tabanlı bir sistem için "Many-to-Many" (Çoka-Çok) ilişkisi kurmalıyız. Bir yazının birden fazla etiketi olabilir, bir etiket de birden fazla yazıya ait olabilir. Bu işlem için posts, tags ve post_tag (pivot tablo) tablolarına ihtiyacımız var.

// Migration dosyası: create_post_tag_table
Schema::create('post_tag', function (Blueprint $table) {
    $table->id();
    $table->foreignId('post_id')->constrained()->onDelete('cascade');
    $table->foreignId('tag_id')->constrained()->onDelete('cascade');
});

Bu yapı, post ve tag tabloları arasındaki ilişkiyi sağlar. onDelete('cascade') komutu, bir yazı silindiğinde ilgili etiket bağlantılarının da otomatik olarak temizlenmesini garanti eder.

Eloquent İlişkilerini Tanımlama

Model sınıflarınızda bu ilişkiyi tanımlamak, sorguları yazarken işimizi oldukça kolaylaştıracaktır. Post ve Tag modelleri arasında belongsToMany metodunu kullanacağız.

// Post.php Modeli
public function tags()
{
    return $this->belongsToMany(Tag::class);
}

// Tag.php Modeli
public function posts()
{
    return $this->belongsToMany(Post::class);
}

Bu tanımlamalar sayesinde $post->tags diyerek bir yazının etiketlerine, $tag->posts diyerek ise bir etikete sahip tüm yazılara kolayca erişebiliriz.

Dinamik Filtreleme İçin Controller Mantığı

Kullanıcıdan gelen etiket ID'lerini alarak, bu etiketlerin tamamına sahip olan yazıları getiren bir sorgu yazmalıyız. Burada whereHas metodu, ilişkili tablolarda filtreleme yapmak için en verimli yöntemdir.

public function index(Request $request)
{
    $query = Post::query();

    if ($request->has('tags')) {
        $tagIds = $request->input('tags'); // Örn: [1, 3, 5]
        
        foreach ($tagIds as $tagId) {
            $query->whereHas('tags', function ($q) use ($tagId) {
                $q->where('tags.id', $tagId);
            });
        }
    }

    return $query->paginate(10);
}

Burada dikkat etmeniz gereken nokta, her bir etiket için ayrı bir whereHas bloğu eklememizdir. Bu, "hem A hem de B etiketine sahip olan" yazıları getirir. Eğer sadece "A veya B etiketine sahip olan" yazıları isteseydik whereIn kullanabilirdik.

Güvenlik ve Performans Uyarıları

Kritik Güvenlik Uyarısı: Kullanıcıdan gelen $request->input('tags') verisini doğrudan veritabanı sorgusuna dahil etmeden önce mutlaka doğrulamalısınız. Kullanıcıdan gelen ID'lerin integer olduğundan emin olmak için validate metodunu kullanın.

Sorgu performansını artırmak için post_tag tablosunda post_id ve tag_id sütunlarına index eklemeyi unutmayın. Büyük verilerde whereHas sorguları yavaşlayabilir, bu durumda "Eager Loading" (with('tags')) kullanarak N+1 problemini engelleyin.

Filtreleme Yöntemlerinin Karşılaştırılması

Yöntem Avantajı Dezavantajı
whereHas (Döngü) Kesin eşleşme (AND) sağlar. Çok fazla etiket varsa yavaşlar.
whereIn Hızlıdır (OR mantığı). Tüm etiketlerin varlığını garanti etmez.
Full-Text Search Çok büyük verilerde etkilidir. Kurulumu karmaşıktır.

Blade Şablonunda Arama Formu Oluşturma

Kullanıcıların etiketleri seçebilmesi için bir checkbox listesi oluşturacağız. Form gönderildiğinde etiket ID'leri bir dizi olarak sunucuya iletilecektir.


    @foreach($tags as $tag)
        
            id, request('tags', [])) ? 'checked' : '' }}>
            {{ $tag->name }}
        
    @endforeach
    Filtrele

name="tags[]" kullanımı, Laravel'in bu veriyi otomatik olarak bir dizi (array) şeklinde algılamasını sağlar. checked kontrolü ise sayfa yenilendiğinde kullanıcının seçtiği etiketlerin işaretli kalmasını sağlar.

Sıkça Sorulan Sorular

whereHas neden döngü içinde kullanılıyor?

Laravel'de whereHas her çağrıldığında o ilişki için bir "exists" sorgusu oluşturur. Birden fazla etiketi "AND" mantığıyla filtrelemek için her etiketi ayrı bir "exists" bloğuna sokmamız gerekir.

Performans için ne yapmalıyım?

Veritabanı tablolarınızda mutlaka foreign key indexlerini oluşturun. Ayrıca çok fazla veri varsa with('tags') kullanarak ilişkili verileri tek sorguda çekin.

Arama sonuçları boş gelirse ne yapmalıyım?

Sorgunuzun başına dd($query->toSql()) ekleyerek üretilen SQL sorgusunu inceleyebilir ve $request->all() ile gelen veriyi kontrol edebilirsiniz.

Bu yöntem e-ticaret sitelerinde kullanılır mı?

Evet, ürün filtreleme (renk, beden, marka) mantığı birebir bu yapı ile kurulur. Ancak ürün sayısı çok fazlaysa Elasticsearch gibi araçlara geçiş yapılması önerilir.

Etiketleri nasıl daha hızlı getirebilirim?

Etiket listesini her sayfa yüklemesinde veritabanından çekmek yerine, Laravel Cache mekanizmasını kullanarak belirli aralıklarla önbelleğe alabilirsiniz.

Yasal Sorumluluk Reddi: Bu rehberdeki kod örnekleri eğitim amaçlıdır. Üretim ortamına almadan önce tüm girdileri sanitize etmeli ve veritabanı sorgularınızda SQL injection riskine karşı Laravel'in sunduğu Query Builder ve Eloquent korumalarını kullandığınızdan emin olmalısınız.

Ajax ile Kesintisiz Kullanıcı Deneyimi

Dinamik filtreleme sistemlerinde sayfa yenileme (page reload) işlemi, özellikle çok sayıda etiket seçimi yapıldığında kullanıcı deneyimini olumsuz etkileyebilir. Laravel'in sunduğu axios kütüphanesi ile filtreleme işlemini arka planda gerçekleştirebiliriz.

Öncelikle, form gönderimini yakalayan bir JavaScript kodu yazalım:

document.querySelector('#filter-form').addEventListener('change', function() {
    const formData = new FormData(this);
    const queryString = new URLSearchParams(formData).toString();

    fetch(`/products?${queryString}`, {
        headers: { 'X-Requested-With': 'XMLHttpRequest' }
    })
    .then(response => response.text())
    .then(html => {
        document.getElementById('product-list').innerHTML = html;
    });
});

Controller tarafında ise isteğin Ajax olup olmadığını kontrol ederek yalnızca ilgili HTML parçasını dönen bir if bloğu eklemelisiniz:

public function index(Request $request) {
    $products = Product::filterByTags($request->tags)->get();

    if ($request->ajax()) {
        return view('partials.product-list', compact('products'))->render();
    }

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

Filtreleme Sisteminde Hata Ayıklama (Debugging) İpuçları

Karmaşık sorgular oluştururken veritabanına gönderilen SQL kodunu görmek, mantıksal hataları tespit etmenizi sağlar. Laravel'in toSql() metodu bu noktada en büyük yardımcınızdır.

Sorgunuzun doğruluğunu test etmek için şu yöntemi kullanabilirsiniz:

$query = Product::whereHas('tags', function($q) use ($tags) {
    $q->whereIn('name', $tags);
});

// SQL çıktısını ve bağlanan değişkenleri görüntüle
dd($query->toSql(), $query->getBindings());

Yaygın Karşılaşılan Hatalar:

  • N+1 Sorgu Problemi: Filtreleme sonrası ürünlerin etiketlerini listelerken with('tags') kullanmayı unutursanız, her ürün için ayrı bir sorgu çalışır.
  • Boş Dizi Kontrolü: whereIn metoduna boş bir dizi gönderirseniz sorgu hata verebilir. Mutlaka if ($request->has('tags')) kontrolü yapın.
  • Case Sensitivity: Veritabanı karşılaştırmalarında büyük/küçük harf duyarlılığı yaşıyorsanız, whereRaw('LOWER(name) = ?', [strtolower($tag)]) yapısını tercih edebilirsiniz.

Test Odaklı Geliştirme (TDD)

Filtreleme sisteminizin gelecekteki güncellemelerde bozulmadığından emin olmak için bir Feature Test yazmak en iyi pratiktir.

public function test_user_can_filter_products_by_tags() {
    $product = Product::factory()->create();
    $tag = Tag::factory()->create(['name' => 'Laravel']);
    $product->tags()->attach($tag);

    $response = $this->get('/products?tags[]=Laravel');

    $response->assertStatus(200);
    $response->assertSee($product->name);
}

Bu test, sisteminize yeni bir özellik eklediğinizde veya Laravel sürümünü yükselttiğinizde filtreleme mantığınızın hala beklendiği gibi çalıştığını doğrular.

Sonuç

Laravel ile dinamik bir etiket tabanlı arama filtresi oluşturmak, veritabanı ilişkilerini doğru kurgulamaktan geçer. Bu rehberde öğrendiğiniz whereHas mantığı ve checkbox form yapısı, profesyonel projelerin temelini oluşturur. Bir sonraki adım olarak, bu filtreleme sistemine "Ajax" desteği ekleyerek sayfa yenilenmeden sonuçların güncellenmesini sağlayabilir, böylece kullanıcı deneyimini bir üst seviyeye taşıyabilirsiniz.

Bu yazıya tepkinizi paylaşın:
Mert Çelik

Kendin yap (DIY) projeleri ve teknik tamirat rehberleri konusunda uzmanım. Denenmiş ve test edilmiş yöntemlerle okuyuculara güvenilir bilgiler sunuyorum.

Yorumlar (0)

Yorum Yaz