# Tip güvenli koleksiyonlar için PHPStan generics mi kullanmalıyım, yoksa her tür için ayrı sınıf mı yazmalıyım?

> `list<Order>` docblock'ları ve CI'da katı PHPStan'ı varsayılan yapın; davranış gerekince tek generic `Collection<T>`, güven sınırında concrete sınıf.

- Soruldu: 2026-07-26
- Yanıtlandı: 2026-07-31
- Soran: Ozan
- Etiketler: php, static-analysis
- Kaynak: https://muhammetsafak.com/tr/sor-bakalim/tip-guvenli-koleksiyonlar-icin-phpstan-generics-mi-kullanmaliyim-yoksa-her-tur/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Domain katmanımda her yerde tipsiz entity dizileri dolaşıyor — `function process(array $orders)` gibi imzalarla `Order[]` geçiyorum ama PHPStan içeride yanlış tip olduğunda bana hiçbir şey söylemiyor.

Runtime generics olmadan compile-time (daha doğrusu analiz zamanı) güvenlik istiyorum. Her entity türü için `OrderCollection`, `InvoiceCollection` gibi elle sınıf yazmak mı doğru, yoksa PHPStan generics ile mi çözmeliyim? Boilerplate ile tip güvenliği arasında nerede durmalıyım?


Kısa cevap: Çoğu durum için PHPStan generics (generic docblock'lar ve array şekilleri) kullanın; elle yazılmış concrete koleksiyon sınıfına yalnızca davranış ya da runtime garanti gerektiğinde geçin.

## Kısa cevap

Sırf tip için her türe ayrı sınıf yazmak boşa boilerplate'tir. PHP'nin motorunda tip bildirimlerinin nereye kadar gittiğini [skaler tip bildirimleri yazısında](/tr/blog/php-7-de-skaler-tip-bildirimleri-ve-donus-tipleri/) anlatmıştım; bir dizinin eleman tipi tam da o sınırın dışında kalır ve boşluğu analiz zamanında PHPStan doldurur.

## Neden

1. **PHP'de runtime generics yok — PHPStan generics tamamen statiktir.** `@param list<Order> $orders` size sıfır runtime maliyetiyle analiz zamanında tip güvenliği verir. İstediğiniz tam olarak budur; interpreter hiçbir şey kontrol etmez, PHPStan CI'da yakalar.

2. **Sadece sınıflar değil, fonksiyonlar da generic olabilir.** Tip taşıyan bir yardımcıya ihtiyacınız varsa (`first`, `map` gibi) sınıf yazmadan fonksiyonu `@template` ile generic yapabilirsiniz — `@param list<T> $items` alıp `@return T` döndüren bir fonksiyon, tipi çağıran tarafta korur. Bu, koleksiyon etrafındaki çoğu ihtiyacı sınıf açmadan karşılar.

3. **Bu güvenlik yalnızca PHPStan gerçekten çalıştığı sürece vardır.** Annotation'ların runtime karşılığı olmadığı için analiz durduğunda kontrol de durur: annotation'lar çürür ve size sahte güven verir. Concrete alt sınıf ise runtime maliyeti ve boilerplate getirir; ikisinin arasındaki seçim tam olarak burada yapılır.

## Ne yapmalı

1. **Tipli dizilerle başlayın.** Koleksiyonları öylece dolaştırmak için imzalarda `list<Order>` veya `array<int, Order>` kullanın. Yeni sınıfa gerek yok; yanlış tip geçen çağrı PHPStan tarafında hemen kırmızıya döner. En ucuz ve en hızlı kazanç budur.

2. **Davranış istediğinizde tek bir generic Collection yazın.** `->map()`, `->filter()`, "boş değil" veya "benzersiz" gibi invariant'lar istiyorsanız `@template`'li TEK bir `Collection<T>` sınıfı yazın — her tür için ayrı değil. Metotların dönüşü de tip taşıdığı için IDE ve PHPStan zinciri boyunca tipi korur. İskelet şöyle görünür:

   ```php
   /**
    * @template T of object
    */
   final class TypedCollection
   {
       /** @var list<T> */
       private array $items = [];

       /** @param T $item */
       public function add(object $item): void
       {
           $this->items[] = $item;
       }

       /** @return list<T> */
       public function all(): array
       {
           return $this->items;
       }
   }

   /** @var TypedCollection<Order> $orders */
   ```

3. **Concrete alt sınıfı yalnızca gerçek gerekçeyle açın.** `OrderCollection extends Collection` yazmayı ya domain okunabilirliği için ya da veri type-checked sınırınızın dışından (JSON, DB, kullanıcı girdisi) geldiğinde `add()` içinde `instanceof` ile runtime kontrol koymak için yapın. Gerekçesini net koyun.

4. **PHPStan'ı katı ve zorunlu koşun.** Level 10/max koşun (`checkGenericClassInNonGenericObjectType` ayarı PHPStan 2.0'da kaldırıldı, artık açmanıza gerek yok) ve PHPStan'ı CI'da zorunlu tutun. Generics ancak bu koşulda işe yarar.

**Sonuç:** Ben olsam varsayılan olarak `list<Order>` docblock'ları + katı PHPStan ile giderdim; `map/filter` gibi davranış gerektiğinde tek bir generic `Collection<T>` eklerdim; concrete alt sınıfları yalnızca güven sınırında, dışarıdan gelen veriyi runtime'da doğrulamak gerektiğinde yazardım. Böylece boilerplate'i minimumda tutar, güvenliğin çoğunu neredeyse bedavaya alırsınız. Unutmayın: bu güvenlik yalnızca PHPStan gerçekten çalıştığı sürece vardır; onu CI'da zorunlu tutmazsanız hiçbir annotation sizi korumaz.

## İlgili Yazılar

- [Eşzamanlı HTTP çağrıları için ham Fiber mı, ReactPHP/AMPHP mı, yoksa Swoole mu seçmeliyim?](https://muhammetsafak.com/tr/sor-bakalim/eszamanli-http-cagrilari-ham-fiber-reactphp-amphp-swoole/) — Sor Bakalım
- [readonly value object'lerimde "withX" tarzı kopyalama metotlarını nasıl temiz yazarım?](https://muhammetsafak.com/tr/sor-bakalim/readonly-value-objectlerimde-withx-tarzi-kopyalama-metotlarini-nasil-temiz-yazarim/) — 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
