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.
-
PHP’de runtime generics yok — PHPStan generics tamamen statiktir.
@param list<Order> $orderssize 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. -
Tipli dizilerle başlayın. Koleksiyonları öylece dolaştırmak için imzalarda
list<Order>veyaarray<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. -
Davranış istediğinizde tek bir generic Collection yazın.
->map(),->filter(), “boş değil” veya “benzersiz” gibi invariant’lar istiyorsanız@template’li TEK birCollection<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. -
Concrete alt sınıfı yalnızca gerçek gerekçeyle açın.
OrderCollection extends Collectionyazmayı 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ğindeadd()içindeinstanceofile runtime kontrol koymak için yapın. Runtime maliyeti ve boilerplate getirir; gerekçesini net koyun. -
Sadece sınıflar değil, fonksiyonlar da generic olabilir. Tip taşıyan bir yardımcıya ihtiyacınız varsa (
first,mapgibi) sınıf yazmadan fonksiyonu@templateile generic yapabilirsiniz —@param list<T> $itemsalıp@return Tdöndüren bir fonksiyon, tipi çağıran tarafta korur. Bu, koleksiyon etrafındaki çoğu ihtiyacı sınıf açmadan karşılar. -
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.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.