Laravel Eloquent ilişkileri, veritabanı tablolarınız arasındaki bağları PHP nesneleri üzerinden okunabilir ve zarif bir şekilde ifade etmenizi sağlar. Bir blog yazısının yorumları, bir kullanıcının siparişleri veya bir kategorinin ürünleri… Bu bağları el ile JOIN sorguları yazarak yönetmek yerine, Eloquent modellerinde tek bir metotla tanımlarsınız. Bu yazıda en sık kullanılan iki ilişkiyi — hasMany ve belongsTo — örneklerle ele alacak, ardından diğer ilişki türlerine ve performansın bel kemiği olan eager loading'e değineceğiz.
İlişki türlerine genel bakış
Eloquent, ilişkili modelleri birer metot olarak tanımladığınız bir yapı sunar. Tipik bir uygulamada şu ilişkilerle karşılaşırsınız:
- Bire-bir (
hasOne/belongsTo): Bir kullanıcının tek bir profili vardır. - Bire-çok (
hasMany/belongsTo): Bir yazının birden çok yorumu vardır. - Çoka-çok (
belongsToMany): Bir yazının birden çok etiketi, bir etiketin birden çok yazısı vardır. - Uzaktan ilişki ve polimorfik:
hasManyThrough,morphManygibi daha ileri senaryolar.
Bu yazıda temel taşı oluşturan bire-çok ilişkisine odaklanıyoruz, çünkü hasMany ve onun karşılığı belongsTo birlikte çalışır.
hasMany: "bir kayıt, birçok kayda sahip"
Klasik örnek bir blog'tur: bir Post (yazı) birçok Comment (yorum) içerir. Veritabanı tarafında comments tablosunda bir post_id sütunu bulunur. Bu yabancı anahtar, hangi yorumun hangi yazıya ait olduğunu söyler.
// app/Models/Post.php
class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Eloquent varsayılan olarak yabancı anahtarın, üst modelin adı (tekil) + _id biçiminde olduğunu varsayar; yani post_id. Eğer sütun adınız farklıysa, ikinci ve üçüncü parametrelerle açıkça belirtebilirsiniz:
return $this->hasMany(Comment::class, 'post_id', 'id');
Artık bir yazının yorumlarına bir özellikmiş gibi erişebilirsiniz:
$post = Post::find(1);
foreach ($post->comments as $comment) {
echo $comment->body;
}
$post->comments ifadesini parantezsiz çağırdığınızda bir Collection dönerken, $post->comments() bir sorgu kurucusu (query builder) döndürür. İkincisi sayesinde ilişkiyi daraltabilirsiniz:
$approved = $post->comments()
->where('approved', true)
->latest()
->get();
belongsTo: ilişkinin diğer ucu
belongsTo, hasMany'nin ters yönüdür. Yorumun bakış açısından düşünün: her yorum bir yazıya aittir. Yabancı anahtarı tutan taraf — yani post_id sütununu barındıran comments tablosu — her zaman belongsTo tarafıdır.
// app/Models/Comment.php
class Comment extends Model
{
public function post()
{
return $this->belongsTo(Post::class);
}
}
Böylece bir yorumdan üst yazıya kolayca ulaşırsınız:
$comment = Comment::find(5);
echo $comment->post->title;
İki tarafı da tanımlamak iyi bir alışkanlıktır: Post tarafında hasMany, Comment tarafında belongsTo. Bu, ilişkide her iki yönde de gezinebilmenizi sağlar ve kodu okuyan herkes için niyetinizi netleştirir.
İlişki üzerinden kayıt oluşturma
Eloquent ilişkileri sadece okuma için değildir; yeni kayıtları yabancı anahtarı otomatik doldurarak oluşturabilirsiniz. save ve create metotları post_id değerini sizin için ayarlar:
$post = Post::find(1);
$post->comments()->create([
'body' => 'Harika bir yazı, teşekkürler!',
'author' => 'Ayşe',
]);
create kullanabilmek için ilgili alanları modelin $fillable dizisine eklemeyi unutmayın. Ters yönde, var olan bir yorumu bir yazıya bağlamak için associate kullanılır:
$comment->post()->associate($post);
$comment->save();
N+1 sorunu ve eager loading
İlişkilerin en sık yapılan hatası gizli performans tuzağıdır: N+1 sorgu problemi. Bir döngü içinde her yazının yazarına eriştiğinizi düşünün:
$posts = Post::all(); // 1 sorgu
foreach ($posts as $post) {
echo $post->author->name; // her yazı için 1 sorgu daha
}
50 yazınız varsa bu, 1 + 50 = 51 sorgu demektir. Çözüm eager loading'dir: with metoduyla ilişkiyi önceden, tek bir ek sorguda yüklersiniz.
$posts = Post::with('author')->get(); // toplam 2 sorgu
foreach ($posts as $post) {
echo $post->author->name; // ek sorgu yok
}
Eloquent burada perde arkasında bir WHERE ... IN (...) sorgusu çalıştırarak tüm yazarları tek seferde çeker. İç içe ve birden çok ilişkiyi de yükleyebilirsiniz:
$posts = Post::with(['author', 'comments.user'])->get();
Geliştirme sırasında bu hatayı erken yakalamak için modelde Model::preventLazyLoading() ile tembel yüklemeyi yasaklayabilir, ya da Laravel Debugbar gibi araçlarla sorgu sayısını izleyebilirsiniz. Yalnızca ilişkili kayıt sayısını istiyorsanız, tüm kayıtları belleğe çekmek yerine withCount kullanın:
$posts = Post::withCount('comments')->get();
echo $posts->first()->comments_count;
Pratik ipuçları
- Yabancı anahtar sütunları için her zaman bir veritabanı indeksi tanımlayın; bu, ilişki sorgularını belirgin biçimde hızlandırır.
- Migration'larda
foreignId('post_id')->constrained()->cascadeOnDelete()ile hem indeks hem de bütünlük kuralı oluşturun. - İlişki metot adlarını anlamlı ve çoğul/tekil tutarlılığına uygun seçin:
hasManyiçin çoğul (comments),belongsToiçin tekil (post). - Listeleme ekranlarında neredeyse her zaman
withkullanın; tek kayıt detayında ise gerektiği kadarını yükleyin.
Sık Sorulan Sorular
hasMany ile belongsTo arasındaki fark nedir?
hasMany ilişkinin "üst" tarafında tanımlanır ve birçok alt kaydı temsil eder. belongsTo ise yabancı anahtarı tutan "alt" tarafta yer alır ve tek bir üst kayda işaret eder. İkisi aynı ilişkinin iki yönüdür.
Yabancı anahtar sütun adımı Eloquent'in varsayımından farklı koyabilir miyim?
Evet. İlişki metoduna ikinci parametre olarak yabancı anahtarı, üçüncü olarak da yerel anahtarı geçirebilirsiniz: hasMany(Comment::class, 'yazi_id', 'id'). Eloquent aksi belirtilmezse tekil model adı + _id varsayar.
Eager loading her zaman mı kullanılmalı?
İlişkili veriye bir döngüde erişiyorsanız evet — N+1 sorununu önler. Ancak ilişkiye hiç erişmeyecekseniz gereksiz yere yüklemeyin; bu da fazladan sorgu ve bellek anlamına gelir. Dengeyi ihtiyacınıza göre kurun.
İlişkileri doğru kurmak, hızlı ve sürdürülebilir bir Laravel uygulamasının temelidir. Projenizde Eloquent modellemesi, N+1 optimizasyonu veya komple bir Laravel arka uç geliştirmesi için yardıma ihtiyacınız varsa benimle iletişime geçin ve birlikte sağlam bir temel kuralım.