# Birçok modele bağlanan yorumlar için polymorphic ilişki mi yoksa ayrı tablolar mı; index ve foreign key açısından hangisi daha sağlıklı?

> 3 stabil tip ve gerçek foreign key gerekiyorsa tek tabloda exclusive arc kurun; tip sayısı açık uçluysa saf morph'ta `enforceMorphMap` şarttır.

- Soruldu: 2026-09-27
- Yanıtlandı: 2026-09-29
- Soran: Ceren
- Etiketler: laravel, eloquent, data-modeling
- Kaynak: https://muhammetsafak.com/tr/sor-bakalim/bircok-modele-baglanan-yorumlar-polymorphic-iliski-ayri-tablolar/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Bir Laravel uygulamasında yorumlar (comments) üç ayrı modele bağlanıyor: post, product ve ticket. Klasik yol olarak `morphMany` ile tek bir `comments` tablosu kullanmayı düşünüyorum ama gerçek foreign key kuramamak beni rahatsız ediyor.

Alternatif olarak her tip için ayrı tablo (post_comments, product_comments, ticket_comments) açıp gerçek FK ve cascade delete kurabilirim. Index ve referential integrity açısından hangisi daha sağlıklı? Yeni tip eklemenin kolaylığı ile veritabanı bütünlüğü arasında nasıl karar vermeliyim?


Kısa cevap: Polymorphic'in tek büyük bedeli gerçek foreign key kuramamaktır.

## Kısa cevap

3 stabil tipiniz varsa ve bütünlük sizin için kritikse tek tabloda "exclusive arc" desenini (nullable tipli FK'ler + CHECK) tercih ederdim; saf morph'ta kalacaksanız morph map kullanmak şart.

## Neden

1. **Polymorphic'in gerçek bedeli: referential integrity yok.** `commentable_id` üç farklı tabloya işaret ettiği için veritabanı bir foreign key uygulayamaz. Bir post silindiğinde ona bağlı yorumlar orphan kalır; cascade delete ve tutarlılık tamamen uygulama koduna (observer, event, cron) kalır. Bu, DB'nin en güçlü garantisini elle taklit etmek demektir.

2. **Ayrı tablolar: temiz bütünlük, ama çoğalma.** Gerçek FK, cascade, tipe özel constraint'ler — hepsi DB tarafından garantili. Bedeli: N tablo, şema ve kod tekrarı, migration yükü ve "bir kullanıcının tüm yorumları" gibi sorgular için UNION.

3. **Orta yol — exclusive arc (tek tablo + tipli FK'ler).** Tek `comments` tablosu; `post_id`, `product_id`, `ticket_id` hepsi nullable, gerçek FK'li ve bir CHECK ile tam olarak biri dolu. Tek tablonun rahatlığı + gerçek foreign key. Bedeli: yeni tip eklemek yeni bir kolon, yani şema değişikliği.

   ```sql
   CREATE TABLE comments (
     id          BIGSERIAL PRIMARY KEY,
     body        TEXT NOT NULL,
     post_id     BIGINT REFERENCES posts(id)    ON DELETE CASCADE,
     product_id  BIGINT REFERENCES products(id) ON DELETE CASCADE,
     ticket_id   BIGINT REFERENCES tickets(id)  ON DELETE CASCADE,
     CONSTRAINT one_target CHECK (
       (post_id IS NOT NULL)::int
     + (product_id IS NOT NULL)::int
     + (ticket_id IS NOT NULL)::int = 1
     )
   );
   CREATE INDEX ON comments (post_id)    WHERE post_id    IS NOT NULL;
   CREATE INDEX ON comments (product_id) WHERE product_id IS NOT NULL;
   ```

4. **Index tarafı iki yaklaşımda da kolay.** Morph'ta `(commentable_type, commentable_id)` composite index (Laravel'in varsayılanı) ["bu varlığın yorumları" sorgusunu](/tr/blog/eloquent-iliskileri-hasmany-belongsto-ve-eager-loading/) iyi karşılar. Arc'ta her FK için partial index yeterlidir. Yani index, kararın belirleyicisi değil.

## Ne yapmalı

1. **Morph kullanacaksanız morph map olmadan yapmayın.** Varsayılan olarak Laravel `commentable_type`'a tam sınıf adını (`App\Models\Post`) yazar. `enforceMorphMap` ile bunu kısa anahtara (`'post'`) çevirin: index ve storage küçülür, en önemlisi sınıf adını yeniden adlandırdığınızda veritabanı bozulmaz.

2. **Karar sürücüsü: tip sayısı stabil mi, bütünlük ne kadar kritik.** Tipler nadiren değişiyor ve integrity kritikse (ödeme, ticket gibi) arc veya ayrı tablolar. Tipler sürekli ekleniyor, esneklik integrity'den önemliyse morph + app-level bütünlük.

**Sonuç:** Ben olsam 3 stabil tip ve gerçek FK ihtiyacıyla exclusive arc'ı seçerdim — tek tablo kalır, cascade delete'i veritabanına bırakırım. Tiplerin sayısı belirsiz ve sürekli büyüyecekse saf morph'a geçer, `enforceMorphMap` ile anahtarlarımı sabitler ve orphan temizliğini bir observer ile kod tarafında garantiye alırdım. Kararı "hangisi şık" değil, "referential integrity'yi DB'ye mi bırakacağım" sorusuyla verirdim.

## İlgili Yazılar

- [Eloquent ilişkileri: hasMany, belongsTo ve eager loading](/tr/blog/eloquent-iliskileri-hasmany-belongsto-ve-eager-loading/) — Blog
- [İşlediğim satırları aynı anda güncellerken chunk yerine chunkById mı kullanmalıyım?](https://muhammetsafak.com/tr/sor-bakalim/isledigim-satirlari-ayni-anda-guncellerken-chunk-yerine-chunkbyid-mi-kullanmaliyim/) — Sor Bakalım
- [Integer cent olarak sakladığım para alanları için custom cast mı yoksa accessor/mutator mı kullanmalıyım?](https://muhammetsafak.com/tr/sor-bakalim/integer-cent-olarak-sakladigim-para-alanlari-icin-custom-cast-mi-yoksa/) — Sor Bakalım
- [Eloquent'te N+1'i erken yakalamak için Model::preventLazyLoading'i yalnızca lokalde mi yoksa production'da da mı açmalıyım?](https://muhammetsafak.com/tr/sor-bakalim/eloquentte-n-1i-erken-yakalamak-icin-model-preventlazyloadingi-yalnizca-lokalde-mi/) — Sor Bakalım
