# Eloquent ilişkileri: hasMany, belongsTo ve eager loading

> Laravel Eloquent'te hasMany ve belongsTo ilişkilerini kurmayı ve N+1 sorgusunu eager loading ile çözmeyi anlattım.

- Yayın: 2015-09-06
- Güncelleme: 2026-09-07
- Kategori: Framework & Kütüphane
- Etiketler: Laravel
- Okuma süresi: 3 dk okuma
- Kaynak: https://muhammetsafak.com/tr/blog/eloquent-iliskileri-hasmany-belongsto-ve-eager-loading/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
[Eloquent ORM ile veritabanı işlemleri kolaylaşıyor](/tr/blog/eloquent-orm-ile-veritabani-islemlerini-sadestirmek/) ama ilişkili verileri çekerken bir tuzak var: **N+1 sorgu problemi**. Bu yazıda en sık kullandığım iki ilişki türünü — `hasMany` ve `belongsTo` — anlatacağım ve ardından eager loading ile bu problemin üstesinden nasıl gelindiğini göstereceğim.

## hasMany ve belongsTo ilişkileri

Bir blog uygulamasında `User` (Kullanıcı) ve `Post` (Gönderi) modelleri olduğunu varsayalım. Bir kullanıcının birden fazla gönderisi olabilir. Bu bir **bir-çok (one-to-many)** ilişkisidir.

`User` modeli, gönderiler için `hasMany` (çoğuna sahip) tanımlar:

```php
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}
```

`Post` modeli ise kullanıcıya geri döner — `belongsTo` (ait olmak):

```php
class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
```

Eloquent bu ilişkiyi kurarken [dokümanın tarif ettiği gibi](https://laravel.com/docs/5.1/eloquent-relationships#one-to-many) varsayılan olarak `posts` tablosundaki `user_id` sütununu kullanır. Farklı bir sütun adı varsa `hasMany(Post::class, 'yazar_id')` şeklinde belirtebilirsiniz.

Kullanımı şöyle:

```php
$kullanici = User::find(1);
$gonderiler = $kullanici->posts; // Tüm gönderiler

$gonderi = Post::find(5);
$yazar = $gonderi->user; // Gönderinin yazarı
```

İlişki metodunu özellik (property) gibi çağırdığınızda Eloquent sorguyu çalıştırır ve sonucu döndürür. Bunu dinamik özellik (dynamic property) deniyor.

## N+1 sorgu problemi

İlişkiler bu kadar basitken neden dikkat gerekiyor?

Şu kodu inceleyin:

```php
$kullanicilar = User::all();

foreach ($kullanicilar as $kullanici) {
    echo $kullanici->name . ': ' . $kullanici->posts->count() . ' gönderi';
}
```

Bu kod çalışır. Ama arka planda ne oluyor?

1. `User::all()` — 1 sorgu (tüm kullanıcıları getir)
2. Döngüde her kullanıcı için `$kullanici->posts` — her kullanıcıya 1 sorgu

10 kullanıcı varsa 11 sorgu çalışıyor. 100 kullanıcı varsa 101 sorgu. Kullanıcı sayısı arttıkça sorgu sayısı da artıyor. Buna **N+1 sorgu problemi** deniyor.

Bu sorunu fark etmem biraz zaman aldı. Geliştirme ortamında küçük veri kümeleriyle her şey hızlıydı; ama bir istemci projesinde kullanıcı sayısı birkaç yüze çıkınca sayfa yüklenme süresi belirgin biçimde uzadı. Sorgu sayısına bakınca 340 kullanıcı için 341 sorgu çalıştığını gördüm. Hata kod'da değil, alışkanlıktaydı.

## Eager loading ile çözüm

Doküman problemi ve çözümü aynı yerde tanımlıyor: [ilişkiye özellik gibi eriştiğinizde veri "lazy load" edilir](https://laravel.com/docs/5.1/eloquent-relationships#eager-loading) — yani özelliğe ilk kez dokunana kadar yüklenmez — ve eager loading "N + 1 sorgu problemini hafifletir". **Eager loading**, ilişkili verileri ana sorguyla birlikte yükler. Bunu `with()` metodu ile yapıyorsunuz:

```php
$kullanicilar = User::with('posts')->get();

foreach ($kullanicilar as $kullanici) {
    echo $kullanici->name . ': ' . $kullanici->posts->count() . ' gönderi';
}
```

Şimdi yalnızca 2 sorgu çalışıyor:

1. `SELECT * FROM users`
2. `SELECT * FROM posts WHERE user_id IN (1, 2, 3, ...)`

Kullanıcı sayısı 100 olsa bile hâlâ 2 sorgu. Bu farkı özellikle veri miktarı büyüdüğünde çok belirgin hissediyorsunuz.

## Birden fazla ilişkiyi yüklemek

`with()` birden fazla ilişkiyi aynı anda yükleyebiliyor:

```php
$gonderiler = Post::with(['user', 'comments'])->get();
```

İç içe ilişkiler için nokta sözdizimi:

```php
$kullanicilar = User::with('posts.comments')->get();
```

Bu, kullanıcıları, onların gönderilerini ve her gönderinin yorumlarını tek seferde getiriyor — üç sorgu toplamda, binlerce olası N+1 yerine.

## Koşullu eager loading

Yüklenen ilişkiye koşul eklemek de mümkün:

```php
$kullanicilar = User::with([
    'posts' => function ($sorgu) {
        $sorgu->where('yayinlandi', true)->orderBy('created_at', 'desc');
    }
])->get();
```

Yalnızca yayımlanmış gönderiler, tarihe göre sıralı olarak yükleniyor — [doküman buna "constraining eager loads" diyor](https://laravel.com/docs/5.1/eloquent-relationships#constraining-eager-loads).

Burada dikkat edilmesi gereken bir nokta var: eager loading koşulu uygulanmış ilişki, o kullanıcıya ait olmayan gönderileri değil, yalnızca koşulu geçemeyenleri dışarıda bırakıyor. Yani `posts` boş gelse de kullanıcı nesnesi hâlâ döner; `null` gelmez. Bu fark özellikle görünüm katmanında koşullu render yazarken yanıltıcı olabiliyor.

## Lazy eager loading

Modeli zaten çektiniz ama sonradan ilişkiye ihtiyaç duydunuz. [Dokümanın "lazy eager loading" dediği](https://laravel.com/docs/5.1/eloquent-relationships#lazy-eager-loading) `load()` metodu bunu çözüyor:

```php
$kullanici = User::find(1);

// Sonradan ilişkiyi yükle
$kullanici->load('posts');
```

Birden fazla ilişki için:

```php
$kullanicilar = User::all();
$kullanicilar->load('posts', 'profile');
```

## Ne zaman hangi yöntem?

Veriyi çekerken ilişkiye kesinlikle ihtiyacınız varsa `with()` kullanın. Koşula bağlı olarak bazen ilişkiye ihtiyaç duyacaksanız `load()` daha uygun olabilir.

N+1 problemini tespit etmenin pratik yolu, Laravel'in sorgu günlüğünü açmak:

```php
DB::enableQueryLog();
// ... kod ...
dd(DB::getQueryLog());
```

Döngü içinde tekrarlayan sorgular görünce `with()` eklemenin zamanı gelmiştir.

Buraya bugünden bir not: bu tespiti artık elle yapmak zorunda değilsiniz. Laravel'e [ilişkilerin lazy load edilmesini tamamen yasaklatabiliyorsunuz](https://laravel.com/docs/12.x/eloquent-relationships#preventing-lazy-loading) — `AppServiceProvider`'ın `boot` metodunda `Model::preventLazyLoading()` çağrıldığında, eager load edilmemiş bir ilişkiye erişmek exception fırlatıyor. Geliştirme ortamında açık bırakmak, bu yazıda anlattığım hatayı fark etmenizi beklemek yerine size gösteriyor.

İlk başta eager loading'i atlayıp sonradan sayfa yükleme sürelerinin uzadığını fark etmek bir uyarı olmaya başladı benim için. Artık ilişki kullanacağım her yerde `with()` gerekip gerekmediğini sormayı alışkanlık edindim.

---

### Kaynaklar

- [Laravel 5.1: Eloquent Relationships — One To Many](https://laravel.com/docs/5.1/eloquent-relationships#one-to-many)
- [Laravel 5.1: Eloquent Relationships — Eager Loading](https://laravel.com/docs/5.1/eloquent-relationships#eager-loading)
- [Laravel 5.1: Eloquent Relationships — Constraining Eager Loads](https://laravel.com/docs/5.1/eloquent-relationships#constraining-eager-loads)
- [Laravel 5.1: Eloquent Relationships — Lazy Eager Loading](https://laravel.com/docs/5.1/eloquent-relationships#lazy-eager-loading)
- [Laravel 12.x: Eloquent Relationships — Preventing Lazy Loading](https://laravel.com/docs/12.x/eloquent-relationships#preventing-lazy-loading)
