aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Geliştirme

Laravel Eloquent İlişkileri: hasMany ve belongsTo Rehberi

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, morphMany gibi 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: hasMany için çoğul (comments), belongsTo için tekil (post).
  • Listeleme ekranlarında neredeyse her zaman with kullanı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.

Devamı için