İçeriğe geç
Muhammet Şafak
en
Soran: Ozan Cevaplandı:

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?


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?

Cevap

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:

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

Etiketler: #php#generics#static-analysis
Paylaş:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi