İçeriğe geç
academia.sh

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; ?int int ya da null, int|string iki tipten biridir.
  • Tipsiz ekle($tutar) yöntemine int eklenince null veren kullanan float(0) yerine çıkış 255 aldı; bu dersin ölçü sınıfı kırandır.
  • Aynı değişiklik ?int ile yapılınca null hâ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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat