Ders 07 / 12
Tip Bildirimleri
Parametre, dönüş ve özellik tipleri, ?int ve birleşim tipi ve bir parametreye tip eklemenin kullananı hangi girdilerde durdurduğu.
Yayın Güncelleme
Bu derste
- Parametre, dönüş ve özellik tipi bildirir; ?int ve birleşim tipi yazar
- Tipsiz bir parametreye int eklemenin null veren kullananı durdurduğunu ölçer
- Aynı eklemenin ?int ile ve sayısal metin gönderen kullanana ulaşmadığını gösterir
İçindekiler
Sihirli yöntemler adları gevşek biçimde çözüyordu; tip bildirimleri (type declaration) sınıfın sınırına bir kapı koyar. Soru, tipsiz bir parametreye int yazmanın kullananı hangi girdilerde durdurduğudur.
Tip Bildirimleri
Bir parametrenin, dönüş değerinin ya da özelliğin adından önce yazılan tip, PHP’nin o noktada kabul edeceği değerleri sınırlar. ?int ya int ya da null demektir. int|string birleşim tipidir (union type): iki tipten birini kabul eder.
<?php // Kdv.php — parametre, donus, ?int ve birlesim tipi class Kdv { public static function ekle(int $tutar): float { return $tutar * 1.2; } public static function yaz(?int $adet): string { return $adet === null ? 'yok' : 'var'; } public static function tur(int|string $kod): string { return gettype($kod); } }
<?php // kdv_kullan.php — tipe uyan girdiler gecer, uymayan TypeError verir require 'Kdv.php'; var_dump(Kdv::ekle(100)); echo Kdv::yaz(null), ' ', Kdv::yaz(7), "\n"; echo Kdv::tur(5), ' ', Kdv::tur('a'), "\n"; try { Kdv::ekle('abc'); } catch (TypeError $e) { echo get_class($e), "\n"; }
float(120) yok var integer string TypeError
NM25. ekle() yalnız int aldı ve 'abc' gelince TypeError fırlattı; yaz() null’ı kabul etti çünkü tipi ?int’ti. Bu davranışın ayrıntısı, yani TypeError, E_DEPRECATED ve strict_types, M12/K01 Değişkenler dersinde ölçülmüştü ve burada yinelenmez. Bu ders bir tip bildiriminin eklenmesinin kullanana ulaşıp ulaşmadığını soruyor.
Dönüş Tipleri: void ve never
void, yöntemin bir değer döndürmediğini söyler; çağrının sonucu her zaman null’dır ve bir değere bağlanması anlamsızdır. never daha güçlü bir sözdür: yöntem hiç dönmez; ya bir istisna fırlatır, ya süreci sonlandırır ya da sonsuza dek sürer. never bildirilmiş bir yöntem sessizce sona ererse PHP bunu TypeError ile bildirir.
<?php // Akis.php — void ve never donus tipleri class Akis { public static function yaz(string $mesaj): void { echo $mesaj, "\n"; } public static function dur(string $sebep): never { throw new RuntimeException($sebep); } public static function bozukDur(): never { echo "ic\n"; } } var_dump(Akis::yaz('a')); try { Akis::dur('x'); } catch (RuntimeException $e) { echo get_class($e), ' ', $e->getMessage(), "\n"; } try { Akis::bozukDur(); } catch (TypeError $e) { echo get_class($e), "\n"; }
a NULL RuntimeException x ic TypeError
yaz() bir şey yazdı ve çağrının değeri null çıktı. dur() hiç dönmedi, istisna yakalandı. bozukDur() gövdesini çalıştırdı ama sonunda dönmeye kalkınca TypeError aldı; never sözünü tutmayan kod tipin kendisi tarafından yakalanır.
Özellik Tipi ve Başlatılmamış Özellik
Özellik tipi, özelliğe yazılan her değeri denetler. Varsayılan değeri olmayan tipli bir özellik başlatılmamış durumdadır; bu, null olmakla aynı şey değildir. Başlatılmamış özelliği okumak Error doğurur. null kabul eden tipli özellikte ise varsayılan değer yazılırsa okuma null verir.
<?php // Profil.php — ozellik tipi ve baslatilmamis ozellik class Profil { public int $yas; public ?string $ad = null; } $profil = new Profil(); var_dump($profil->ad); try { echo $profil->yas; } catch (Error $e) { echo get_class($e), "\n"; } try { $profil->yas = 'abc'; } catch (TypeError $e) { echo get_class($e), "\n"; } $profil->yas = '42'; var_dump($profil->yas);
NULL Error TypeError int(42)
ad varsayılan olarak null taşıdığı için okunabildi; yas ise hiç yazılmadığı için Error verdi. 'abc' sayıya çevrilemediğinden TypeError doğdu, '42' ise kipsiz dosyada int’e zorlandı. Özellik tipi bu yüzden iki ayrı sözdür: yazılan değerin tipi ve yazılmadan okunamayacağı.
mixed ve Birleşim Tipinde Zorlama
mixed her değeri kabul eder; null dahil. Tip yazmamakla farkı, tipin bilerek açık bırakıldığını söylemesidir. Birleşim tipinde ise, değer üyelerden hiçbirine tam uymuyorsa PHP kipsiz dosyada bir zorlama dener ve hangi üyeye çevireceğini kurala göre seçer: tam sayı olarak okunabilen bir metin int, ondalıklı bir metin float olur.
<?php // Esle.php — mixed ve int|float birlesim tipinde zorlama class Esle { public static function herhangi(mixed $deger): string { return gettype($deger); } public static function sayi(int|float $sayi): string { return gettype($sayi); } } echo Esle::herhangi(null), ' ', Esle::herhangi([1]), "\n"; echo Esle::sayi('1.5'), ' ', Esle::sayi('7'), ' ', Esle::sayi(true), "\n";
NULL array double integer integer
mixed hiçbir değeri reddetmedi. int|float ise '1.5' metnini float’a (gettype bunu double diye yazar), '7' metnini ve true değerini int’e çevirdi. Birleşim tipi bir girdinin tipini korumaz, kabul ettiği üyelerden birine çevirebilir; bu yüzden tipi sınırlayan kişi, kabul edilen çevirilerin neler olduğunu da sınırlamış olur.
static Dönüş Tipi
Bir yöntemin dönüş tipi static ise, yöntemin hangi sınıf üzerinden çağrıldıysa o sınıfın nesnesini döndüreceğini söyler. self bunun aksine, yöntemin yazıldığı sınıfı gösterir. Zincirleme çağrılan yöntemler static kullanırsa alt sınıfın nesnesi zincir boyunca alt sınıf olarak kalır.
<?php // Sorgu.php — static donus tipi zincirde alt sinifi korur class Sorgu { private array $parca = []; public function nerede(string $kosul): static { $kopya = clone $this; $kopya->parca[] = $kosul; return $kopya; } public function say(): int { return count($this->parca); } } class OzelSorgu extends Sorgu { } $sorgu = (new OzelSorgu())->nerede('a')->nerede('b'); echo get_class($sorgu), ' ', $sorgu->say(), "\n";
OzelSorgu 2
nerede() Sorgu sınıfında yazılmış olsa da OzelSorgu üzerinden çağrıldığı için dönen nesne OzelSorgu oldu. Dönüş tipi self yazılmış olsaydı tip Sorgu olarak okunur ve zincirin ucundaki nesnenin alt sınıf olduğu tip düzeyinde kaybolurdu. Bu fark, bir sınıfı sonradan genişletecek kodlar için önemlidir: alt sınıfın kendi yöntemlerini zincirin sonunda çağırabilmek, dönüş tipinin alt sınıfı koruyup korumadığına bağlıdır.
Kalıtımda Tip Uyumu
Alt sınıf bir yöntemi yeniden tanımladığında tipleri üst sınıfla uyumlu olmak zorundadır. Kural şudur: dönüş tipi daraltılabilir (üst sınıf iterable döndürüyorsa alt sınıf array döndürebilir, çünkü her array bir iterable’dır), parametre tipi ise genişletilebilir ama daraltılamaz. Üst sınıf kodunun beklediği garantiler alt sınıfta zayıflamamalıdır.
<?php // Kaynak.php — donus tipini daraltmak serbesttir class Kaynak { public function ver(): iterable { return [1]; } } class Daraltan extends Kaynak { public function ver(): array { return [1, 2]; } } echo count((new Daraltan())->ver()), "\n";
2
Tersini deneyen kod sınıf bildirimi aşamasında durur.
# uyumsuz.sh — donus tipini genisletmek ve parametre tipini daraltmak bildirilemez yapar cat > Genis.php <<'PHP' <?php class Taban { public function ver(): array { return []; } } class Alt extends Taban { public function ver(): iterable { return []; } } PHP cat > Dar.php <<'PHP' <?php class Taban { public function al(int|string $girdi): void {} } class Alt extends Taban { public function al(int $girdi): void {} } PHP php -d display_errors=0 Genis.php; echo "genis cikis=$?" php -d display_errors=0 Dar.php; echo "dar cikis=$?"
genis cikis=255 dar cikis=255
İkisi de aynı ilkeye çarptı. Taban tipli bir değişkene konan her nesne, ver() çağrıldığında array döndüreceğine söz verir; iterable döndüren bir alt sınıf bu sözü bozar. Benzer biçimde al() hem int hem string kabul ediyorsa, yalnız int kabul eden alt sınıf, üst sınıf tipiyle yazılmış çağrıyı reddedebilir.
Ölçüm Çekirdeği
Bu dersin ölçümü de yayilim.php dosyasını kullanır; çekirdek ilk derste satır satır açıklanmıştı.
<?php // yayilim.php — bir degisikligin nereye ulastigini olcen yardimcilar function yaz(string $dizin, array $dosyalar): void { foreach ($dosyalar as $ad => $icerik) { if ($icerik === null) { continue; // null: dosya silindi } $yol = "$dizin/$ad"; if (!is_dir(dirname($yol))) { mkdir(dirname($yol), 0777, true); } file_put_contents($yol, $icerik); } } function oku(string $dizin): array { $dosyalar = []; $gezgin = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dizin, FilesystemIterator::SKIP_DOTS) ); foreach ($gezgin as $dosya) { $ad = substr($dosya->getPathname(), strlen($dizin) + 1); $dosyalar[$ad] = file_get_contents($dosya->getPathname()); } return $dosyalar; } function kostur(string $dizin): array { $komut = 'cd ' . escapeshellarg($dizin) . ' && php -d display_errors=0 main.php 2>/dev/null'; exec($komut, $satirlar, $kod); return [$kod, implode("\n", $satirlar)]; } function olc(string $ad, array $taban, array $degisiklik, array $baglanti = []): string { if (array_key_exists('main.php', $degisiklik)) { throw new LogicException("$ad: kullanan duzenlenmez"); } yaz("olcum/$ad/once", $taban); yaz("olcum/$ad/sonra", array_merge($taban, $degisiklik)); [$kodOnce, $ciktiOnce] = kostur("olcum/$ad/once"); [$kodSonra, $ciktiSonra] = kostur("olcum/$ad/sonra"); if ($kodOnce !== 0) { throw new LogicException("$ad: taban calismadi"); } $sinif = match (true) { $kodSonra !== 0 => 'kiran', $ciktiOnce !== $ciktiSonra => 'ortuk', array_intersect(array_keys($degisiklik), $baglanti) !== [] => 'baglantida', default => 'yerinde', }; printf( "%-18s %d dosya cikis=%-3d cikti %-6s -> %s\n", $ad, count($degisiklik), $kodSonra, $ciktiOnce === $ciktiSonra ? 'ayni' : 'farkli', $sinif ); return $sinif; }
Parametreye Tip Eklenir
Ölçülen değişiklik, tipsiz ekle($tutar) yöntemini ekle(int $tutar): float yapmaktır. Kullanan main.php değişkeni null ile bırakıp Kdv::ekle($tutar) çağırır ve sonucu var_dump ile yazdırır.
<?php // parametre_tipi.php — tipsiz parametreye int eklenir, kullanan null veriyor require_once 'yayilim.php'; olc('parametre-tipi', [ 'kdv.php' => <<<'PHP' <?php class Kdv { public static function ekle($tutar) { return $tutar * 1.2; } } PHP, 'main.php' => <<<'PHP' <?php require 'kdv.php'; $tutar = null; var_dump(Kdv::ekle($tutar)); PHP, ], [ 'kdv.php' => <<<'PHP' <?php class Kdv { public static function ekle(int $tutar): float { return $tutar * 1.2; } } PHP, ]); foreach (['once', 'sonra'] as $asama) { [$kod, $cikti] = kostur("olcum/parametre-tipi/$asama"); echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n"; }
parametre-tipi 1 dosya cikis=255 cikti farkli -> kiran once cikis=0 cikti=float(0) sonra cikis=255 cikti=
NM26. Önce null * 1.2 sessizce float(0) verdi, tip eklenince null bir int’e dönüşemediği için TypeError doğdu ve çıkış 255 oldu. Hata, kullanan dosya değişmeden, yürütme sırasında duyuldu. Dersin birinci ölçü sınıfı kırandır.
Sınırlayıcı Ölçüm
Kırılma tip bildirimi eklemekten mi geliyor, yoksa eklenen tipin null’ı reddetmesinden mi? Aynı değişikliği ?int ile yapmak ikincisini ayırır.
<?php // bos_izinli.php — ayni degisiklik, bu kez ?int ile require_once 'yayilim.php'; olc('bos-izinli', [ 'kdv.php' => <<<'PHP' <?php class Kdv { public static function ekle($tutar) { return $tutar * 1.2; } } PHP, 'main.php' => <<<'PHP' <?php require 'kdv.php'; $tutar = null; var_dump(Kdv::ekle($tutar)); PHP, ], [ 'kdv.php' => <<<'PHP' <?php class Kdv { public static function ekle(?int $tutar): float { return $tutar * 1.2; } } PHP, ]); foreach (['once', 'sonra'] as $asama) { [$kod, $cikti] = kostur("olcum/bos-izinli/$asama"); echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n"; }
bos-izinli 1 dosya cikis=0 cikti ayni -> yerinde once cikis=0 cikti=float(0) sonra cikis=0 cikti=float(0)
NM27. ?int ile null hâlâ geçti ve float(0) aynı kaldı: tip bildirimi yerinde bir değişiklik olabildi. Kıran, tipin null’ı reddetmesiydi. Bir başka girdi türü sayısal metindir: strict_types kullanılmayan bir dosyada '5' gibi sayısal metin bir int parametreye zorlanır.
<?php // sayisal_metin.php — kullanan '5' veriyor, dosyasi strict_types kullanmiyor require_once 'yayilim.php'; olc('sayisal-metin', [ 'kdv.php' => <<<'PHP' <?php class Kdv { public static function ekle($tutar) { return $tutar * 1.2; } } PHP, 'main.php' => <<<'PHP' <?php require 'kdv.php'; var_dump(Kdv::ekle('5')); PHP, ], [ 'kdv.php' => <<<'PHP' <?php class Kdv { public static function ekle(int $tutar): float { return $tutar * 1.2; } } PHP, ]); foreach (['once', 'sonra'] as $asama) { [$kod, $cikti] = kostur("olcum/sayisal-metin/$asama"); echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n"; }
sayisal-metin 1 dosya cikis=0 cikti ayni -> yerinde once cikis=0 cikti=float(6) sonra cikis=0 cikti=float(6)
NM28. '5' zorlamayla 5 oldu ve sonuç önce de sonra da float(6) kaldı: kullanan yerinde. Yani tip eklemek kullananı yalnız tipin reddettiği girdilerde durdurur; kabul ettiği ya da zorlayabildiği girdilerde ulaşmaz. Bu kursta ölçülen kıranlar bildirimde de çağrıda da duyuldu ve hangi aşamada duyuldukları M11/K01’in konusudur; kurs aşamayı değil değişikliğin kullanana ulaşıp ulaşmadığını ölçer.
Özet
- Parametre, dönüş ve özellik tipi bir değerin kabul edildiği noktayı sınırlar;
?intintya danull,int|stringiki tipten biridir. - Tipsiz
ekle($tutar)yöntemineinteklenincenullveren kullananfloat(0)yerine çıkış 255 aldı; bu dersin ölçü sınıfı kırandır. - Aynı değişiklik
?intile yapılıncanullhâlâ geçti ve çıktıfloat(0)kaldı: yerinde. - Kullanan
'5'veriyorsa ve dosyası kipsizse tip eklemek zorlama nedeniyle yerinde kaldı, çıktıfloat(6). - Tip eklemenin kullanana ulaşması tipin reddettiği girdilere bağlıdır.
Sonraki Adım
Sınıflar, arayüzler, trait’ler, sabitler, sihirli yöntemler ve tipler tek dosyadan çok dosyaya yayıldıkça adların çakışması kaçınılmaz olur. Sıradaki ders Ad Alanları, use ile içe aktarmanın ve ad alanı değişikliğinin kullananı nasıl etkilediğini soruyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.