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çinvalidatemetodunu 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ü:
whereInmetoduna boş bir dizi gönderirseniz sorgu hata verebilir. Mutlakaif ($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.


Yorumlar (0)
Yorum Yaz