PHP’de Türler Üzerine Uzun Ders: Union, Intersection, Nullable ile mixed, never, static, callable
Kahvenizi alın; bugün PHP’nin modern tür sisteminde derin bir tura çıkıyoruz. Hedefimiz: union/intersection/nullable türler ile mixed, never, static ve callable’ı anlaşılır örneklerle oturtmak. Yazının sonunda hem neden varlar hem de nerede nasıl kullanılırlar, net olacak.
Hızlı sürüm haritası (özet)
- PHP 7.1: Nullable türler (?T)
- PHP 7.4: Tipli özellikler (typed properties)
- PHP 8.0: Union türler (A|B), mixed, static (dönüş türü)
- PHP 8.1: Intersection türler (A&B), never (dönüş türü), first-class callable syntax
- PHP 8.2: DNF (Disjunctive Normal Form) türleri, true türü vb.
- Union türler (A|B)
Bir değişkenin birden fazla farklı türde olabileceğini ifade eder. Söz dizimi: T1|T2|…|Tn.
Temel kurallar
- Yinelemeye gerek yok: int|int yazmak hatadır.
- Üstyin türler hataya yol açar: array|iterable geçersizdir; iterable zaten array’i kapsar.
- void ve never bir union’ın parçası olamaz.
- null bir union’a dahil edilebilir: int|string|null gibi.
- Kısaltma: ?T, T|null demektir; ama sadece tek bir T için geçerlidir. int|string|null yazmalısınız; ?(int|string) diye bir şey yoktur.
- false bir literal tür olarak union’da kullanılabilir (özellikle eski API’lerle uyum için). true türü de desteklenir (güncel 8.x).
Örnekler
<?php
function parseId(int|string $id): int {
return is_int($id) ? $id : (int) $id;
}
function loadUser(int|string $id): User|null {
// Bulunamazsa null dönebilir
// ...
return null;
}
// Eski tarz "false veya değer" kalıbını type-safe yapmak:
function findInCache(string $key): string|false {
// bulamazsa false
return false;
}
İyi pratikler
- API sınırlarınızda union kullanmak netlik sağlar: getirme işlemi başarısızsa null, başarıda User gibi.
- Mümkünse bool yerine özel durumlar için özel sınıflar veya Exceptions tercih edin; ama mevcut ekosistemle uyumda false/true niteli union’lar pratik olabilir.
- Nullable türler (?T)
?T, T|null anlamına gelir. Parametre, dönüş değeri ve özelliklerde kullanılabilir.
Kurallar ve örnekler
- ?T sadece tek bir T için kısayoldur. Birden fazla tür varsa açık yazın: A|B|null.
- ?void ve ?never geçersizdir.
- Varsayılan değer ile uyum: ?string name=nulluygundur;stringname = null hatadır.
<?php
function greet(?string $name = null): string {
return $name ? "Merhaba, $name" : "Merhaba!";
}
// Null-safe operator ile güzel bir ikili
$userNameLength = $user?->getProfile()?->getName()?->length();
Ne zaman kullanmalı?
- Parametre gerçekten isteğe bağlıysa veya “değer yok” anlamlı bir durumsa (?T seçin).
- Aksi halde “boş değeri” alan türle ifade etmek daha iyi olabilir (ör. boş dizi, boş nesne, Null Object deseni).
- Intersection türler (A&B)
Intersection, verilen değerin aynı anda birden fazla türü sağlamasını ister. Özellikle arayüz kombinasyonlarını modellemek için idealdir.
Kurallar
- Yalnızca sınıf/arayüz türleriyle anlamlıdır; int&string gibi kesişimler geçersizdir.
- Birden fazla arayüzü birleştirebilirsiniz: Traversable&Countable.
- En fazla bir adet somut sınıf olabilir; iki farklı sınıfın kesişimi anlamsızdır.
- null/false/mixed gibi özel türler intersection’da kullanılamaz.
- Union ile birlikte (8.2+): DNF türleriyle (A&B)|C gibi ifadeler yazabilirsiniz; fakat A&(B|C) yazmanız gerekirse DNF’e açın: (A&B)|(A&C).
Örnekler
<?php
function processCollection(Traversable&Countable $data): void {
if (count($data) === 0) return;
foreach ($data as $item) { /* ... */ }
}
// DNF tür örneği (8.2+):
// Ya hem IteratorAggregate hem Countable, ya da doğrudan Iterator
function iterate((IteratorAggregate&Countable)|Iterator $it): void {
$iterator = $it instanceof Iterator ? $it : $it->getIterator();
foreach ($iterator as $v) { /* ... */ }
}
Ne zaman kullanmalı?
- Bir tüketici fonksiyonun belirli protokolleri aynı anda beklediği durumlarda (ör. hem gezilebilirlik hem sayılabilirlik).
- Aksi halde tür kontrolü kodun her yerine dağılır.
- mixed
mixed “her şey olabilir” demektir. Kabul ettiği değerler: array, bool, callable, int, float, null, object, string ve hatta kaynaklar (resource) dahil tüm değerler. Yani türsel olarak tepedeki “en geniş” türdür.
Kurallar
- mixed zaten null’ü içerir; ?mixed, mixed|null gibi yazımlar geçersiz veya gereksizdir.
- mixed bir union’ın parçası olamaz (zaten her şeyi kapsadığı için).
- Parametre, dönüş ve özellik tipi olarak kullanılabilir.
Ne zaman kullanmalı?
- Kesin tür çıkarımı yapamayacağınız sınır yüzeyleri: JSON decode sonuçları, kullanıcı girdisi, dinamik veri.
- İçeride mümkün olduğunca hızlı daraltın: is_array, is_string, instanceof vs. ile.
Örnek
<?php
function fromJson(string $json): mixed {
return json_decode($json, true);
}
function head(mixed $value): mixed {
return is_array($value) ? ($value[0] ?? null) : null;
}
- never
never, fonksiyonun asla çağırana dönmeyeceğini garanti eder. Bu fonksiyonlar ya her zaman istisna fırlatır ya da çıkış yapar (exit/die) veya sonsuz döngüde kalır. never bir “alt türdür” (bottom type).
Kurallar
- Sadece dönüş türü olarak kullanılabilir.
- Bir never fonksiyonunda return; dahi kullanamazsınız; aksi hâlde fatal hata.
- Union/intersection içinde kullanılamaz.
void vs never
- void: Fonksiyon bir şey döndürmez ama normal akışla çağırana döner.
- never: Fonksiyon çağırana dönmez (program akışı biter/atılır).
Örnekler
<?php
function redirect(string $url): never {
header("Location: $url");
exit; // asla geri dönmez
}
function fail(string $message): never {
throw new RuntimeException($message);
}
Ne zaman kullanmalı?
- Hata fırlatan yardımcılar, CLI komutlarında exit eden noktalar, web’de yönlendirme yardımcıları.
- Statik analiz ve IDE’ler, never sayesinde sonraki kodu “ulaşılamaz” olarak bilir.
- static
static türü “geç bağlanan (late static binding) sınıf türünü” ifade eder: Çağrılan sınıfın gerçek türü. self ile farkı, self bildirim yapılan sınıfı sabitlerken static alt sınıfı da yakalayabilmesidir.
Özellikler
- Dönüş türü olarak (8.0+) çok faydalıdır: Fluent API’ler, named constructor’lar, klonlama/immutability kalıpları.
- self vs parent vs static:
- self: Bildiren sınıfın kendisi.
- parent: Üst sınıf.
- static: Çağrıyı yapan geç bağlanan sınıf (geç statik bağlama).
- Güncel PHP sürümlerinde static, dönüşün yanı sıra parametre/özellik türü olarak da desteklenir (8.x). Farklı sunucularda farklı PHP sürümleri olabileceği için bu kullanımları yapmadan önce minimum sürümü doğrulayın.
Örnekler
class Euro extends Money {}
$e = Euro::fromInt(100); // Euro döner (static sayesinde)
<?php
class Money {
public function add(self $other): static {
// Hesaplama...
return new static(); // alt sınıf döndürülebilir
}
public static function fromInt(int $amount): static {
// alt sınıflar kendi örneğini döndürür
return new static();
}
}
Ne zaman kullanmalı?
- Miras hiyerarşisinde “aynı sınıf türünde dön” garantisi istediğinizde.
- Builder/fluent zincirlerde alt sınıf türünü korumak istediğinizde.
- callable
callable, herhangi bir “çağrılabilir” şeyi ifade eder:
- Bir Closure (anonim fonksiyon)
- Bir fonksiyon ismi (string): ‘strlen’
- Statik yöntem: [‘ClassName’, ‘method’]
- Nesne yöntemi: [$obj, ‘method’]
- __invoke tanımlı bir nesne
Kullanım
- Parametre, dönüş ve özellik tipi olarak kullanılabilir.
- Bir callable değeri doğrudan çağırabilirsiniz: $cb(…).
- Closure türü, callable’ın alt kümesidir (Closure ⊂ callable).
Örnekler
<?php
function map(array $xs, callable $f): array {
$out = [];
foreach ($xs as $x) {
$out[] = $f($x);
}
return $out;
}
$uppercase = fn(string $s): string => strtoupper($s);
$result = map(['a', 'b'], $uppercase);
// Dizi biçiminde callable:
class Greeter {
public function hello(string $name): string { return "Hi $name"; }
}
$g = new Greeter();
$cb = [$g, 'hello'];
echo $cb('Alice');
// İlk sınıf callable sözdizimi (8.1+):
$len = strlen(...); // Closure döner
echo $len('hello'); // 5
İmza belirtme meselesi
- Dil düzeyinde callable(int): string gibi bir imza yazamazsınız. Bunu PHPDoc ile ifade edin ve statik analiz (PHPStan/Psalm) kullanın.
- Eğer imza sabitse, callable yerine Closure kullanıp Closure’ın parametre ve dönüş türlerini işlevin içinde belirtmek genelde daha sağlamdır.
- Birlikte kullanım ve sık düşülen tuzaklar
Union + nullable
- Sadece bir tür varsa ?T kullanın; birden fazlaysa açık yazın:
- Doğru: ?Foo
- Doğru: Foo|Bar|null
- Yanlış: ?(Foo|Bar)
Redundans hataları
- array|iterable, Traversable|iterable gibi türler geçersizdir (iterable zaten kapsar).
- mixed ile başka türleri birleştirmeyin; mixed her şeyi kapsar.
- void ve never union/intersection içinde kullanılamaz.
Intersection pratikleri
- Genellikle arayüzlerle kullanın: Traversable&Countable yaygın bir örnek.
- Union’la birlikte gerekiyorsa DNF’e dönüştürün: A&(B|C) yerine (A&B)|(A&C).
static ile miras
- Dönüş türünüz static ise alt sınıflarınız doğru türle dönmeye devam eder; self yazsaydınız, alt sınıf için dönüş türü yanlış sabitlenebilirdi.
callable doğrulama
- Kullanıcıdan gelen veriyi callable bekliyorsanız is_callable ile doğrulayın.
- İmzayı korumak için Closure terchi often more predictable.
nullable ve varsayılan
- ?T tipli parametreye varsayılan null vermeyi unutmayın, yoksa çağıranın her zaman bir değer geçmesi gerekir:
phpDownloadCopy code Wrapfunction foo(?string $x = null) { /* ... */ }
- Kısa bir “hangi durumda hangisi” rehberi
- union (A|B): “Şunlardan biri” diyorsanız.
- intersection (A&B): “Aynı anda şu özelliklerin hepsi” diyorsanız.
- ?T: “Evet, null gelebilir” diyorsanız.
- mixed: “Her şey olabilir, içerde daraltacağım” diyorsanız.
- never: “Bu fonksiyon asla geri dönmez” diyorsanız.
- static: “Çağrılan gerçek sınıf türüyle dön/çalış” diyorsanız.
- callable: “Bana çalıştırılabilir bir şey ver” diyorsanız.
- Daha kapsamlı örnek: Hepsinden biraz
<?php
interface Normalizer {
public function normalize(mixed $value): string;
}
final class StringNormalizer implements Normalizer {
public function normalize(mixed $value): string {
return is_string($value) ? trim($value) : json_encode($value);
}
}
/**
* $source:
* - Traversable&Countable: tek geçişlik, sayılabilir koleksiyon
* - veya array
*/
function pipeline(
(Traversable&Countable)|array $source,
callable $transform, // her elemanı dönüştüren callable
?Normalizer $normalizer = null // opsiyonel
): array {
$norm = $normalizer ?? new StringNormalizer();
$iter = is_array($source) ? new ArrayIterator($source) : $source;
$out = [];
foreach ($iter as $item) {
$out[] = $norm->normalize($transform($item));
}
return $out;
}
// Kullanım
$data = [1, 2, 3];
$result = pipeline($data, fn(int $x): int => $x * 10);
// ["10","20","30"]
Son söz
PHP’nin modern tür sistemi, niyeti kodun içine taşıyıp hataları erkenden yakalamak için güçlü araçlar sunuyor. Union ve intersection ile ifade gücünüz artıyor; nullable ile “değer yok” durumunu açık ediyorsunuz; mixed ve callable ile esnekliği yönetiyor; static ile miras hiyerarşilerinde tür güvenliğini koruyor; never ile akış garantisi veriyorsunuz. Küçük adımlarla başlayın: önce en dış API’leri tipleyin, ardından içeride türleri giderek daraltın. Kodunuz hem daha okunaklı olacak hem de daha sağlam.

