# 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?

> PHP'de runtime generics yok; PHPStan generics tam da bunun için. Çoğu durumda list<Order> docblock'ları + katı PHPStan yeter. Davranış gerektiğinde tek bir generic Collection<T> yazın.

- Soruldu: 2026-07-26
- Yanıtlandı: 2026-07-31
- Soran: Ozan
- Etiketler: php, generics, static-analysis
- Kaynak: https://www.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. Sırf tip için her türe ayrı sınıf yazmak boşa boilerplate'tir.

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. **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.

3. **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.

4. **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. Runtime maliyeti ve boilerplate getirir; gerekçesini net koyun.

5. **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.

6. **Generics ancak PHPStan katı çalışıyorsa işe yarar.** Level 8/max koşun, `checkGenericClassInNonGenericObjectType`'ı açın ve PHPStan'i CI'da zorunlu tutun. Aksi halde annotation'lar çürür ve size sahte güven verir.

Tek generic koleksiyon iskeleti şö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 */
```

**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.
