İçeriğe geç
academia.sh

Ders 01 / 12

Sınıflar ve Nesneler

Bir sınıfın içindeki ad değiştiğinde onu kullanan kodun ne yaşadığı: özel özellik, açık özellik ve public özelliğe yazan kullanan; bir değişikliğin nereye ulaştığını ölçen dört sınıf.

Yayın Güncelleme

Bu derste

  • Bir sınıfın özellik, yöntem ve yapıcı biçimlerini ve nesnelerin ayrı durum taşıdığını gösterir
  • Bir tanım değişikliğinin kullananlara ulaşıp ulaşmadığını önce ve sonra koşumuyla sınıflandırır
  • Özel özelliğin adının değişmesinin kullananı etkilemediğini, açık özelliğin değişmesinin etkilediğini ölçer
  • Public özelliğe yazan kullananın ad değişince durmadığını, yalnız bir E_DEPRECATED bildirdiğini doğrular
İçindekiler

Önceki kurs PHP’nin dilini tek bir dosya içinde ölçtü: bir değerin tipi ne zaman değişir, bir hata hangi kanaldan duyulur. Bu kurs dosyanın dışına çıkıyor. Gerçek bir programda tanımlar bir dosyada, onları kullanan kod başka dosyalarda durur ve tanım bir gün değişir. Soru şudur: bu değişiklik nereye ulaşıyor? Kurs bu soruyu her düzenek için aynı yöntemle sorar ve yanıtı dört addan biriyle verir.

Yerinde: değişiklik tanımın içinde kaldı, kullananlar düzenlenmeden aynı sonucu verdi. Bağlantıda: değişiklik tek bir bağlantı noktasında karşılandı, bir yükleyicide ya da bir kurulum dosyasında. Kıran: değişiklik kullananlara ulaştı ve onları durdurdu, çıkış kodu 0 değil. Örtük: değişiklik kullananlara ulaştı, onlar çalıştı ama sonuç değişti. Kıran ve örtük sınıflar M12/K01’in sinyal ayrımından farklıdır; orada bir aksaklığın hangi kanaldan duyulduğu ölçülmüştü, burada iki koşumun sonucu karşılaştırılacak. M11/K03’te ölçülen de başkaydı: orada bir verinin değişikliği, burada bir tanımın değişikliği söz konusu. Kurs boyunca on iki değişiklik ölçülecek.

Sınıf, Özellik ve Yöntem

Sınıf (class), nesnelerin kalıbıdır. İçinde özellikler (properties) ve yöntemler (methods) bulunur. Özellik nesnenin taşıdığı veridir, yöntem nesnenin yapabildiği iştir. new ile sınıftan bir nesne, yani bir örnek (instance) kurulur ve her örnek kendi özellik değerlerini taşır. Bir yöntemin içinden kendi nesnesine $this ile ulaşılır. Aşağıdaki sınıf, bu kursun dosya adı kuralını da gösterir: sınıf dosyasının adı sınıfın adıyla aynıdır, sınıflar StudlyCaps, yöntem ve özellikler camelCase yazılır.

<?php
// Hesap.php — ozellik, yontem ve yapici tasiyan bir sinif
class Hesap
{
    public function __construct(private int $bakiye = 0) {}

    public function yatir(int $tutar): void
    {
        $this->bakiye += $tutar;
    }

    public function bakiye(): int
    {
        return $this->bakiye;
    }
}

__construct yapıcı işlevdir (constructor): new çağrısında çalışır. Parametrenin önündeki private sözcüğü parametreyi aynı anda özellik de yapar; ayrı bir özellik bildirimi ve atama satırı gerekmez. private bir görünürlüktür (visibility): özelliğe yalnız sınıfın kendi yöntemleri ulaşabilir. Bu kapsülleme (encapsulation) demektir. Sınıf dışarıya veriyi değil, yöntemleri açar.

<?php
// hesap_kullan.php — iki nesne iki ayri durum tasir; ozel ozellige disaridan erisilemez
require 'Hesap.php';

$birinci = new Hesap(10);
$ikinci = new Hesap();
$birinci->yatir(50);
$ikinci->yatir(5);
echo $birinci->bakiye(), ' ', $ikinci->bakiye(), "\n";

try {
    echo $birinci->bakiye;
} catch (Error $hata) {
    echo get_class($hata), "\n";
}
60 5
Error

NM1. İki nesne aynı sınıftan kuruldu ve yatırılan tutarlar birbirine karışmadı: 60 ve 5. Özel özelliğe yöntem olmadan ulaşan satır Error fırlattı. Kullanan koda açılan tek yüzey yöntemlerdir; özelliğin adı bu yüzeyin parçası değildir. Bu gözlem, aşağıdaki ölçümün neden yerinde çıkacağının gerekçesidir.

Yapıcıda Özellik, readonly ve Üç Görünürlük

Yapıcı parametresinin önüne görünürlük yazmak (constructor promotion), uzun biçimin kısaltmasıdır: özellik bildirimi ve $this->bakiye = $bakiye ataması kendiliğinden yapılır. İki biçim aynı nesneyi üretir.

<?php
// Esdeger.php — uzun bicim ve kisa bicim ayni nesneyi kurar
class Uzun
{
    private int $bakiye;

    public function __construct(int $bakiye = 0)
    {
        $this->bakiye = $bakiye;
    }

    public function bakiye(): int
    {
        return $this->bakiye;
    }
}

class Kisa
{
    public function __construct(private int $bakiye = 0) {}

    public function bakiye(): int
    {
        return $this->bakiye;
    }
}

var_dump((new Uzun(7))->bakiye() === (new Kisa(7))->bakiye());
bool(true)

Bir özellik kurulduktan sonra değişmeyecekse readonly yazılır: değer yapıcıda bir kez atanır, sonra hiçbir yerden değiştirilemez. Okuma açık kalır, yazma Error doğurur.

<?php
// Sabit.php — readonly ozellik bir kez atanir
class Sabit
{
    public function __construct(public readonly int $no) {}
}

$sabit = new Sabit(4);
echo $sabit->no, "\n";

try {
    $sabit->no = 5;
} catch (Error $e) {
    echo get_class($e), "\n";
}
4
Error

Görünürlük üç düzeydir. public her yerden, protected sınıfın ve alt sınıfların içinden, private yalnız tanımlandığı sınıfın içinden erişilir. Aşağıdaki sınıfta üçü de içeriden okunuyor, dışarıdan ise yalnız public okunabiliyor.

<?php
// Gorunurluk.php — uc gorunurluk iceriden ve disaridan
class Ust
{
    public int $a = 1;
    protected int $b = 2;
    private int $c = 3;

    public function oku(): string
    {
        return $this->a . $this->b . $this->c;
    }
}

class Alt extends Ust
{
    public function bak(): string
    {
        return $this->a . $this->b;
    }
}

$nesne = new Alt();
echo $nesne->oku(), ' ', $nesne->bak(), "\n";

$ust = new Ust();
foreach (['a', 'b', 'c'] as $ad) {
    try {
        echo $ust->$ad, ' ';
    } catch (Error $e) {
        echo get_class($e), ' ';
    }
}
echo "\n";
123 12
1 Error Error 

Alt kendi içinden protected olan b’yi okudu, private olan c’ye ise erişemedi; bu yüzden bak() yalnız a ve b’yi birleştirdi. Bir Ust nesnesine dışarıdan bakınca yalnız a okundu. Özelliğin görünürlüğü, o özelliğe bağlı kodun kimlerden oluşabileceğini belirler.

Ölçüm Çekirdeği

Bir değişikliğin nereye ulaştığı iki koşumla ölçülür: tanımın eski hâliyle ve yeni hâliyle. Kurs bu işi yayilim.php dosyasına bırakır. Dosya her derste aynı kalır ve bir kez kurulur.

<?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;
}

Dosyayı yukarıdan aşağı okumak yeter. yaz() bir dizinin altına dosyaları yazar. Anahtar dosya adıdır, değer içeriğidir; değeri null olan dosya “silindi” anlamına gelir ve yazılmaz, ilerideki derslerde bir dosyanın kaldırıldığı değişiklikler bu işaretle anlatılacak. Alt dizin gerekiyorsa mkdir onu açar. oku() bir dizindeki dosyaları ad ve içerik çiftleri olarak geri verir; bu derste çağrılmaz, ama dosya kursun ortak çekirdeği olduğu için eksiksiz verildi.

kostur() bir dizine girer ve orada main.php dosyasını php ile çalıştırır. İki ayar ölçümün temelidir: display_errors kapatılır ve standart hata atılır. Böylece bir uyarı ya da hata iletisi çıktıya karışmaz; ölçülen şey iletinin metni değil, çıkış kodudur. Dönüş değeri çıkış kodu ile çıktının bir çiftidir.

olc() asıl işi yapar ve dört parametre alır: gözlemin adı, taban dosyalar, değişiklik dosyaları ve bir bağlantı listesi. Liste boş verilebilir; bu dersin gözlemlerinde boştur. Fonksiyon önce kullananı sınar: değişiklik dizisinde main.php varsa istisna fırlatır, çünkü kullananı düzenleyen bir değişiklik ölçülmez. Sonra tabanı olcum/<ad>/once dizinine yazar, tabanın üzerine değişikliği bindirir ve sonucu olcum/<ad>/sonra dizinine yazar. İki dizin koşturulur. Önceki koşumun çıkış kodu 0 değilse fonksiyon yine istisna fırlatır: bozuk bir tabandan gelen sonuç bir değişikliği ölçmez.

match (true) bloğu karar sırasıdır ve sınıf bu sırayla verilir: önce kıran, sonra örtük, sonra bağlantıda, hiçbiri değilse yerinde. Sıra önemlidir. Bir değişiklik hem çıktıyı değiştirip hem durduruyorsa kıran sayılır, çünkü durmak daha erken duyulur. Bu sıra kursun hiçbir dersinde yeniden anlatılmaz. Sonuncu printf, her gözlemin tek satırlık özetini basar: ad, düzenlenen dosya sayısı, değişiklikten sonraki çıkış kodu, çıktının aynı ya da farklı olması ve sınıf. Dosya sayısı sınıfı belirlemez; belirleyen, kullananın düzenlenip düzenlenmediğidir.

Özel Özelliğin Adı Değişir

İlk gözlem Hesap sınıfının özel özelliğinin adını bakiye’den toplam’a çevirir. Kullanan main.php yalnız yatir() ve bakiye() yöntemlerini çağırır. Sürücü olc() çağrısını koşturur ve ardından iki koşumun çıktısını ayrıca basar.

<?php
// siniflar.php — ozel ozelligin adi degisir; kullanan yalniz yontem cagirir
require_once 'yayilim.php';

olc('ozel-ozellik', [
    'hesap.php' => <<<'PHP'
        <?php
        class Hesap
        {
            private int $bakiye = 0;
            public function yatir(int $tutar): void { $this->bakiye += $tutar; }
            public function bakiye(): int { return $this->bakiye; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'hesap.php';
        $hesap = new Hesap();
        $hesap->yatir(50);
        $hesap->yatir(25);
        echo $hesap->bakiye(), "\n";
        PHP,
], [
    'hesap.php' => <<<'PHP'
        <?php
        class Hesap
        {
            private int $toplam = 0;
            public function yatir(int $tutar): void { $this->toplam += $tutar; }
            public function bakiye(): int { return $this->toplam; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/ozel-ozellik/$asama");
    echo $asama, ': ', $cikti, "\n";
}
ozel-ozellik       1 dosya  cikis=0   cikti ayni   -> yerinde
once: 75
sonra: 75

NM2. Bir dosya düzenlendi, çıkış kodu 0 kaldı, çıktı aynı kaldı: 75 önce de sonra da. Kullanan dosya hiç açılmadı. Değişikliği sınıfın içinde tutan görünürlüktür; kullananın görebildiği tek şey yöntemlerdi ve ikisinin imzası aynı kaldı. Bu dersin ölçü sınıfı yerindedir. M12/K01’in sessiz sınıfı ile karıştırılmamalı: orada bir aksaklık sinyal vermiyordu, burada ortada bir aksaklık yok.

Özellik Açıksa

Aynı değişiklik özellik public olduğunda başka bir sonuç verir. Karşıt ölçümde Hesap sınıfının bakiye özelliği açıktır ve kullanan özelliği doğrudan okur. Ad yine toplam olur; kullanan main.php yine düzenlenmez.

<?php
// acik.php — ayni degisiklik, ama ozellik public ve kullanan okuyor
require_once 'yayilim.php';

olc('acik-ozellik', [
    'hesap.php' => <<<'PHP'
        <?php
        class Hesap
        {
            public int $bakiye = 0;
            public function yatir(int $tutar): void { $this->bakiye += $tutar; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'hesap.php';
        $hesap = new Hesap();
        $hesap->yatir(50);
        $hesap->yatir(25);
        var_dump($hesap->bakiye);
        PHP,
], [
    'hesap.php' => <<<'PHP'
        <?php
        class Hesap
        {
            public int $toplam = 0;
            public function yatir(int $tutar): void { $this->toplam += $tutar; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/acik-ozellik/$asama");
    echo $asama, ': ', $cikti, "\n";
}
acik-ozellik       1 dosya  cikis=0   cikti farkli -> ortuk
once: int(75)
sonra: NULL

NM3. Çıkış kodu yine 0, ama çıktı değişti: int(75) yerine NULL. Kullanan, tanımda artık bulunmayan bir özelliği okudu ve NULL aldı. Bu ders bir sinyalin varlığını ölçmez, iki koşumun karşılaştırmasını ölçer ve karşılaştırma örtük sınıfı verir. Aynı kod ve aynı değişiklik, yalnız görünürlük farklı olduğu için yerinde sınıfından örtük sınıfına geçti.

Sınırlayıcı Ölçüm

Yukarıdaki sonuç kullananın özelliği okumasına dayanıyordu. Kullanan aynı özelliğe yazarsa durum değişir. Bu ölçümde Hesap sınıfının public özelliğinin adı yine değişir, ama kullanan değeri okumadan önce kendisi atar.

<?php
// yazan.php — public ozellige yazan kullanan, ad degisince durmaz
require_once 'yayilim.php';

olc('acik-yazma', [
    'hesap.php' => <<<'PHP'
        <?php
        class Hesap
        {
            public int $bakiye = 0;
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'hesap.php';
        $hesap = new Hesap();
        $hesap->bakiye = 75;
        echo $hesap->bakiye, "\n";
        PHP,
], [
    'hesap.php' => <<<'PHP'
        <?php
        class Hesap
        {
            public int $toplam = 0;
        }
        PHP,
]);
acik-yazma         1 dosya  cikis=0   cikti ayni   -> yerinde

Sınıf yerinde çıktı, ama bu dilin kullananı koruduğu anlamına gelmez. bakiye adı artık tanımda yok; PHP olmayan bir özelliğe yazılınca onu nesneye dinamik özellik (dynamic property) olarak ekler; PHP 8.2’den beri bunu, sınıf #[\AllowDynamicProperties] ile işaretlenmedikçe E_DEPRECATED ile bildirir. Yazılan değer okununca geri gelir, yani olc() iki çıktıyı aynı bulur. Dilin bir sinyal verip vermediğini ayrı bir blokta, bir hata işleyicisiyle ölçelim.

<?php
// dinamik.php — olmayan ozellige yazmak hangi seviyeyi bildirir
$adlar = [E_DEPRECATED => 'E_DEPRECATED', E_WARNING => 'E_WARNING', E_NOTICE => 'E_NOTICE'];
set_error_handler(function (int $seviye) use ($adlar): bool {
    echo 'bildirilen: ', $adlar[$seviye] ?? 'baska', "\n";
    return true;
});

class Hesap
{
    public int $toplam = 0;
}

$hesap = new Hesap();
$hesap->bakiye = 75;
echo $hesap->bakiye, "\n";
bildirilen: E_DEPRECATED
75

NM4. Dil sinyal verdi, ama sınıfı değiştirmedi: olmayan özelliğe yazmak E_DEPRECATED bildirdi ve değer yine okundu, 75. Kullanan durmadı, çünkü yazma işlemi tanımla değil nesneyle konuşur. Yerinde sonucu tanımın ne kadar sağlam olduğunun değil, kullananın tanımın neresine dokunduğunun ölçüsüdür: okuyan kullanan adı kaybedince örtük sınıfa girdi, yazan kullanan ise adı kendisi yeniden yarattığı için yerinde kaldı. Ders bu bildirimin seviyesini yalnız E_DEPRECATED adıyla ölçtü; iletinin metni basılmadı.

Özet

  • Sınıf özellik ve yöntem taşır, new ile örnek kurar ve her örnek kendi durumunu tutar; private görünürlük özelliği dışarıya kapatır.
  • Bir değişikliğin ulaştığı yer önce ve sonra iki koşumuyla ölçülür ve dört sınıftan biri seçilir; karar sırası kıran, örtük, bağlantıda, yerinde.
  • Özel özelliğin adı değişince kullanan yöntemleri çağırdığı için hiçbir şey duymadı: 75 önce de sonra da. Bu dersin ölçü sınıfı yerindedir.
  • Aynı değişiklik public özellikte, kullanan okuyorsa, örtük sınıfa geçti: int(75) yerine NULL.
  • Public özelliğe yazan kullanan ad değişince durmadı; PHP dinamik özellik kurdu ve E_DEPRECATED bildirdi, olc() sınıfı yine yerinde buldu.

Sonraki Adım

Bir sınıfın içi değişince onu kullananlar etkilenmedi, çünkü araya görünürlük girmişti. Kalıtım dersi sınıfın bir başka sınıfı genişlettiği durumu soruyor: üst sınıfın içi değişince alt sınıf ne yaşıyor?

İ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