---
title: 'Nesneye Dayalı PHP ve Standartlar'
source: 'https://academia.sh/tr/kurslar/php-nesne-modeli'
language: tr
updated: '2026-10-21T05:03:34+00:00'
license: 'CC BY-SA 4.0'
lessons: 12
outcomes:
    - 'Bir değişikliğin yerinde kaldığını, bağlantı noktasında karşılandığını, kullananı kırdığını ya da sonucunu örtük biçimde değiştirdiğini önce/sonra koşumuyla ölçer'
    - 'Sınıf, arayüz ve trait ile kodu düzenler ve görünürlüğü bir sınır olarak kullanır'
    - 'Ad alanı ve otomatik yükleme standartlarına uyan bir dizin yapısı kurar'
    - 'Bağımlılık bildirimi, sürüm kısıtı ve kilit dosyasının bir güncellemenin sonucunu nasıl belirlediğini açıklar'
    - 'Bağımlılıkları dışarıdan vererek bir değişikliği tek bir kurulum noktasında toplar'
---

# Nesneye Dayalı PHP ve Standartlar

Bir tanım değiştiğinde onu kullanan kodun ne kadarı etkilenir? Kimi değişiklik tanımın içinde kalır, kimi tek bir bağlantı noktasında karşılanır, kimi kullananı durdurur, kimi kullananın sonucunu sessizce değiştirir. Kurs sınıfları, kalıtımı, arayüzleri, trait'leri, ad alanlarını, otomatik yüklemeyi, bağımlılık yönetimini ve bağımlılık enjeksiyonunu tek bir soruyla ölçer — bu değişiklik nereye ulaşıyor?

## Nesne Modeli

Sınıflar ve nesneler, kalıtım ve soyut sınıflar, arayüzler, trait'ler, statik üyeler, sihirli yöntemler ve tip bildirimleri — her biri bir tanım değiştiğinde kullananın ne yaşadığını belirleyen bir düzenek olarak.

### Sınıflar ve Nesneler

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/siniflar-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.

Ö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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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?

### Kalıtım ve Soyut Sınıflar

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/kalitim-ve-soyut-siniflar

Bir üst sınıfın içi değiştiğinde alt sınıfın sonucunun örtük biçimde kayması, soyut yöntem eklemenin alt sınıfları durdurması ve sayacı bileşimle kurmanın aynı değişikliği yerinde bırakması.

Sınıflar ve Nesneler dersinde bir sınıfın içindeki ad değişti ve kullanan etkilenmedi, çünkü araya görünürlük girmişti. Kalıtımda araya hiçbir şey girmez: alt sınıf, üst sınıfın yöntemlerini ve korunan özelliklerini doğrudan çağırır. Bu yüzden soru değişir. Üst sınıfın içi değiştiğinde alt sınıf ne yaşıyor?

#### Alt Sınıf Kurmak

Kalıtım (inheritance), bir sınıfın başka bir sınıfın özelliklerini ve yöntemlerini devralmasıdır. Devralan sınıf alt sınıf (subclass), devralınan üst sınıf (parent class) olur. PHP'de `extends` sözcüğü bunu bildirir. `protected` görünürlüğü, bir üyeyi sınıfın kendisine ve alt sınıflara açar, dışarıya kapalı tutar. Alt sınıf bir yöntemi aynı adla yazarsa yöntemi yeniden tanımlar (overriding); üst sınıfın sürümüne yine de `parent::` ile ulaşılır.

```php
<?php
// Liste.php — protected bir ozellik ve iki yontem tasiyan ust sinif
class Liste
{
    protected array $ogeler = [];

    public function ekle(string $oge): void
    {
        $this->ogeler[] = $oge;
    }

    public function hepsiniEkle(array $ogeler): void
    {
        foreach ($ogeler as $oge) {
            $this->ekle($oge);
        }
    }

    public function say(): int
    {
        return count($this->ogeler);
    }
}
```

```php
<?php
// SayanListe.php — ekle() yontemini yeniden tanimlayan alt sinif
class SayanListe extends Liste
{
    public int $sayi = 0;

    public function ekle(string $oge): void
    {
        $this->sayi++;
        parent::ekle($oge);
    }
}
```

```php
<?php
// liste_kullan.php — alt sinif ust sinifin yontemini kullanir; protected ozellik disaridan kapali
require 'Liste.php';
require 'SayanListe.php';

$liste = new SayanListe();
$liste->hepsiniEkle(['a', 'b', 'c']);
echo $liste->sayi, ' ', $liste->say(), "\n";

try {
    echo count($liste->ogeler);
} catch (Error $hata) {
    echo get_class($hata), "\n";
}
```

```
3 3
Error
```

**NM5.** `hepsiniEkle()` alt sınıfta yazılmadı, devralındı; her öğe için `ekle()` çağrıldı ve `SayanListe::ekle()` her çağrıda sayacı artırdı: `3 3`. `protected` özellik alt sınıfa açıktı ama dışarıya kapalı kaldı. Burada dikkat çeken şey, alt sınıfın sayacının üst sınıfın bir iç ayrıntısına dayanmasıdır: `hepsiniEkle()`'nin tek tek `ekle()` çağırması.

#### Soyut Sınıf ve final

Soyut sınıf (abstract class), örneği kurulamayan ve gövdesiz yöntem taşıyabilen sınıftır; alt sınıflar o yöntemleri yazmak zorundadır. Aşağıdaki sınıf bir yöntemi gövdesiz bildirir ve bir yöntemi gövdesiyle verir.

```php
<?php
// Rapor.php — govdesiz bir yontem ve ona dayanan govdeli bir yontem
abstract class Rapor
{
    abstract public function baslik(): string;

    public function yaz(): string
    {
        return strtoupper($this->baslik());
    }
}
```

```php
<?php
// Gunluk.php — soyut yontemi yazan somut alt sinif
class Gunluk extends Rapor
{
    public function baslik(): string
    {
        return 'gunluk';
    }
}
```

```php
<?php
// rapor_kullan.php — somut alt sinif kurulur, soyut sinif kurulamaz
require 'Rapor.php';
require 'Gunluk.php';

echo (new Gunluk())->yaz(), "\n";

try {
    new Rapor();
} catch (Error $hata) {
    echo get_class($hata), "\n";
}
```

```
GUNLUK
Error
```

Bir yöntem `final` ile işaretlenirse alt sınıflar onu yeniden tanımlayamaz. Dosya yüklenir yüklenmez durur. Aşağıdaki betik bir `final` yöntemi yeniden tanımlayan iki sınıfı bir dosyaya yazar ve çıkış kodunu basar.

```bash
# final.sh — final yontemi yeniden tanimlayan sinif yuklenemez
cat > Sabit.php <<'PHP'
<?php
class Taban
{
    final public function kimlik(): string { return 'taban'; }
}
class Alt extends Taban
{
    public function kimlik(): string { return 'alt'; }
}
PHP
php -d display_errors=0 Sabit.php; echo "cikis=$?"
```

```
cikis=255
```

**NM6.** `new Gunluk()` çalıştı ve `Rapor::yaz()` alt sınıfın `baslik()` yöntemini kullandı. `new Rapor()` ve `final` yöntemi yeniden tanımlayan dosya ise sinyal verdi: ilki `Error` fırlattı, ikincisi çıkış 255 ile durdu. Soyut sınıf ve `final`, kalıtımın iki ucudur: biri alt sınıfa yazma zorunluluğu getirir, öteki yazma yetkisini alır.

#### Üst Sınıfın Yapıcısı

Alt sınıf kendi yapıcısını yazarsa üst sınıfın yapıcısı kendiliğinden çalışmaz. Üst sınıfın kurduğu durumun hazır olması için `parent::__construct()` açıkça çağrılır. Çağrı unutulursa üst sınıfın özellikleri kurulmadan kalır.

```php
<?php
// Kisi.php — ust sinif yapicisi, cagrilan ve unutulan alt sinif
class Kisi
{
    public function __construct(protected string $ad) {}

    public function selam(): string
    {
        return 'merhaba ' . $this->ad;
    }
}

class Calisan extends Kisi
{
    public function __construct(string $ad, private int $no)
    {
        parent::__construct($ad);
    }

    public function kart(): string
    {
        return $this->selam() . ' #' . $this->no;
    }
}

class Unutan extends Kisi
{
    public function __construct(private int $no) {}

    public function oku(): string
    {
        return $this->ad;
    }
}

echo (new Calisan('ali', 7))->kart(), "\n";

try {
    echo (new Unutan(1))->oku();
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
merhaba ali #7
Error
```

`Calisan` üst sınıfın yapıcısını çağırdığı için `ad` kuruldu. `Unutan` çağırmadığı için `ad` hiç yazılmadı ve okunduğunda `Error` doğdu. Kalıtımda yapıcı zinciri yalnız yazılırsa işler.

#### Yeniden Tanımlamada İmza ve final Sınıf

Alt sınıf bir yöntemi yeniden tanımlarken üst sınıfın imzasıyla uyumlu kalmak zorundadır; üst sınıfı tipli bir değişkende tutan kod alt sınıfla da aynı çağrıyı yapabilmelidir. Zorunlu bir parametre eklemek bu uyumu bozar. Bir sınıfın hiç genişletilmemesi isteniyorsa sınıf `final` ile işaretlenir; genişleten dosya yüklenir yüklenmez durur.

```bash
# imza.sh — zorunlu parametre eklemek ve final sinifi genisletmek bildirilemez yapar
cat > Imza.php <<'PHP'
<?php
class Taban { public function ekle(string $oge): void {} }
class Alt extends Taban { public function ekle(string $oge, int $adet): void {} }
PHP
cat > KapaliSinif.php <<'PHP'
<?php
final class Kapali {}
class Acan extends Kapali {}
PHP
php -d display_errors=0 Imza.php; echo "imza cikis=$?"
php -d display_errors=0 KapaliSinif.php; echo "final cikis=$?"
```

```
imza cikis=255
final cikis=255
```

İki durumda da hata yürütme sırasında değil, sınıf bildirimi sırasında çıktı. `final` yöntem tek bir davranışı, `final` sınıf bütün davranışı alt sınıfların elinden alır.

#### Kalıtım Ne Zaman Yerindedir

Kalıtım iki sınıf arasında "bir tür" ilişkisi varsa yerindedir: `Calisan` bir `Kisi`'dir, `Gunluk` bir `Rapor`'dur. Yalnız kodu paylaşmak için kurulan kalıtım, alt sınıfı üst sınıfın bütün yöntemlerine ve gelecekteki bütün değişikliklerine bağlar. Bu ders boyunca ölçülen şey de budur: üst sınıfın içi değişince bağlı alt sınıflar bundan etkilenir. Paylaşılan kod bir ilişkiyi değil bir yeteneği anlatıyorsa, arayüz ya da trait daha az bağ kurar; bunlar sıradaki iki derstir.

#### Ölçüm Çekirdeği

Bu dersin ölçümü `yayilim.php` dosyasını kullanır; dosya Sınıflar ve Nesneler dersinde satır satır açıklanan çekirdektir ve her dersin çalışma dizininde yeniden yazılır.

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

#### Üst Sınıfın İçi Değişir

İlk gözlem yukarıdaki `Liste` ve `SayanListe` ikilisini kullanır. Üst sınıfın `hepsiniEkle()` yöntemi artık tek tek `ekle()` çağırmaz, diziyi `array_merge()` ile bir seferde birleştirir. Alt sınıf ve kullanan `main.php` aynı kalır. Üst sınıfın dışarıya verdiği sözleşme de değişmez: iki yöntem aynı adı ve imzayı taşır.

```php
<?php
// kalitim.php — ust sinif toplu eklemede tekli ekleme yontemini artik cagirmaz
require_once 'yayilim.php';

olc('ust-sinif', [
    'liste.php' => <<<'PHP'
        <?php
        class Liste
        {
            protected array $ogeler = [];
            public function ekle(string $oge): void { $this->ogeler[] = $oge; }
            public function hepsiniEkle(array $ogeler): void
            {
                foreach ($ogeler as $oge) {
                    $this->ekle($oge);
                }
            }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'liste.php';
        class SayanListe extends Liste
        {
            public int $sayi = 0;
            public function ekle(string $oge): void { $this->sayi++; parent::ekle($oge); }
        }
        $liste = new SayanListe();
        $liste->hepsiniEkle(['a', 'b', 'c']);
        echo $liste->sayi, "\n";
        PHP,
], [
    'liste.php' => <<<'PHP'
        <?php
        class Liste
        {
            protected array $ogeler = [];
            public function ekle(string $oge): void { $this->ogeler[] = $oge; }
            public function hepsiniEkle(array $ogeler): void
            {
                $this->ogeler = array_merge($this->ogeler, $ogeler);
            }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/ust-sinif/$asama");
    echo $asama, ': ', $cikti, "\n";
}
```

```
ust-sinif          1 dosya  cikis=0   cikti farkli -> ortuk
once: 3
sonra: 0
```

**NM7.** Çıkış kodu 0, ama çıktı değişti: sayaç `3` iken `0` oldu. Alt sınıf yalnız `ekle()` yöntemini yeniden tanımlamıştı; üst sınıf artık `ekle()` çağırmadığı için alt sınıfın yöntemi hiç çalışmadı. Kullanan kodda hata yok, uyarı yok, yalnızca yanlış bir sayı var. Bu, kırılgan üst sınıf sorunudur (fragile base class problem): alt sınıf, üst sınıfın sözleşmesine değil iç işleyişine güvenmişti ve iç işleyiş değişti. Bu dersin ölçü sınıfı örtüktür. M08/K02 çağrılan yöntemi hangi tipin seçtiğini ölçmüştü; burada ölçülen, üst sınıftaki değişikliğin alt sınıfa ulaşıp ulaşmadığıdır ve ulaştı.

#### Soyut Yöntem Eklemek

Karşıt ölçüm değişikliği üst sınıfın içinden sözleşmesine taşır. Soyut bir sınıfa yeni bir `abstract` yöntem eklenir. Daha önce yazılmış `Gunluk`'un benzeri bir alt sınıf bu yöntemi bilmez.

```php
<?php
// soyut.php — soyut ust sinifa yeni bir abstract yontem eklenir
require_once 'yayilim.php';

olc('soyut-yontem', [
    'rapor.php' => <<<'PHP'
        <?php
        abstract class Rapor
        {
            abstract public function baslik(): string;
            public function yaz(): string { return strtoupper($this->baslik()); }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'rapor.php';
        class Gunluk extends Rapor
        {
            public function baslik(): string { return 'gunluk'; }
        }
        echo (new Gunluk())->yaz(), "\n";
        PHP,
], [
    'rapor.php' => <<<'PHP'
        <?php
        abstract class Rapor
        {
            abstract public function baslik(): string;
            abstract public function alt(): string;
            public function yaz(): string { return strtoupper($this->baslik()); }
        }
        PHP,
]);
```

```
soyut-yontem       1 dosya  cikis=255 cikti farkli -> kiran
```

**NM8.** Aynı kullanan, aynı alt sınıf, aynı tek dosyalık değişiklik; bu kez kullanan durdu. Soyut yöntem bir alt sınıfa "yazmak zorundasın" der ve alt sınıf yazmadığında sınıf bildirilemez. Çıkış kodu 0 değil, sınıf kıran. Sözleşme gövdesiz olduğu için dil değişikliği yakalar; üst sınıfın iç değişikliğini yakalayacak bir sözleşme yoktu.

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

Örtük sonucun nedeni kalıtımın kendisi mi, yoksa sayacın üst sınıfın içine bağlanması mı? Aynı sayaç bileşimle (composition) kurulduğunda ölçüm bunu ayırır. Bileşimde sınıf başka bir sınıfı genişletmez, onun bir örneğini taşır ve kendi iki yönteminde sayar. Üst sınıfta yapılan değişiklik ise öncekiyle aynıdır: `hepsiniEkle()` artık `ekle()` çağırmaz.

```php
<?php
// bilesim.php — sayac kalitimla degil bilesimle kurulur; ayni ust sinif degisikligi
require_once 'yayilim.php';

olc('bilesim', [
    'liste.php' => <<<'PHP'
        <?php
        class Liste
        {
            protected array $ogeler = [];
            public function ekle(string $oge): void { $this->ogeler[] = $oge; }
            public function hepsiniEkle(array $ogeler): void
            {
                foreach ($ogeler as $oge) {
                    $this->ekle($oge);
                }
            }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'liste.php';
        class SayanListe
        {
            public int $sayi = 0;
            private Liste $ic;
            public function __construct() { $this->ic = new Liste(); }
            public function ekle(string $oge): void { $this->sayi++; $this->ic->ekle($oge); }
            public function hepsiniEkle(array $ogeler): void { $this->sayi += count($ogeler); $this->ic->hepsiniEkle($ogeler); }
        }
        $liste = new SayanListe();
        $liste->hepsiniEkle(['a', 'b', 'c']);
        echo $liste->sayi, "\n";
        PHP,
], [
    'liste.php' => <<<'PHP'
        <?php
        class Liste
        {
            protected array $ogeler = [];
            public function ekle(string $oge): void { $this->ogeler[] = $oge; }
            public function hepsiniEkle(array $ogeler): void
            {
                $this->ogeler = array_merge($this->ogeler, $ogeler);
            }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/bilesim/$asama");
    echo $asama, ': ', $cikti, "\n";
}
```

```
bilesim            1 dosya  cikis=0   cikti ayni   -> yerinde
once: 3
sonra: 3
```

**NM9.** Üst sınıfın değişikliği aynı, kullanan aynı; sonuç `3` kaldı. Bileşimde sayaç, üst sınıfın hangi yöntemi hangisine çağırdığına bakmaz: kendi iki yönteminde sayar ve üst sınıfa yalnız sözleşmeyi, yani iki yöntemin adını ve imzasını kullanarak ulaşır. Aynı iç değişiklik kalıtımda örtük, bileşimde yerinde kaldı. Ölçülen ayrım dilin değil tasarımın: kalıtım alt sınıfı üst sınıfın içine bağlar, bileşim bağlamaz. Bedel de buradan gelir: `SayanListe` artık bir `Liste` değildir.

#### Özet

- `extends` bir alt sınıf kurar, `protected` üyeyi alt sınıfa açıp dışarıya kapalı tutar, `parent::` üst sınıfın yöntemine ulaşır.
- Soyut sınıf örneklenemez ve alt sınıfa gövdesiz yöntemleri yazma zorunluluğu getirir; `final` yöntem yeniden tanımlanamaz.
- Üst sınıf toplu eklemede `ekle()` çağırmayı bırakınca alt sınıfın sayacı `3`'ten `0`'a düştü; bu dersin ölçü sınıfı örtüktür.
- Soyut sınıfa `abstract` yöntem eklemek aynı kullananı durdurdu, kıran sınıf; sözleşme gövdesiz olduğu için dil yakalar.
- Sayaç bileşimle kurulunca aynı üst sınıf değişikliği yerinde kaldı: `3`.

#### Sonraki Adım

Soyut yöntem gövdesiz bir sözleşme getirdi ve sözleşmeye yöntem eklemek alt sınıfı durdurdu. Arayüzler dersi bu sözleşmeyi kalıtımdan ayırıyor: bir arayüze yöntem eklenince onu gerçekleştiren sınıflar ne yaşıyor?

### Arayüzler

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/arayuzler

Bir arayüze yöntem eklendiğinde onu gerçekleştiren sınıfın bildirilememesi, tip olarak arayüz ve instanceof, ve aynı eklemenin gövdeli bir soyut sınıfta yerinde kalması.

Kalıtım dersinde soyut sınıfa yeni bir `abstract` yöntem eklemek alt sınıfı durdurmuştu ve bunun nedeni sözleşmenin gövdesiz olmasıydı. Arayüz bu sözleşmenin yalın hâlidir: yalnız imza taşır, hiçbir gövde taşımaz. Soru, bir sözleşmeye yöntem eklenince onu gerçekleştiren sınıfların ne yaşadığıdır.

#### Arayüz ve Gerçekleştirim

Arayüz (interface), bir sınıfın hangi yöntemleri sunması gerektiğini bildirir ve hiçbirini yazmaz. Arayüzü uygulayan sınıf `implements` ile bildirimde bulunur ve bütün yöntemleri yazmak zorundadır; buna gerçekleştirim (implementation) denir. Bir sınıf birden çok arayüzü gerçekleştirebilir, virgülle ayrılmış olarak.

```php
<?php
// Odenebilir.php — yalniz bir imza tasiyan arayuz
interface Odenebilir
{
    public function tutar(): int;
}
```

```php
<?php
// Yazdirilabilir.php — ikinci bir arayuz
interface Yazdirilabilir
{
    public function metin(): string;
}
```

```php
<?php
// Fatura.php — iki arayuzu birden gerceklestiren sinif
class Fatura implements Odenebilir, Yazdirilabilir
{
    public function tutar(): int { return 120; }
    public function metin(): string { return 'fatura'; }
}
```

```php
<?php
// Maas.php — yalniz Odenebilir'i gerceklestiren sinif
class Maas implements Odenebilir
{
    public function tutar(): int { return 300; }
}
```

Arayüz bir tip olarak da kullanılır. Aşağıdaki fonksiyon `Odenebilir` bekler ve bu arayüzü gerçekleştiren herhangi bir sınıfın nesnesini kabul eder. `instanceof` işleci bir nesnenin verilen arayüzü gerçekleştirip gerçekleştirmediğini sorar.

```php
<?php
// arayuz_kullan.php — arayuz parametre tipi olarak ve instanceof ile
require 'Odenebilir.php';
require 'Yazdirilabilir.php';
require 'Fatura.php';
require 'Maas.php';

function toplamTutar(Odenebilir ...$kalemler): int
{
    $toplam = 0;
    foreach ($kalemler as $kalem) {
        $toplam += $kalem->tutar();
    }
    return $toplam;
}

echo toplamTutar(new Fatura(), new Maas()), "\n";
var_dump(new Fatura() instanceof Yazdirilabilir);
var_dump(new Maas() instanceof Yazdirilabilir);
```

```
420
bool(true)
bool(false)
```

**NM10.** `toplamTutar()` iki ayrı sınıfın nesnesini aldı, çünkü ikisi de `Odenebilir`'i gerçekleştiriyordu: `120 + 300`. `Fatura` iki arayüze, `Maas` yalnız birine uyuyor; `instanceof` bu farkı `true` ve `false` ile söyledi. Arayüz imzayı denetler, anlamı denetlemez: `tutar()` negatif bir sayı da döndürebilirdi ve dil sesini çıkarmazdı. M08/K03 bir davranışın garantisini kimin verdiğini sormuştu; burada arayüz bir söz değil, bir sınırdır. İmzanın dışındaki anlamı ölçmek Ortak Arayüz Standartları dersine kalıyor.

#### Arayüz Sabiti ve Arayüzün Arayüzü Genişletmesi

Arayüz yalnız imza taşımaz, sabit de taşıyabilir. Arayüz sabiti (interface constant) gerçekleştiren her sınıfta aynı adla okunur. Bir arayüz başka bir arayüzü `extends` ile genişletebilir; bu durumda genişleten arayüzü gerçekleştiren sınıf iki arayüzün bütün imzalarını yazmak zorundadır. Sınıflarda tek kalıtım kuralı arayüzler için geçerli değildir: bir arayüz birden çok arayüzü genişletebilir.

```php
<?php
// Vergili.php — Odenebilir'i genisleten ve bir sabit tasiyan arayuz
require_once 'Odenebilir.php';

interface Vergili extends Odenebilir
{
    public const ORAN = 20;

    public function vergi(): int;
}
```

```php
<?php
// Satis.php — Vergili'yi gerceklestiren sinif, iki arayuzun imzalarini da yazar
require_once 'Vergili.php';

class Satis implements Vergili
{
    public function tutar(): int { return 200; }
    public function vergi(): int { return intdiv($this->tutar() * self::ORAN, 100); }
}
```

```php
<?php
// satis_kullan.php — genisleten arayuz, sabit ve ust arayuz tipi
require 'Satis.php';

$satis = new Satis();
echo $satis->tutar(), ' ', $satis->vergi(), ' ', Vergili::ORAN, "\n";
var_dump($satis instanceof Odenebilir);
```

```
200 40 20
bool(true)
```

`Satis` yalnız `Vergili`'yi gerçekleştirdiğini söylüyor, ama `instanceof Odenebilir` de `true` verdi: genişleten arayüz, üst arayüzün tipi sayılır. Sabit `Vergili::ORAN` ile arayüzün adı üzerinden de, `self::ORAN` ile gerçekleştiren sınıfın içinden de okunur.

#### Tip Olarak Arayüz ve Örneklenememe

Arayüz bir sınıf gibi örneklenemez, çünkü gövdesi yoktur. Aynı sözleşmeyi taşımayan bir nesne de arayüz tipli bir parametreye giremez. İkisi de çalışma anında bir hata olarak görünür; ilki `Error`, ikincisi `TypeError` verir.

```php
<?php
// tip_hatasi.php — arayuz ornek olamaz, sozlesmesiz nesne parametreye giremez
require 'Satis.php';

function ode(Odenebilir $kalem): int
{
    return $kalem->tutar();
}

echo ode(new Satis()), "\n";

try {
    ode(new stdClass());
} catch (TypeError $e) {
    echo get_class($e), "\n";
}

try {
    $kalem = new Odenebilir();
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
200
TypeError
Error
```

`ode()` bir sınıfa değil bir davranışa bağlıdır: `Satis`, `Fatura` ya da yarın yazılacak başka bir sınıf `Odenebilir`'i gerçekleştirdiği sürece gider. Bu bağımsızlık arayüzün asıl yararıdır, çünkü çağıran kod gerçekleştirimi tanımaz. Öte yandan sözleşmeyi taşımayan `stdClass` tür denetimine takılır ve `TypeError` doğurur.

#### Gerçekleştirenin Yükümlülüğü

Arayüzü bildiren sınıf, arayüzün bütün yöntemlerini yazmak ve hepsini `public` yapmak zorundadır. Biri eksikse ya da görünürlüğü daraltılmışsa sınıf bildirilemez. Aşağıdaki betik bu iki durumu ayrı dosyalarda dener.

```bash
# yukumluluk.sh — eksik yontem ve daraltilmis gorunurluk sinifi bildirilemez yapar
cat > Eksik.php <<'PHP'
<?php
interface Odenebilir { public function tutar(): int; }
class Bos implements Odenebilir {}
PHP
cat > Dar.php <<'PHP'
<?php
interface Odenebilir { public function tutar(): int; }
class Gizli implements Odenebilir { private function tutar(): int { return 1; } }
PHP
php -d display_errors=0 Eksik.php; echo "eksik cikis=$?"
php -d display_errors=0 Dar.php; echo "dar cikis=$?"
```

```
eksik cikis=255
dar cikis=255
```

Kısıt sınıfın değil kullananın korunması içindir: arayüz tipli bir parametreye giren nesnenin `tutar()` yöntemi her zaman çağrılabilir olmalıdır. Bu garanti, aşağıdaki gibi bir yer değiştirmeyi güvenli yapar.

```php
<?php
// sahte.php — sozlesmeyi tasiyan sahte bir gerceklestirim, gercek olanin yerine gecer
require 'Odenebilir.php';
require 'Yazdirilabilir.php';
require 'Fatura.php';

class SahteKalem implements Odenebilir
{
    public function tutar(): int { return 1; }
}

$kalemler = [new Fatura(), new SahteKalem()];
echo array_sum(array_map(fn (Odenebilir $kalem): int => $kalem->tutar(), $kalemler)), "\n";
```

```
121
```

`array_map()` içindeki fonksiyon yalnız `Odenebilir` biliyor; gerçek bir `Fatura` ile sınama için yazılmış `SahteKalem` aynı kodun içinden geçti. Arayüzün sınamada sağladığı yarar budur: kodu gerçek ödeme ya da gerçek dosya gibi ağır bir bağımlılığa bağlamadan çalıştırmak mümkün olur. Kurs kapanışında ele alınan, bağımlılığın dışarıdan verilmesi bu yararın üzerine kuruludur ve arayüz olmadan aynı esnekliği vermez.

#### Ölçüm Çekirdeği

Bu dersin ölçümü de `yayilim.php` dosyasını kullanır; Sınıflar ve Nesneler dersinde açıklanan çekirdek bu çalışma dizininde yeniden yazılıyor.

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

#### Arayüze Yöntem Eklenir

Ölçülecek değişiklik, yukarıdaki arayüze ikinci bir imza eklemektir: `paraBirimi()`. Gerçekleştiren sınıf `Fatura` yalnız `tutar()` yöntemini taşır. Kullanan `main.php` sınıfı bildirir ve `tutar()` değerini yazdırır; dosya düzenlenmez.

```php
<?php
// arayuz.php — arayuze yeni bir yontem eklenir
require_once 'yayilim.php';

olc('arayuze-yontem', [
    'odenebilir.php' => <<<'PHP'
        <?php
        interface Odenebilir
        {
            public function tutar(): int;
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'odenebilir.php';
        class Fatura implements Odenebilir
        {
            public function tutar(): int { return 120; }
        }
        echo (new Fatura())->tutar(), "\n";
        PHP,
], [
    'odenebilir.php' => <<<'PHP'
        <?php
        interface Odenebilir
        {
            public function tutar(): int;
            public function paraBirimi(): string;
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/arayuze-yontem/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
arayuze-yontem     1 dosya  cikis=255 cikti farkli -> kiran
once cikis=0 cikti=120
sonra cikis=255 cikti=
```

**NM11.** Tek dosya düzenlendi ve kullanan durdu: `Fatura` artık `paraBirimi()` yöntemini taşımadığı için bildirilemedi, çıkış 255. Önceki koşum `120` vermişti. Hata, kullanan kodun kendisinde değil gerçekleştiren sınıftadır; arayüzün imzası değişince onu gerçekleştiren her sınıf sözleşmeyi yeniden karşılamak zorunda kalır. Bu dersin ölçü sınıfı kırandır. `olc()` hata iletisini basmaz, yalnız çıkış kodunu görür.

#### Soyut Sınıf mı, Arayüz mü

İkisi de gövdesiz imza bildirebilir, ama bir sınıfa farklı şeyler söylerler. Arayüz "bu davranışı sunuyorsun" der ve ilişkisiz sınıflar arasında bir ortak ad kurar; durum ve gövde taşıyamaz; sabit ve PHP 8.4'ten beri özellik sözleşmesi bildirebilir. Soyut sınıf "sen bu ailedensin" der; ortak özellik ve gövdeli yöntem taşıyabilir ama sınıfın tek kalıtım hakkını harcar.

Seçimi üç soru yönlendirir. Gerçekleştirenler birbirine akraba mı, yoksa yalnızca aynı davranışı mı sunuyor? Ortak gövde ya da ortak durum paylaşılacak mı? Sözleşme zamanla büyüyecek mi? Birinci soruya "yalnız aynı davranış", ikinciye "hayır" derseniz arayüz doğrudur. Ortak durum ya da gövde istiyorsanız soyut sınıf gerekir; bir sınıfın hem bir soyut sınıfı genişletmesi hem de birkaç arayüzü gerçekleştirmesi yaygın bir birleşimdir. Üçüncü soruya "evet" derseniz aşağıdaki ölçüm, bu seçimin bedelini gösteriyor: arayüz büyüdükçe gerçekleştirenlerin her biri durur, gövdeli soyut sınıf büyüdükçe durmaz. Bu nedenle sözleşmesi sık değişecek bir davranış için soyut sınıf, uzun süre sabit kalacak bir sınır için arayüz seçmek, yazan tarafın değişikliğin bedelini kimin ödeyeceğine verdiği karardır.

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

Kırılmanın kaynağı arayüz kavramı mı, yoksa eklenen yöntemin gövdesiz olması mı? Aynı yöntemi gövdesiyle bir soyut sınıfa eklemek bunu ayırır. Bu ölçümde sözleşme soyut bir sınıftır, `Fatura` onu genişletir ve eklenen `paraBirimi()` yöntemi bir gövde taşır.

```php
<?php
// govdeli.php — ayni ek yontem, bu kez govdesiyle bir soyut sinifa
require_once 'yayilim.php';

olc('soyut-govde', [
    'odenebilir.php' => <<<'PHP'
        <?php
        abstract class Odenebilir
        {
            abstract public function tutar(): int;
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'odenebilir.php';
        class Fatura extends Odenebilir
        {
            public function tutar(): int { return 120; }
        }
        echo (new Fatura())->tutar(), "\n";
        PHP,
], [
    'odenebilir.php' => <<<'PHP'
        <?php
        abstract class Odenebilir
        {
            abstract public function tutar(): int;
            public function paraBirimi(): string { return 'TL'; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/soyut-govde/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
soyut-govde        1 dosya  cikis=0   cikti ayni   -> yerinde
once cikis=0 cikti=120
sonra cikis=0 cikti=120
```

**NM12.** Aynı yöntem, aynı ad, aynı kullanan; bu kez sınıf yerinde kaldı: çıkış 0, çıktı `120`. Eklenen yöntemin gövdesi olduğu için `Fatura`'nın bir şey yazması gerekmedi, yöntem devralındı. Demek ki kırılmayı arayüz sözcüğü değil gövdesiz sözleşme doğurdu. Bu çözümün bir bedeli var: bir sınıf yalnız bir sınıfı genişletebilir. Aşağıdaki betik iki üst sınıfı virgülle sayan bir bildirim yazar ve çıkış kodunu basar.

```bash
# tekli.sh — bir sinif iki sinifi genisletemez
cat > Cift.php <<'PHP'
<?php
abstract class Odenebilir {}
abstract class Yazdirilabilir {}
class Fatura extends Odenebilir, Yazdirilabilir {}
PHP
php -d display_errors=0 Cift.php; echo "cikis=$?"
```

```
cikis=255
```

Bir sınıf istediği kadar arayüzü gerçekleştirebilir ama yalnız bir sınıfı genişletebilir. Gövdeli soyut sınıf değişikliği yerinde tutar, ama sınıfın tek kalıtım hakkını harcar. Arayüz bu hakkı harcamaz, bunun karşılığında eklenen her yöntemi gerçekleştirenlere dayatır.

#### Özet

- Arayüz yalnız imza taşır, bir sınıf istediği kadar arayüzü `implements` ile gerçekleştirebilir ve arayüz parametre tipi olarak kullanılır; `instanceof` hangi arayüzün gerçekleştirildiğini söyler.
- Arayüz imzayı denetler, anlamı denetlemez.
- Arayüze `paraBirimi()` imzası eklenince `Fatura` bildirilemedi, `120` yerine çıkış 255; bu dersin ölçü sınıfı kırandır.
- Aynı yöntem gövdesiyle bir soyut sınıfa eklenince sınıf yerinde kaldı, çıktı `120` olarak sürdü.
- Gövdeli soyut sınıfın bedeli tek kalıtımdır: bir sınıf iki sınıfı genişletemez, çıkış 255.

#### Sonraki Adım

Soyut sınıf gövde taşıyabiliyordu ama sınıfa yalnız bir kez kalıtım hakkı bırakıyordu. Trait'ler dersi gövdeyi paylaşmanın başka bir yolunu soruyor: iki trait aynı adda bir yöntem getirirse kullanan sınıf ne yaşıyor?

### Trait'ler

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/traitler

Trait ile yöntemlerin sınıflar arasında paylaşılması, çakışmanın insteadof ve as ile çözülmesi, ve kullanılan trait'e aynı adlı yöntem eklenince kullananın durması.

Gövdeli soyut sınıf paylaşım sağlıyordu ama sınıfa yalnız bir kalıtım hakkı bırakıyordu. Trait (trait) bu paylaşımı yatay yapar: bir yöntem kümesi bir kez yazılır ve birbiriyle akraba olmayan birçok sınıfa katılır. Soru, kullanılan bir trait'e yöntem eklenince kullanan sınıfın ne yaşadığıdır.

#### Trait ve use

Trait bir sınıf değildir ve örneği yaratılmaz. İçindeki yöntemler `use` ile bir sınıfın içine alınır ve sanki sınıfta yazılmış gibi çalışır. Bir sınıf istediği kadar trait kullanabilir; bu, kalıtımdaki tek üst sınıf kısıtını aşar.

```php
<?php
// Kayitli.php — kayit tutma davranisini tasiyan trait
trait Kayitli
{
    public function ozet(): string
    {
        return 'kayit';
    }
}
```

```php
<?php
// Zamanli.php — saat bilgisini tasiyan ikinci trait
trait Zamanli
{
    public function saat(): string
    {
        return '12:00';
    }
}
```

```php
<?php
// Belge.php — iki trait'i birlikte kullanan sinif
require_once 'Kayitli.php';
require_once 'Zamanli.php';

class Belge
{
    use Kayitli, Zamanli;
}
```

```php
<?php
// belge_kullan.php — trait yontemleri sinifin kendi yontemi gibi cagrilir
require 'Belge.php';

$belge = new Belge();
echo $belge->ozet(), ' ', $belge->saat(), "\n";
var_dump($belge instanceof Kayitli);
```

```
kayit 12:00
bool(false)
```

**NM13.** `Belge` ne `ozet()` ne de `saat()` yazdı ve ikisini de çağırabildik. `instanceof Kayitli` ise `false` verdi: trait bir tip değildir, bir sınıfın içine kopyalanan koddur. Bu yüzden trait'e bir parametre tipi olarak güvenilemez; tip gerekiyorsa arayüz dersindeki yola dönülür.

#### Çakışma Çözümü

İki trait aynı adda bir yöntem getirirse sınıf hangisini kullanacağını bildirmek zorundadır. `insteadof` seçileni belirler, `as` dışarıda kalan yönteme başka bir ad verir.

```php
<?php
// cakisma.php — iki trait ayni adli yontem getirir ve cakisma cozulur
trait Kayitli
{
    public function ozet(): string { return 'kayit'; }
}

trait Zamanli
{
    public function saat(): string { return '12:00'; }
    public function ozet(): string { return 'zaman'; }
}

class Belge
{
    use Kayitli, Zamanli {
        Kayitli::ozet insteadof Zamanli;
        Zamanli::ozet as zamanOzet;
    }
}

$belge = new Belge();
echo $belge->ozet(), ' ', $belge->zamanOzet(), ' ', $belge->saat(), "\n";
```

```
kayit zaman 12:00
```

**NM14.** `ozet()` adı `Kayitli`'ye gitti, `Zamanli`'nin yöntemi `zamanOzet()` olarak kaldı. Çözüm blok kullanan sınıfın içindedir: çakışmayı çözmek kullananın işidir, trait'in değil. Aşağıdaki ölçüm bu cümlenin bedelini gösteriyor.

#### Trait'te Durum ve Soyut Yöntem

Trait yalnız yöntem taşımaz, özellik de taşır. Trait içindeki yöntemde `$this`, trait'i kullanan sınıfın nesnesidir; yani trait kendi başına çalışmaz, her zaman bir sınıfın içine girince anlam kazanır. Trait kullananından bir şey bekliyorsa bunu soyut yöntemle (`abstract`) bildirir: kullanan sınıf o yöntemi yazmak zorundadır. Her kullanan nesnenin özelliği kendine aittir, çünkü özellik sınıfın içine kopyalanmıştır.

```php
<?php
// Sayilabilir.php — ozellik ve soyut yontem tasiyan trait
trait Sayilabilir
{
    private int $sayac = 0;

    abstract public function ad(): string;

    public function artir(): static
    {
        $this->sayac++;
        return $this;
    }

    public function durum(): string
    {
        return $this->ad() . '=' . $this->sayac;
    }
}
```

```php
<?php
// Ziyaret.php — trait'in bekledigi soyut yontemi yazan sinif
require_once 'Sayilabilir.php';

class Ziyaret
{
    use Sayilabilir;

    public function ad(): string
    {
        return 'ziyaret';
    }
}
```

```php
<?php
// ziyaret_kullan.php — iki nesne ayni trait'in ayri durumlarini tasir
require 'Ziyaret.php';

$birinci = new Ziyaret();
$ikinci = new Ziyaret();
$birinci->artir()->artir();

echo $birinci->durum(), ' ', $ikinci->durum(), "\n";
```

```
ziyaret=2 ziyaret=0
```

`durum()` trait'te yazılmıştı ama `$this->ad()` ile sınıfın yazdığı yöntemi çağırdı; `sayac` ise her nesne için ayrı kaldı. Soyut yöntemi yazmayan bir sınıf trait'i kullanamaz.

```bash
# soyut_yok.sh — trait'in soyut yontemi yazilmazsa sinif bildirilemez
cat > Eksik.php <<'PHP'
<?php
trait Sayilabilir { abstract public function ad(): string; }
class Eksik { use Sayilabilir; }
PHP
php -d display_errors=0 Eksik.php; echo "cikis=$?"
```

```
cikis=255
```

#### Görünürlük ve Takma Ad

`as` anahtar sözcüğü yalnız çakışmayı çözmek için değildir. Tek başına kullanılınca bir trait yönteminin görünürlüğünü kullanan sınıfta değiştirir; bir ad eklenince aynı yönteme ikinci bir ad ve görünürlük verir. Trait yönteminin özgün adı ve özgün görünürlüğü böylece sınıfın dışına kapatılırken yeni ad açık bırakılabilir.

```php
<?php
// gorunurluk.php — as ile gorunurluk degisir ve takma ad eklenir
trait Kayitli
{
    public function ozet(): string
    {
        return 'kayit';
    }
}

class Gizli
{
    use Kayitli {
        ozet as protected;
        ozet as public kayitOzet;
    }
}

$gizli = new Gizli();
echo $gizli->kayitOzet(), "\n";

try {
    $gizli->ozet();
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
kayit
Error
```

Aynı yöntem iki adla durdu: `kayitOzet()` açıktı, `ozet()` ise korunmuş olduğu için dışarıdan çağrılınca `Error` verdi.

#### Öncelik Sırası ve Kalıtımdan Farkı

Aynı adda yöntem birden fazla yerden geliyorsa sıra şöyledir: sınıfın kendi yöntemi, trait yöntemi, üst sınıfın yöntemi. Trait bu yüzden üst sınıfın yöntemini bastırabilir, ama sınıfın kendi yazdığını bastıramaz.

```php
<?php
// oncelik.php — sinif, trait ve ust sinif ayni adi tasirsa kazanan
class Temel
{
    public function selam(): string { return 'temel'; }
}

trait Nazik
{
    public function selam(): string { return 'trait'; }
}

class Alt extends Temel
{
    use Nazik;
}

class Ozel extends Temel
{
    use Nazik;

    public function selam(): string { return 'sinif'; }
}

echo (new Alt())->selam(), ' ', (new Ozel())->selam(), "\n";
```

```
trait sinif
```

`Alt` kendi `selam()` yöntemini yazmadı ve trait kazandı; `Ozel` yazdı ve sınıf kazandı. Üst sınıf iki durumda da sonda kaldı.

Trait kalıtımın yerine geçmez, çünkü aralarında üç fark vardır. Birincisi, trait bir tip değildir: `instanceof` ile sorulamaz ve parametre tipi olamaz. İkincisi, yöntemler kullanan sınıfa kopyalanır, bu yüzden aynı trait'i kullanan iki sınıfın statik özelliği ayrı kopyalardır. Üçüncüsü, trait'in bir üst sınıfı yoktur; ilişkili bir hiyerarşi kurmaz, yalnız gövde taşır.

```php
<?php
// statik_trait.php — trait'in statik ozelligi kullanan sinif basina ayri bir kopyadir
trait Sayac
{
    public static int $adet = 0;

    public static function artir(): int
    {
        return ++self::$adet;
    }
}

class Birinci { use Sayac; }
class Ikinci { use Sayac; }

Birinci::artir();
Birinci::artir();
echo Birinci::$adet, ' ', Ikinci::$adet, "\n";
```

```
2 0
```

`Birinci` iki kez artırdı ve `Ikinci` sıfırda kaldı: ortak paylaşım isteniyorsa trait değil sınıf statik özelliği ya da bir üst sınıf gerekir. Kalıtım "bu bir şeydir" ilişkisini kurar, trait "bu şu davranışı da taşır" ilişkisini; kod tekrarını gidermek için kalıtım kullanmak ilişkiyi yanlış kurar ve Kalıtım ve Soyut Sınıflar dersindeki kırılgan üst sınıf sorununu büyütür.

#### Trait ve Arayüzü Birlikte Kullanmak

Trait tip olmadığı için, paylaşılan gövdenin tip olarak da görünmesi isteniyorsa arayüz ve trait birlikte kullanılır: arayüz sözleşmeyi ve tipi, trait ise gövdeyi verir. Sınıf arayüzü `implements` ile bildirir, yöntemin gövdesini `use` ile alır.

```php
<?php
// birlikte.php — arayuz tipi verir, trait govdeyi saglar
interface Etiketli
{
    public function etiket(): string;
}

trait EtiketGovdesi
{
    public function etiket(): string
    {
        return 'etiket';
    }
}

class Gonderi implements Etiketli
{
    use EtiketGovdesi;
}

$gonderi = new Gonderi();
var_dump($gonderi instanceof Etiketli);
echo $gonderi->etiket(), "\n";
```

```
bool(true)
etiket
```

`Gonderi` hem `Etiketli` tipindedir hem de gövdeyi tekrar yazmamıştır. Bu birleşim, Arayüzler dersindeki sözleşmeyi, bu dersteki paylaşımla buluşturur; ama trait'e sonradan eklenen bir yöntemin kullananı durdurma riski aynen kalır.

#### Ölçüm Çekirdeği

Bu dersin ölçümü de `yayilim.php` dosyasını kullanır; çekirdek Sınıflar ve Nesneler dersinde satır satır açıklanmıştı.

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

#### Trait'e Yöntem Eklenir

Ölçülen değişiklik, `Zamanli` trait'ine `Kayitli` ile aynı adlı bir `ozet()` yöntemi eklemektir. Kullanan `Belge` sınıfı iki trait'i `use` eder ve `ozet()`, `saat()` değerlerini yazdırır; `Belge` ve `main.php` düzenlenmez, düzenlenen tek dosya trait dosyasıdır.

```php
<?php
// trait.php — kullanilan trait'e ayni adli bir yontem eklenir
require_once 'yayilim.php';

olc('trait-cakismasi', [
    'izler.php' => <<<'PHP'
        <?php
        trait Kayitli
        {
            public function ozet(): string { return 'kayit'; }
        }
        trait Zamanli
        {
            public function saat(): string { return '12:00'; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'izler.php';
        class Belge
        {
            use Kayitli, Zamanli;
        }
        $belge = new Belge();
        echo $belge->ozet(), ' ', $belge->saat(), "\n";
        PHP,
], [
    'izler.php' => <<<'PHP'
        <?php
        trait Kayitli
        {
            public function ozet(): string { return 'kayit'; }
        }
        trait Zamanli
        {
            public function saat(): string { return '12:00'; }
            public function ozet(): string { return 'zaman'; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/trait-cakismasi/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
trait-cakismasi    1 dosya  cikis=255 cikti farkli -> kiran
once cikis=0 cikti=kayit 12:00
sonra cikis=255 cikti=
```

**NM15.** Kullanan dosyada tek satır değişmedi ve çıkış 255 oldu. Çakışma `use` satırında doğar: iki trait aynı adda yöntem getirince kullanan sınıf bildirilemez. Bu, çakışma çözümünün neden kullanana ait olduğunun bedelidir; trait yazarı yeni bir yöntem eklerken kimin `use` ettiğini göremez. Dersin ölçü sınıfı kırandır.

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

Her ekleme kırar mı? Eğer kullanan sınıf zaten kendi `ozet()` yöntemini taşıyorsa, trait'in aynı adlı yöntemleri arasındaki çakışma sınıfa ulaşmaz: sınıfın kendi yöntemi trait yöntemlerinin hepsini bastırır.

```php
<?php
// sinifin_kendisi.php — sinif ozet()'ini kendisi yazar, trait'e ayni ekleme yapilir
require_once 'yayilim.php';

olc('sinifin-kendisi', [
    'izler.php' => <<<'PHP'
        <?php
        trait Kayitli
        {
            public function ozet(): string { return 'kayit'; }
        }
        trait Zamanli
        {
            public function saat(): string { return '12:00'; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'izler.php';
        class Belge
        {
            use Kayitli, Zamanli;
            public function ozet(): string { return 'belge'; }
        }
        $belge = new Belge();
        echo $belge->ozet(), ' ', $belge->saat(), "\n";
        PHP,
], [
    'izler.php' => <<<'PHP'
        <?php
        trait Kayitli
        {
            public function ozet(): string { return 'kayit'; }
        }
        trait Zamanli
        {
            public function saat(): string { return '12:00'; }
            public function ozet(): string { return 'zaman'; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/sinifin-kendisi/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
sinifin-kendisi    1 dosya  cikis=0   cikti ayni   -> yerinde
once cikis=0 cikti=belge 12:00
sonra cikis=0 cikti=belge 12:00
```

**NM16.** Aynı ekleme, aynı trait dosyası; bu kez sınıf yerinde kaldı ve çıktı `belge 12:00` olarak sürdü. Kırılma `ozet()` adının sınıfa iki trait'ten birden ulaşmasından doğuyordu; sınıfın kendi tanımı o ulaşmayı kesti. Yani trait'e eklemenin kırıp kırmayacağı trait'te değil, kullananın `ozet()` adını zaten sahiplenip sahiplenmediğinde yatıyor.

#### Özet

- Trait yöntemleri `use` ile bir sınıfa katar, bir sınıf birden çok trait kullanabilir ve trait bir tip değildir.
- İki trait aynı adı getirirse kullanan sınıf `insteadof` ile seçer, `as` ile ikinci yönteme yeni ad verir.
- Kullanılan trait'e çakışan bir `ozet()` eklenince kullanan dosya hiç değişmeden durdu, çıkış 255; bu dersin ölçü sınıfı kırandır.
- Kullanan sınıf `ozet()` yöntemini kendisi yazıyorsa aynı ekleme yerinde kaldı.
- Çakışma çözümü kullanana aittir, bu yüzden trait yazarı yeni yöntem eklerken sınıfları görmez.

#### Sonraki Adım

Trait'ler yöntemleri paylaştırıyordu ama durum ve sabitler başka bir konudur. Statik Üyeler ve Sabitler dersi sınıfa bağlı üyeleri soruyor: bir nesne yaratma yolunu statik bir fabrika yöntemine taşımak ve yapıcıya bir parametre eklemek kullananı neden durdurmuyor?

### Statik Üyeler ve Sabitler

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/statik-uyeler-ve-sabitler

Sınıfa bağlı özellik, yöntem ve sabitler, statik fabrika yöntemiyle yapıcının gizlenmesi ve bunların kullananı hangi değişikliklerde durdurduğu.

Trait'ler yöntemleri paylaştırıyordu; bu dersin konusu ise bir nesneye değil sınıfın kendisine bağlı üyelerdir. Soru şudur: nesne yaratma yolu bir statik fabrika yöntemine taşınınca yapıcıya parametre eklemek kullananı neden durdurmuyor ve yapıcı doğrudan açık kalsaydı bu neden farklı olurdu?

#### Statik Üyeler

Statik (static) bir üye sınıfa bağlıdır ve nesne yaratılmadan `Sinif::` ile erişilir. Sınıfın kendi içinden aynı sınıfa `self::` ile erişilir. Statik özellik sınıf başına tek kopyadır ve bütün nesneler onu paylaşır. Sınıf sabiti (class constant) `const` ile bildirilir, değeri değişmez ve `self::ORAN` ya da `Vergi::ORAN` ile okunur.

```php
<?php
// Vergi.php — sinifa bagli sabit, ozellik ve yontem
class Vergi
{
    public const ORAN = 20;
    public static int $hesaplanan = 0;

    public static function ekle(int $tutar): int
    {
        self::$hesaplanan++;
        return $tutar + intdiv($tutar * self::ORAN, 100);
    }
}
```

```php
<?php
// vergi_kullan.php — statik uyelere nesne olmadan erisilir
require 'Vergi.php';

echo Vergi::ekle(100), "\n";
echo Vergi::ekle(200), "\n";
echo Vergi::ORAN, ' ', Vergi::$hesaplanan, "\n";
```

```
120
240
20 2
```

**NM17.** `Vergi` için hiçbir nesne yaratılmadı ve `ekle()` iki kez çalıştı; `$hesaplanan` o iki çağrıyı tek kopyada saydı. Bu paylaşım M11/K03'te kimin neyi gördüğü sorusuyla ölçülmüştü ve burada yeniden ölçülmüyor. Sınıfın alt sınıfta yeniden belirlenmesini isteyen `static::` kullanımı (geç statik bağlama) ise M08/K02'nin konusudur ve burada ölçülmüyor.

#### Statik Fabrika Yöntemi

Statik fabrika yöntemi (static factory method), bir nesneyi yaratan statik bir yöntemdir. Yapıcı `private` yapılınca nesne yalnız bu yöntemden doğar. Böylece sınıf, nesnenin hangi değerlerle yaratılabileceğini kendisi sınırlar.

```php
<?php
// Para.php — yapicisi gizli, nesneyi statik fabrika uretir
class Para
{
    private function __construct(private int $kurus) {}

    public static function tl(int $tl): self
    {
        return new self($tl * 100);
    }

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

```php
<?php
// para_kullan.php — nesne yalniz fabrika yoluyla yaratilir
require 'Para.php';

echo Para::tl(5)->kurus(), "\n";

try {
    $para = new Para(500);
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
500
Error
```

**NM18.** Fabrika `5` lirayı `500` kuruşa çevirdi, doğrudan `new Para(500)` ise `Error` verdi çünkü yapıcı kapalıdır. Kullananın tek yolu `Para::tl()` oldu. Bu dersin sorusu bu tek yolun neden değiştirilebilir olduğudur.

#### Nesne Bağlamı ve Statik Bağlam

Örnek yöntemi bir nesnenin üzerinde çalışır ve `$this` o nesnedir. Statik yöntemde ise nesne yoktur, bu yüzden `$this` kullanılamaz; yalnız statik üyelere `self::` ile ulaşılır. Aynı sınıf iki bağlamı birlikte taşıyabilir: örnek özelliği her nesnede ayrı, statik özellik tek kopyadır.

```php
<?php
// Sayac.php — ornek ozelligi ve statik ozellik bir arada
class Sayac
{
    private static int $toplam = 0;
    private int $yerel = 0;

    public function say(): string
    {
        self::$toplam++;
        $this->yerel++;
        return $this->yerel . '/' . self::$toplam;
    }

    public static function toplam(): int
    {
        return self::$toplam;
    }

    public static function bozuk(): int
    {
        return $this->yerel;
    }
}
```

```php
<?php
// sayac_kullan.php — iki nesnenin yerel sayaci ayri, toplam ortak
require 'Sayac.php';

$birinci = new Sayac();
$ikinci = new Sayac();

echo $birinci->say(), ' ', $birinci->say(), ' ', $ikinci->say(), "\n";
echo Sayac::toplam(), "\n";

try {
    Sayac::bozuk();
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
1/1 2/2 1/3
3
Error
```

`$ikinci` kendi sayacında `1` gördü, ama toplamda `3`: ilk nesnenin iki çağrısı da aynı statik özelliğe yazılmıştı. Statik yöntemde `$this` aranınca `Error` doğdu. Bir sınıfın alt sınıflarda farklı çözülen statik çağrısı için `static::` kullanılır; geç statik bağlama denen bu davranış M08/K02'de ölçülmüştür ve burada yinelenmez.

#### Sınıf Sabitinin Görünürlüğü

Sınıf sabiti de özellik ve yöntem gibi görünürlük taşır. `public const` her yerden okunur, `private const` yalnız sınıfın kendi içinden. Sabit tanımı başka bir sabite dayanabilir, ama fonksiyon çağrısı gibi sabit olmayan bir ifadeye dayanamaz.

```php
<?php
// Ayarlar.php — farkli gorunurlukte sabitler
class Ayarlar
{
    public const AD = 'panel';
    private const SIR = 5;
    public const KATSAYI = self::SIR * 2;

    public static function sir(): int
    {
        return self::SIR;
    }
}
```

```php
<?php
// ayarlar_kullan.php — private sabite sinif disindan ulasilamaz
require 'Ayarlar.php';

echo Ayarlar::AD, ' ', Ayarlar::KATSAYI, ' ', Ayarlar::sir(), "\n";

try {
    echo Ayarlar::SIR;
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
panel 10 5
Error
```

`KATSAYI` sınıf içindeki `SIR`'den türedi ve dışarıdan okunabildi; `SIR` ise yalnız `sir()` yöntemi üzerinden dışarı çıktı. Bu, Sınıflar ve Nesneler dersindeki özellik görünürlüğünün sabit için aynen geçerli olduğunu gösterir: sabiti `private` yapmak, değerdeki bir değişikliğin hangi kullananlara ulaşabileceğini sınırlar.

#### Ö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
<?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;
}
```

#### Fabrikaya Parametre Eklenir

Ölçülen değişiklik, `Para` sınıfının yapıcısına `birim` adında ikinci bir parametre eklemektir. Fabrika bu parametreyi kendisi sağlar: `tl()` yapıcıya `'TL'` verir. Kullanan `main.php` `Para::tl(5)->kurus()` değerini yazdırır.

```php
<?php
// fabrika.php — yapiciya parametre eklenir, fabrika degismeden kullanana ayni degeri verir
require_once 'yayilim.php';

olc('statik-fabrika', [
    'para.php' => <<<'PHP'
        <?php
        class Para
        {
            private function __construct(private int $kurus) {}
            public static function tl(int $tl): self { return new self($tl * 100); }
            public function kurus(): int { return $this->kurus; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'para.php';
        echo Para::tl(5)->kurus(), "\n";
        PHP,
], [
    'para.php' => <<<'PHP'
        <?php
        class Para
        {
            private function __construct(private int $kurus, private string $birim) {}
            public static function tl(int $tl): self { return new self($tl * 100, 'TL'); }
            public function kurus(): int { return $this->kurus; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/statik-fabrika/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
statik-fabrika     1 dosya  cikis=0   cikti ayni   -> yerinde
once cikis=0 cikti=500
sonra cikis=0 cikti=500
```

**NM19.** Yapıcının imzası değişti, ama kullananın gördüğü şey `Para::tl(5)->kurus()` olduğu için çıktı `500` olarak kaldı. Yapıcıyı yalnız fabrika çağırıyordu ve fabrikayı yazan taraf yeni parametreyi kendi içinde sağladı. Bu dersin birinci ölçü sınıfı yerindedir.

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

Yerinde kalışın nedeni fabrika mı yoksa değişikliğin kendisi mi? Yapıcıyı açık bırakıp kullananın `new Para(500)` demesini sağlayarak ayırabiliriz: aynı parametre eklenince kullanan doğrudan yapıcıya bağlıdır.

```php
<?php
// acik_yapici.php — yapici acik, kullanan new ile dogrudan cagiriyor
require_once 'yayilim.php';

olc('acik-yapici', [
    'para.php' => <<<'PHP'
        <?php
        class Para
        {
            public function __construct(private int $kurus) {}
            public function kurus(): int { return $this->kurus; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'para.php';
        echo (new Para(500))->kurus(), "\n";
        PHP,
], [
    'para.php' => <<<'PHP'
        <?php
        class Para
        {
            public function __construct(private int $kurus, private string $birim) {}
            public function kurus(): int { return $this->kurus; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/acik-yapici/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
acik-yapici        1 dosya  cikis=255 cikti farkli -> kiran
once cikis=0 cikti=500
sonra cikis=255 cikti=
```

**NM20.** Aynı eklenen parametre, aynı sınıf; ama kullanan yapıcıyı kendisi çağırınca ikinci parametre zorunlu hâle geldi ve çıkış 255 oldu. Demek ki yerinde kalışı sağlayan parametrenin kendisi değil, kullananın yapıcıya hiç dokunamamasıydı. Fabrika yapıcı üzerindeki bağımlılığı tek bir noktaya toplar.

Sınıf sabiti ise başka bir sınıfa girer. Sınıf sabitinin değeri değişirse kullananı durduran bir şey yoktur, çünkü tip ve imza aynı kalır.

```php
<?php
// sabit.php — sinif sabitinin degeri degisir, imza ayni kalir
require_once 'yayilim.php';

olc('sinif-sabiti', [
    'vergi.php' => <<<'PHP'
        <?php
        class Vergi
        {
            public const ORAN = 20;
            public static function ekle(int $tutar): int { return $tutar + intdiv($tutar * self::ORAN, 100); }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'vergi.php';
        echo Vergi::ekle(100), "\n";
        PHP,
], [
    'vergi.php' => <<<'PHP'
        <?php
        class Vergi
        {
            public const ORAN = 18;
            public static function ekle(int $tutar): int { return $tutar + intdiv($tutar * self::ORAN, 100); }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/sinif-sabiti/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
sinif-sabiti       1 dosya  cikis=0   cikti farkli -> ortuk
once cikis=0 cikti=120
sonra cikis=0 cikti=118
```

**NM21.** `ORAN` `20`'den `18`'e indi, kullanan hata almadı, ama aldığı değer `120`'den `118`'e kaydı: çıkış 0, çıktı farklı. Sabitin değeri bir sözleşme sayılmadığı için hiçbir yer durmadı. Bu ölçü sınıfı örtüktür.

#### Alt Sınıfta Statik Özellik

Alt sınıf, üst sınıfın statik özelliğini miras alır ama yeni bir kopya almaz: ikisi aynı depolamayı paylaşır. Alt sınıf özelliği kendi gövdesinde yeniden tanımlarsa ayrı bir kopya doğar. İkisi arasındaki fark, sayaç ya da kayıt defteri gibi yapıları kalıtımla genişletirken sonuç doğurur.

```php
<?php
// Paylasim.php — ust sinifin statik ozelligi alt sinifla paylasilir
class Taban
{
    public static int $adet = 0;
}

class Ortak extends Taban
{
}

class Ayri extends Taban
{
    public static int $adet = 0;
}
```

```php
<?php
// paylasim_kullan.php — Ortak Taban ile paylasir, Ayri paylasmaz
require 'Paylasim.php';

Ortak::$adet = 5;
echo Taban::$adet, "\n";

Ayri::$adet = 9;
echo Taban::$adet, ' ', Ayri::$adet, "\n";
```

```
5
5 9
```

`Ortak::$adet` yazımı aslında `Taban`'ın özelliğine yazdı; bu yüzden `Taban::$adet` de `5` verdi. `Ayri` özelliği yeniden tanımladığı için kendi değerini tuttu ve `Taban`'ı etkilemedi. Alt sınıflara ayrı sayaç vermek isteyen kod, özelliği her alt sınıfta yeniden tanımlamak zorundadır; unutulursa sayaçlar sessizce birleşir.

#### Statik Üye ve Sınama Güçlüğü

Statik üyenin ortaklığı sınıf adıyla her yerden erişilebildiği için, bir kodun ne durumda çalışacağı önceki çağrılara bağlanır. Sınama senaryoları birbirinden bağımsız olmalıdır; statik durum bu bağımsızlığı bozar. Aşağıdaki iki senaryo ayrı ayrı yazılmıştır ama ikincisi birincinin bıraktığı veriyi görür.

```php
<?php
// Onbellek.php — sinif adiyla erisilen ortak durum
class Onbellek
{
    private static array $veri = [];

    public static function koy(string $anahtar, int $deger): void
    {
        self::$veri[$anahtar] = $deger;
    }

    public static function say(): int
    {
        return count(self::$veri);
    }
}
```

```php
<?php
// senaryo.php — ikinci senaryo birincinin biraktigi veriyi gorur
require 'Onbellek.php';

function birinciSenaryo(): int
{
    Onbellek::koy('a', 1);
    return Onbellek::say();
}

function ikinciSenaryo(): int
{
    return Onbellek::say();
}

echo birinciSenaryo(), ' ', ikinciSenaryo(), "\n";
```

```
1 1
```

`ikinciSenaryo()` hiçbir şey koymadı ve yine de `1` gördü. Tek tek çalışsa `0` görecekti; sırayla çalışınca sonucu çalıştırma sırası belirliyor. Aynı veriyi bir nesnenin özelliği olarak tutup nesneyi dışarıdan vermek bu bağımlılığı koparır: her senaryo kendi nesnesini yaratır ve başkasının bıraktığını görmez. Bağımlılığı dışarıdan vermek kurs kapanışında ayrıca ele alınacak; bu ders yalnız statik durumun neden bu çözümü gerektirdiğini gösterdi.

#### Özet

- Statik üye sınıfa bağlıdır; `self::` ile erişilir, statik özellik sınıf başına tek kopyadır ve sınıf sabiti değişmez.
- Yapıcıyı gizleyip nesneyi statik fabrika yöntemiyle yaratmak, nesne yaratma yolunu tek noktaya toplar.
- Yapıcıya parametre eklenince fabrikadan yaratan kullanan yerinde kaldı, çıktı `500`; bu dersin ölçü sınıfı yerindedir.
- Aynı ekleme yapıcı açıkken kullanan `new Para(500)` diyorsa kıran oldu, çıkış 255.
- Sınıf sabitinin değeri `20`'den `18`'e inince kullanan örtük etkilendi: çıkış 0, çıktı `120`'den `118`'e kaydı.

#### Sonraki Adım

Sabitin değeri kullananı sessizce kaydırdı; sihirli yöntemler ise okumanın kendisini bir yönteme çevirir. Sihirli Yöntemler dersi bir özelliğin okunduğu `__get` yönteminin içeriği değişince kullananın ne gördüğünü soruyor.

### Sihirli Yöntemler

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/sihirli-yontemler

__get, __set, __isset, __call ve __toString yöntemleri, okunan bir adın yöntemden çözülmesi ve bu çözümün değişince kullananın uğradığı örtük ve kıran sonuçlar.

Statik Üyeler dersinde sabitin değeri değişince kullanan hata almadan kaydı. Sihirli yöntemlerde (magic method) aynı sessizlik daha büyük bir alana yayılır, çünkü bir özelliği okumak ya da bir yöntemi çağırmak artık sınıfın içindeki bir yönteme çevrilebilir. Soru, bu çevirinin iç adı değişince kullananın neyi gördüğüdür.

#### Sihirli Yöntemler

Sihirli yöntemler adı iki alt çizgiyle başlayan, PHP'nin belirli durumlarda kendisi çağırdığı yöntemlerdir. `__get` olmayan ya da erişilemeyen bir özellik okunduğunda, `__set` yazıldığında, `__isset` `isset()` ile sorulduğunda, `__call` olmayan bir yöntem çağrıldığında, `__toString` nesne metin olarak kullanıldığında çalışır.

```php
<?php
// Ayar.php — okuma, yazma, sorma, yontem cagrisi ve metne cevirmeyi yakalayan sinif
class Ayar
{
    private array $degerler = [];

    public function __set(string $ad, mixed $deger): void
    {
        $this->degerler[$ad] = $deger;
    }

    public function __get(string $ad): mixed
    {
        return $this->degerler[$ad] ?? null;
    }

    public function __isset(string $ad): bool
    {
        return isset($this->degerler[$ad]);
    }

    public function __call(string $ad, array $arguman): string
    {
        return $ad . '(' . count($arguman) . ')';
    }

    public function __toString(): string
    {
        return 'Ayar[' . count($this->degerler) . ']';
    }
}
```

```php
<?php
// ayar_kullan.php — sihirli yontemler kullananin yazdigi ifadelerden tetiklenir
require 'Ayar.php';

$ayar = new Ayar();
$ayar->tema = 'koyu';
echo $ayar->tema, "\n";
var_dump(isset($ayar->tema), isset($ayar->dil));
echo $ayar->kaydet(1, 2), "\n";
echo $ayar, "\n";
```

```
koyu
bool(true)
bool(false)
kaydet(2)
Ayar[1]
```

**NM22.** `tema` diye bir özellik tanımlı değildi ve kullanan yine de yazıp okuyabildi: `__set` değeri `$degerler` dizisine koydu, `__get` oradan çıkardı. `kaydet()` diye bir yöntem de yoktu, `__call` adı ve argüman sayısını geri verdi. Bu, Sınıflar ve Nesneler dersindeki dinamik özellikten farklıdır: orada özellik nesnenin üzerine kuruluyordu ve bir kullanımdan kaldırma uyarısı (`E_DEPRECATED`) doğuruyordu, burada ise tanımlı bir yöntem karşılıyor.

#### __unset ve __callStatic

`__unset`, erişilemeyen bir özellik `unset()` ile silinmek istendiğinde çalışır; `__isset` ile birlikte yazılır, çünkü ikisi aynı veri yapısına bakar. `__callStatic` ise `__call`'ın statik karşılığıdır: sınıfta olmayan bir yöntem `Sinif::ad()` biçiminde çağrıldığında devreye girer. İkisi de adı ve argümanları yöntemin gövdesine verir.

```php
<?php
// Kayit.php — silmeyi ve statik cagriyi yakalayan sinif
class Kayit
{
    private array $veri = ['a' => 1, 'b' => 2];

    public function __isset(string $ad): bool
    {
        return isset($this->veri[$ad]);
    }

    public function __unset(string $ad): void
    {
        unset($this->veri[$ad]);
    }

    public static function __callStatic(string $ad, array $arguman): string
    {
        return 'statik ' . $ad . '(' . count($arguman) . ')';
    }
}
```

```php
<?php
// kayit_kullan.php — unset ve statik cagri sihirli yontemlere gider
require 'Kayit.php';

$kayit = new Kayit();
var_dump(isset($kayit->a));
unset($kayit->a);
var_dump(isset($kayit->a));
echo Kayit::bul(7), "\n";
```

```
bool(true)
bool(false)
statik bul(1)
```

`$veri` özel olduğu için `$kayit->a` dışarıdan erişilemezdi; `isset` ve `unset` bu yüzden sihirli yöntemlere düştü ve dizideki anahtarı sildi. `__unset` yazılmasaydı `unset($kayit->a)` hiçbir şey yapmadan geçerdi.

#### Sihirli Yöntem Yalnız Erişilemeyen Adda Çalışır

Sihirli okuma yöntemi bir kısayol değildir: PHP önce adı sınıfın gerçek özelliklerinde arar, bulursa yöntemi hiç çağırmaz. `__get` yalnız ad yoksa ya da çağıran yerden görünmüyorsa çalışır. Bu ayrım, `public` bir özelliği sonradan sihirli yönteme taşımanın kullananı nasıl etkilediğini belirler.

```php
<?php
// Kasa.php — gercek ozellik varken __get calismaz
class Kasa
{
    public string $acik = 'a';
    private string $gizli = 'g';

    public function __get(string $ad): string
    {
        return 'sihirli:' . $ad;
    }
}
```

```php
<?php
// kasa_kullan.php — __get yalniz erisilemeyen adda calisir
require 'Kasa.php';

$kasa = new Kasa();
echo $kasa->acik, ' ', $kasa->gizli, ' ', $kasa->yok, "\n";

unset($kasa->acik);
echo $kasa->acik, "\n";
```

```
a sihirli:gizli sihirli:yok
sihirli:acik
```

`acik` gerçek ve görünür olduğu için sihirli yönteme uğramadı; `gizli` dışarıdan görünmediği, `yok` hiç olmadığı için uğradı. `acik` silindikten sonra ise adı gerçek özellik olarak bulunamadı ve `__get` devreye girdi. Bu davranış, sihirli yöntemin gerçek özelliğin yerini sessizce alabileceğini gösterir.

#### __toString ve Stringable

`__toString` nesne bir metin bağlamına girdiğinde çalışır: `echo`, birleştirme, metin bekleyen bir fonksiyon parametresi. PHP 8'de bu yöntemi tanımlayan her sınıf, `Stringable` arayüzünü kendiliğinden gerçekleştirmiş sayılır; arayüz bildirimine gerek yoktur. Yöntemi olmayan bir nesne metin bağlamına girerse `Error` doğar.

```php
<?php
// Etiket.php — __toString ve Stringable
class Etiket
{
    public function __construct(private string $ad)
    {
    }

    public function __toString(): string
    {
        return '<' . $this->ad . '>';
    }
}

class Susuz
{
}

$etiket = new Etiket('b');
echo $etiket, ' ', strlen($etiket), ' ', $etiket instanceof Stringable ? 'evet' : 'hayir', "\n";
echo 'Metin: ' . $etiket . "\n";

try {
    echo new Susuz();
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
<b> 3 evet
Metin: <b>
Error
```

`strlen()` nesneyi metne çevirip `<b>` üzerinden `3` saydı. `Stringable` bildirimi hiçbir yerde yazılmadığı hâlde `instanceof` doğru çıktı. Sınıfa `__toString` eklemek, o sınıfın nesnelerini metin bekleyen her yere sokar; bu kolaylık, dönüşümün biçimini, kullananın söz hakkı olmadan, sınıfın tek başına belirlemesi demektir.

#### __set ve Yazım Hatası

`__set` her bilinmeyen adı kabul ettiğinde, adı yanlış yazan kod hata almaz. Yanlış yazılmış ad dizide yeni bir anahtar olur; doğru ad ise eski değerinde kalır. Okuma tarafında `?? null` bırakılmışsa yanlış adı okumak da sessizce `null` verir.

```php
<?php
// Esnek.php — her adi kabul eden __set ve __get
class Esnek
{
    public string $baslik = '';
    private array $ek = [];

    public function __set(string $ad, mixed $deger): void
    {
        $this->ek[$ad] = $deger;
    }

    public function __get(string $ad): mixed
    {
        return $this->ek[$ad] ?? null;
    }
}

$esnek = new Esnek();
$esnek->baslik = 'ilk';
$esnek->baslk = 'ikinci';
echo $esnek->baslik, ' ', $esnek->baslk, "\n";
var_dump($esnek->baslikk);
```

```
ilk ikinci
NULL
```

`baslk` yazım hatasıydı ve `__set` onu sorgusuz kabul etti; `baslik` ilk değerinde kaldı. `baslikk` okunduğunda da hata çıkmadı. Sihirli yöntemi yazan taraf kabul edilen adları bir listeyle sınırlamazsa, kullanan yazım denetimini kaybeder.

#### __call ile Yönlendirme

`__call` sık kullanılan bir amaca hizmet eder: başka bir nesneye yönlendirmek. Sarmalayan sınıf bilmediği her yöntemi içindeki nesneye aktarır. Kullanan sarmalayıcıyı çağırır, ama aslında içerideki nesnenin yöntemi çalışır; içerideki nesnede de olmayan bir ad ise yine `Error` doğurur.

```php
<?php
// Sarmal.php — bilinmeyen yontemi ic nesneye yonlendiren sinif
class Sarmal
{
    public function __construct(private ArrayObject $ic)
    {
    }

    public function __call(string $ad, array $arguman): mixed
    {
        return $this->ic->$ad(...$arguman);
    }
}

$sarmal = new Sarmal(new ArrayObject([3, 1, 2]));
echo $sarmal->count(), "\n";

try {
    $sarmal->yoktur();
} catch (Error $e) {
    echo get_class($e), "\n";
}
```

```
3
Error
```

`count()` sarmalayıcıda tanımlı değildi ve yönlendirme sayesinde `ArrayObject` tarafından yanıtlandı. Bu yönlendirme, sarmalanan sınıfın yöntemlerini sarmalayıcının bildiriminden gizler; hangi adların çalıştığı kodu okuyarak değil çalıştırarak anlaşılır.

#### __invoke

`__invoke` bir nesneyi fonksiyon gibi çağırmaya izin verir. Nesne `$nesne(argüman)` biçiminde çağrılır ve PHP yöntemin gövdesini çalıştırır. Nesne bu yüzden `callable` kabul edilir: `array_map` gibi bir fonksiyona geri çağrı olarak verilebilir. Durum taşıyan bir dönüşüm gerektiğinde, bu özellik nesnenin özelliklerini çağrının yanında götürür.

```php
<?php
// Carpan.php — nesneyi islev gibi cagirmak
class Carpan
{
    public function __construct(private int $katsayi)
    {
    }

    public function __invoke(int $sayi): int
    {
        return $sayi * $this->katsayi;
    }
}

$uc = new Carpan(3);
echo $uc(4), ' ', implode(',', array_map($uc, [1, 2, 3])), ' ', var_export(is_callable($uc), true), "\n";
```

```
12 3,6,9 true
```

`$uc(4)` doğrudan `__invoke(4)` oldu; `array_map` nesneyi dizinin her öğesi için çağırdı. Katsayı nesnede durduğu için dönüşümü farklı katsayıyla yeniden kullanmak, yeni bir nesne yaratmaktan ibaret kaldı.

#### Ö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
<?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;
}
```

#### İç Ad Değişir

Ölçülen değişiklik, `Ayar` sınıfının iç dizisindeki `tema` anahtarını `gorunum` olarak yeniden adlandırmaktır. `__get` ve sınıfın başka hiçbir yeri değişmez. Kullanan `main.php` `(new Ayar())->tema` değerini `var_dump` ile yazdırır.

```php
<?php
// sihirli_okuma.php — __get'in baktigi ic anahtar yeniden adlandirilir
require_once 'yayilim.php';

olc('sihirli-okuma', [
    'ayar.php' => <<<'PHP'
        <?php
        class Ayar
        {
            private array $degerler = ['tema' => 'koyu'];
            public function __get(string $ad): mixed { return $this->degerler[$ad] ?? null; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'ayar.php';
        var_dump((new Ayar())->tema);
        PHP,
], [
    'ayar.php' => <<<'PHP'
        <?php
        class Ayar
        {
            private array $degerler = ['gorunum' => 'koyu'];
            public function __get(string $ad): mixed { return $this->degerler[$ad] ?? null; }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/sihirli-okuma/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
sihirli-okuma      1 dosya  cikis=0   cikti farkli -> ortuk
once cikis=0 cikti=string(4) "koyu"
sonra cikis=0 cikti=NULL
```

**NM23.** Kullanan hiçbir şey görmedi: çıkış 0 kaldı, ama `string(4) "koyu"` yerine `NULL` geldi. `__get` bilinmeyen adı `null` ile karşılıyordu, yani `tema` artık yok ve bunu söyleyen bir şey olmadı. Bu dersin birinci ölçü sınıfı örtüktür. Sihirli yöntem, okumayı bir sözleşmeden çıkarıp bir arama işlemine çevirdiği için yeniden adlandırma hiçbir imzayı bozmaz.

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

Sessizliğin nedeni yeniden adlandırma mı, yoksa `__get`'in bilinmeyen adı `null` ile karşılaması mı? İkinci seçenek ölçülür: aynı yeniden adlandırma yapılır, ama bu kez `__get` bilinmeyen bir adda `OutOfRangeException` fırlatır.

```php
<?php
// bilinmeyen_ad.php — ayni yeniden adlandirma, bu kez __get bilinmeyen adi reddediyor
require_once 'yayilim.php';

olc('bilinmeyen-ad', [
    'ayar.php' => <<<'PHP'
        <?php
        class Ayar
        {
            private array $degerler = ['tema' => 'koyu'];
            public function __get(string $ad): mixed
            {
                if (!array_key_exists($ad, $this->degerler)) {
                    throw new OutOfRangeException($ad);
                }
                return $this->degerler[$ad];
            }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'ayar.php';
        var_dump((new Ayar())->tema);
        PHP,
], [
    'ayar.php' => <<<'PHP'
        <?php
        class Ayar
        {
            private array $degerler = ['gorunum' => 'koyu'];
            public function __get(string $ad): mixed
            {
                if (!array_key_exists($ad, $this->degerler)) {
                    throw new OutOfRangeException($ad);
                }
                return $this->degerler[$ad];
            }
        }
        PHP,
]);

foreach (['once', 'sonra'] as $asama) {
    [$kod, $cikti] = kostur("olcum/bilinmeyen-ad/$asama");
    echo $asama, ' cikis=', $kod, ' cikti=', $cikti, "\n";
}
```

```
bilinmeyen-ad      1 dosya  cikis=255 cikti farkli -> kiran
once cikis=0 cikti=string(4) "koyu"
sonra cikis=255 cikti=
```

**NM24.** Aynı yeniden adlandırma, aynı kullanan; ancak çıkış 255 oldu, çünkü `tema` adı artık bilinmiyordu ve `__get` bunu bir istisnayla bildirdi. Sessizliği doğuran yeniden adlandırma değil, `__get`'in bilinmeyen adı geçiştirmesiydi. Bir sihirli yöntemin yanıtı sınıf yazarının kararıdır: aynı değişiklik `null` ile örtük, istisnayla kıran olabiliyor.

#### Özet

- Sihirli yöntemler `__get`, `__set`, `__isset`, `__call` ve `__toString` gibi adları iki alt çizgiyle başlayan ve PHP'nin kendisinin çağırdığı yöntemlerdir.
- Okuma, yazma ve yöntem çağrısı sihirli yönteme çevrilince tanımsız ad kullananı durdurmaz.
- `__get`'in baktığı iç anahtar `tema`'dan `gorunum`'a inince kullanan hata almadı, çıkış 0; `string(4) "koyu"` yerine `NULL` geldi, dersin ölçü sınıfı örtüktür.
- `__get` bilinmeyen adda `OutOfRangeException` fırlatınca aynı değişiklik kıran oldu, çıkış 255.
- Aynı değişikliğin örtük mü kıran mı olduğunu sihirli yöntemin bilinmeyen adı nasıl karşıladığı belirler.

#### Sonraki Adım

Sihirli yöntemler gevşek bir çevirici koyuyordu; tip bildirimleri tam tersini yapar ve sınıfın sınırına bir parametre, bir dönüş, bir özellik tipi koyar. Tip Bildirimleri dersi bir yöntemin parametresine `int` yazmanın kullananı hangi girdilerde durdurduğunu soruyor.

### Tip Bildirimleri

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/tip-bildirimleri

Parametre, dönüş ve özellik tipleri, ?int ve birleşim tipi ve bir parametreye tip eklemenin kullananı hangi girdilerde durdurduğu.

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
<?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
<?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
<?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
<?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
<?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
<?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
<?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.

```bash
# 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
<?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
<?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
<?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
<?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.

## Standartlar ve Paketler

Ad alanları, otomatik yükleme, ortak arayüz standartları, bağımlılık yöneticisi ve bağımlılık enjeksiyonu — bir değişikliği tek bir bağlantı noktasında toplayan ya da onu bütün kullananlara taşıyan düzenler.

### Ad Alanları

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/ad-alanlari

namespace, use ve takma ad ile tam nitelikli ad; aynı adlı iki sınıfın ad alanıyla ayrılması ve ad alanı olmayan kodda aynı çakışmanın programı durdurması; nitelenmemiş fonksiyon çağrısının küresel fonksiyona düşmesi.

Tip Bildirimleri dersinde değişiklik tanımın kendi içinden geldi: bir parametreye tip eklendi ve eski çağrı bunun bedelini ödedi. Bu derste değişiklik tanımın dışından geliyor. Başka biri, bizim sınıfımızla aynı adı taşıyan bir sınıf ekliyor. Kurs boyunca sorulan soru değişmiyor: **bu değişiklik nereye ulaşıyor?** Yanıt dört sınıftan biri: **yerinde**, **bağlantıda**, **kıran**, **örtük**.

M09/K02 bir adın bağlandığı yeri arıyordu: bildirimde mi, kullanım yerinde mi, değerde mi. Ad alanı o bağın kurulduğu yere bir önek ekler. Bu derste ise bağ değişince kaç dosyanın düzenlendiği ve düzenlenmemiş kodun ne yaşadığı ölçülür.

#### Ad Alanı, use ve Tam Nitelikli Ad

Ad alanı (namespace), adlara önek veren bir kapsamdır. `namespace Magaza;` ile açılan dosyadaki sınıf `Kayit` değil, `Magaza\Kayit` adını taşır. Önekli bu uzun biçime **tam nitelikli ad** denir. `use` bir tam nitelikli adı dosya içinde kısa adla anılabilir yapar; `as` ise aynı kısa ada sahip iki sınıf için takma ad verir.

```php
<?php
// adlar.php — ad alani, tam nitelikli ad ve takma ad
namespace Magaza {
    class Kayit {}
}
namespace Dis {
    class Kayit {}
}
namespace {
    use Magaza\Kayit;
    use Dis\Kayit as DisKayit;

    echo Kayit::class, "\n";
    echo DisKayit::class, "\n";
    echo \Dis\Kayit::class, "\n";
    echo get_class(new DisKayit()), "\n";
}
```

```
Magaza\Kayit
Dis\Kayit
Dis\Kayit
Dis\Kayit
```

İki sınıfın kısa adı aynı, tam nitelikli adları farklı. `Kayit` ismi `Magaza\Kayit`'e, `DisKayit` takma adı `Dis\Kayit`'e bağlandı; ters eğik çizgiyle başlayan üçüncü satır `use` olmadan tam adı yazıyor. `get_class` ise nesnenin gerçek adını tam nitelikli biçimde döndürüyor. Ad alanı bir klasör değil, bir ad önekidir: sınıfın hangi dosyada durduğunu söylemez.

Ad alanının içinde yazılan nitelenmemiş bir sınıf adı, o ad alanının sınıfı sayılır. PHP'nin kendi sınıfları küresel ad alanında durur; bu yüzden ad alanı açıkken onlara da tam adla ya da `use` ile ulaşılır.

```php
<?php
// kok.php — ad alaninda nitelenmemis sinif adi kuresele dusmez
namespace Magaza;

echo Exception::class, "\n";
echo \Exception::class, "\n";

try {
    new Exception('x');
} catch (\Error $hata) {
    echo get_class($hata), "\n";
}
echo get_class(new \Exception('x')), "\n";
```

```
Magaza\Exception
Exception
Error
Exception
```

`Exception` yazıldığında PHP onu `Magaza\Exception` diye okudu; böyle bir sınıf olmadığı için `new` bir `Error` fırlattı ve `catch` onu yakaladı. Başına ters eğik çizgi konan ad küresel sınıfı seçti. Sınıflar için kural böyle: ad alanında bulunamayan sınıf adı küresele **düşmez**. Fonksiyonlar için kural farklı; bu dersin sınırlayıcı ölçümü tam olarak o fark.

#### Ad Çözümleme: Nitelenmemiş, Nitelenmiş, Tam Nitelikli

PHP bir adı üç biçimde görür ve her biri farklı çözülür. **Nitelenmemiş** ad ters eğik çizgi içermez (`Para`). **Nitelenmiş** ad içerir ama başında çizgi yoktur (`Alt\Para`). **Tam nitelikli** ad küresel ad alanından başlar ve başında çizgi taşır (`\Para`). Çözümleme sırası kısadır. Tam nitelikli ad olduğu gibi kalır. Nitelenmemiş ad önce `use` ile içe alınmış mı diye bakılır, alınmamışsa bulunulan ad alanına eklenir. Nitelenmiş adın ilk parçası `use` takma adıysa o parça takma adın gösterdiği adla değiştirilir, değilse yine bulunulan ad alanının altına yazılır. `namespace\Para` biçimi ise açıkça bulunulan ad alanını seçer.

Bu çözümleme derleme sırasında yapılır ve sınıfın var olmasını gerektirmez. `::class` yalnız çözülmüş adı verir, bu yüzden aşağıdaki sürücüde hiçbir sınıf tanımlanmadan beş biçimin sonucu yan yana görülebilir.

```php
<?php
// cozum.php — bes yazilis bicimi ve cozulmus adlari
namespace Magaza\Siparis;

use Magaza\Arac as A;

echo 'nitelenmemis   : ', Para::class, "\n";
echo 'nitelenmis     : ', Alt\Para::class, "\n";
echo 'takma adli     : ', A\Para::class, "\n";
echo 'tam nitelikli  : ', \Para::class, "\n";
echo 'namespace\ oneki: ', namespace\Para::class, "\n";
```

```
nitelenmemis   : Magaza\Siparis\Para
nitelenmis     : Magaza\Siparis\Alt\Para
takma adli     : Magaza\Arac\Para
tam nitelikli  : Para
namespace\ oneki: Magaza\Siparis\Para
```

Beş satırın dördü bulunulan ad alanına ya da takma adın gösterdiği yere gitti, yalnız ters eğik çizgiyle başlayan ad küresele çıktı. Bu, kodun başka bir ad alanına taşındığında ne olacağını da söyler: nitelenmemiş ve nitelenmiş adlar taşıyan dosyanın ad alanıyla birlikte kayar, tam nitelikli adlar yerinde kalır. Sınıflar için çözümleme burada biter. Fonksiyon ve sabitlerde bir adım daha vardır: bulunulan ad alanında bulunamayan nitelenmemiş ad küresele düşer. Sınırlayıcı ölçüm bu farkın sonucunu ölçüyor.

#### Gruplu use, use function ve use const

Bir dosyada birden çok `use` olabilir ve aynı ad alanından gelen adlar süslü parantezle tek satırda toplanabilir. Sınıflar için düz `use` yeterlidir; fonksiyon ve sabit için ayrı anahtar sözcük gerekir: `use function` ve `use const`. Düz `use` bir fonksiyonu ya da sabiti içe almaz.

```php
<?php
// kullan.php — gruplu use, use function ve use const
namespace Magaza\Arac {
    const KDV = 20;
    function vergili(int $fiyat): int { return $fiyat + intdiv($fiyat * KDV, 100); }
    class Yuvarla {}
    class Para {}
}
namespace {
    define('GENEL', 'kuresel');
}
namespace Uygulama {
    use Magaza\Arac\{Yuvarla, Para as Tutar};
    use function Magaza\Arac\vergili;
    use const Magaza\Arac\KDV;

    echo vergili(100), ' ', KDV, "\n";
    echo Yuvarla::class, ' ', Tutar::class, "\n";
    echo GENEL, "\n";
}
```

```
120 20
Magaza\Arac\Yuvarla Magaza\Arac\Para
kuresel
```

İlk satır iki içe almayı gösteriyor: `vergili` fonksiyonu ve `KDV` sabiti ad alanı yazılmadan çağrıldı. İkinci satırda gruplu `use` iki sınıfı tek ifadeyle aldı ve `Para` sınıfına grup içinde `Tutar` takma adı verildi. Üçüncü satır küresel bir sabitin, hiçbir `use` olmadan, ad alanı içinden okunabildiğini gösteriyor: sabitler de fonksiyonlar gibi küresele düşer. Sınıflar düşmez.

#### namespace Bildiriminin Yeri ve Dizinden Bağımsızlık

`namespace` bildirimi dosyanın ilk deyimi olmalıdır; yalnız `declare` ondan önce gelebilir. Önüne yazılan bir `echo` bile dosyayı derleme aşamasında durdurur. Aşağıdaki sürücü bunu ayrı bir PHP sürecinde dener ve yalnız çıkış kodunu basar. Aynı sürücü ikinci bir şeyi de gösterir: `Magaza\Model` ad alanındaki sınıf, adıyla hiçbir ilgisi olmayan `derin/ic` dizinindeki dosyada tanımlanıyor ve yine de çalışıyor.

```php
<?php
// yer.php — namespace ilk deyim olmali; dizin ad alanini belirlemez
$dizin = 'olcum/yer/derin/ic';
mkdir($dizin, 0777, true);
file_put_contents("$dizin/sinif.php", "<?php\nnamespace Magaza\\Model;\nclass Urun {}\n");
file_put_contents('olcum/yer/gec.php', "<?php\necho 'once';\nnamespace Magaza;\n");

require "$dizin/sinif.php";
$dosya = (new ReflectionClass('Magaza\Model\Urun'))->getFileName();
echo \Magaza\Model\Urun::class, ' ', basename($dosya), "\n";

exec('php -d display_errors=0 olcum/yer/gec.php 2>/dev/null', $satirlar, $kod);
echo "namespace ilk degil: cikis=$kod\n";
```

```
Magaza\Model\Urun sinif.php
namespace ilk degil: cikis=255
```

İlk satır dizinden bağımsızlığı kanıtlıyor: sınıfın adı `Magaza\Model\Urun`, dosyası ise `olcum/yer/derin/ic/sinif.php`. PHP ikisini eşleştirmez; ad alanı yalnız dosyanın içinde bildirilen bir önektir. İkinci satır `namespace` bildirimi `echo`'dan sonra geldiği için sürecin 255 ile bittiğini gösteriyor. Ad alanı ile dizin arasındaki eşleme dilin değil, bir sözleşmenin işidir. Bu sözleşmeyi bir sonraki ders kuruyor: otomatik yükleyici, ad alanı önekini dizinle eşleştirmeyi kendi üstüne alıyor.

#### Ölçüm Düzeneği

Ölçüm `yayilim.php` ile yapılır. Dosya `olc()` fonksiyonunu taşır; `olc()` aynı programı değişiklikten önce ve sonra kurar, ikisini koşturur ve sınıfı sırayla sorar: sonraki koşum 0 dışı çıkış verdiyse **kıran**, çıktı farklıysa **örtük**, düzenlenen dosya bağlantı listesindeyse **bağlantıda**, hiçbiri değilse **yerinde**. İki koşul vardır: önceki sürüm 0 ile çıkmalıdır ve kullanan `main.php` düzenlenmez. Kod blokları aşağıdaki derslerde yalnız anılır; bu dersin sürücüsü `require_once` ile onu çeker. Dosya çıktı bloğu olmadan, yalnız diske yazılır.

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

#### Çakışma: Ad Alanı Olan ve Olmayan Kod

`ad-alani` gözleminde `bizim.php`, `Uygulama` ad alanında `Kayit` sınıfını tanımlıyor ve `kaynak()` yöntemi `uygulama` döndürüyor. Dış kod ise `dis.php`'ye kendi ad alanı olan `Dis` içinde aynı adlı bir `Kayit` ekliyor. Kullanan `use Uygulama\Kayit;` ile bizim sınıfı çağırıyor. Karşıtı `kuresel-ad`: iki sınıf da ad alanı olmadan, küresel ad alanında duruyor.

```php
<?php
// adalani.php — ayni adli sinif ad alaniyla ve ad alani olmadan
require_once 'yayilim.php';

// 8. Dis kod, kendi ad alaninda bizim sinifimizla ayni adda bir sinif ekler
$siniflar[] = olc('ad-alani', [
    'bizim.php' => <<<'PHP'
        <?php
        namespace Uygulama;
        class Kayit
        {
            public function kaynak(): string { return 'uygulama'; }
        }
        PHP,
    'dis.php' => <<<'PHP'
        <?php
        namespace Dis;
        class Gunluk {}
        PHP,
    'main.php' => <<<'PHP'
        <?php
        use Uygulama\Kayit;
        require 'bizim.php';
        require 'dis.php';
        echo (new Kayit())->kaynak(), "\n";
        PHP,
], [
    'dis.php' => <<<'PHP'
        <?php
        namespace Dis;
        class Gunluk {}
        class Kayit
        {
            public function kaynak(): string { return 'dis'; }
        }
        PHP,
]);

$siniflar[] = olc('kuresel-ad', [
    'bizim.php' => "<?php\nclass Kayit\n{\n    public function kaynak(): string { return 'uygulama'; }\n}\n",
    'dis.php' => "<?php\nclass Gunluk {}\n",
    'main.php' => "<?php\nrequire 'bizim.php';\nrequire 'dis.php';\necho (new Kayit())->kaynak(), \"\\n\";\n",
], [
    'dis.php' => "<?php\nclass Gunluk {}\nclass Kayit\n{\n    public function kaynak(): string { return 'dis'; }\n}\n",
]);

echo kostur('olcum/ad-alani/once')[1], ' -> ', kostur('olcum/ad-alani/sonra')[1], "\n";
```

```
ad-alani           1 dosya  cikis=0   cikti ayni   -> yerinde
kuresel-ad         1 dosya  cikis=255 cikti farkli -> kiran
uygulama -> uygulama
```

İlk satır `ad-alani`: dış kod aynı adı getirdi, bir dosya değişti, çıkış 0, çıktı `uygulama` iken `uygulama` kaldı. Bağlantı listesi boş verildiği için sınıf **yerinde**: değişiklik `dis.php`'nin içinde kaldı ve kullanana ulaşmadı. Ad alanı çakışmayı tanımın içine hapsetti.

İkinci satır karşıtı: aynı tek dosyalık ekleme ad alanı olmadan yapılınca program durdu, çıkış 255 verdi. Sınıf **kıran**. Aradaki fark dosya sayısında değil; iki durumda da bir dosya değişti. Fark, adın nerede yaşadığında.

Ad alanının işe yarayabilmesi için adın kendisinin de çakışmaması gerekir. Bu yüzden ad alanının ilk bölümü genellikle projeye ya da onu yazan ekibe ait bir ad olur ve alt bölümler dizin yapısını izler: `Magaza\Model`, `Magaza\Servis`. İki ayrı kitaplığın aynı ilk bölümü seçmesi, ad alanını koruyucu olmaktan çıkarır. Önek seçimi bir sözleşme sorunudur, dilin denetlediği bir şey değildir.

**ST1.** Bu ders ad alanını yalnız ad önekleri açısından ele alır. Dosyaların nasıl bulunup yüklendiği Otomatik Yükleme dersinindir; burada her sınıf `require` ile elle çekilir.

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

Ad alanı sınıfları korudu ama her şeyi korumaz. Nitelenmemiş bir **fonksiyon** çağrısında PHP önce çağrının bulunduğu ad alanına bakar, orada yoksa küresel fonksiyona düşer. Aşağıdaki sürücüde kullanan `namespace Uygulama;` içinde `strlen` çağırıyor. Dahil ettiği `yardimci.php` önce yalnız `selam()` taşıyor; değişiklikte aynı ad alanına bir `strlen` fonksiyonu da giriyor. İkinci ölçümde çağrı `\strlen` diye yazılmış.

```php
<?php
// fonksiyon.php — nitelenmemis fonksiyon cagrisi kuresele duser
require_once 'yayilim.php';

$yardimci = <<<'PHP'
    <?php
    namespace Uygulama;
    function selam(): string { return 'merhaba'; }
    PHP;
$ekli = $yardimci . "\n" . 'function strlen(string $metin): int { return 0; }' . "\n";
$main = "<?php\nnamespace Uygulama;\nrequire 'yardimci.php';\necho %s('kalem'), \"\\n\";\n";

olc('fonksiyon-dusmesi', ['yardimci.php' => $yardimci, 'main.php' => sprintf($main, 'strlen')], ['yardimci.php' => $ekli]);
olc('kok-cagri', ['yardimci.php' => $yardimci, 'main.php' => sprintf($main, '\strlen')], ['yardimci.php' => $ekli]);

echo kostur('olcum/fonksiyon-dusmesi/once')[1], ' -> ', kostur('olcum/fonksiyon-dusmesi/sonra')[1], "\n";
```

```
fonksiyon-dusmesi  1 dosya  cikis=0   cikti farkli -> ortuk
kok-cagri          1 dosya  cikis=0   cikti ayni   -> yerinde
5 -> 0
```

İlk satır `fonksiyon-dusmesi`: `strlen('kalem')` çağrısı `Uygulama\strlen` fonksiyonunu buldu ve `5` yerine `0` yazdı. Program durmadı, çıkış 0 kaldı; çıktı değişti, bu yüzden sınıf **örtük**. Kullanan hiç düzenlenmedi, değişiklik ona yine de ulaştı: nitelenmemiş çağrı hangi `strlen`'in kastedildiğini söylemiyor. İkinci satır `kok-cagri`: aynı çağrı `\strlen` diye yazılınca PHP ad alanına bakmadan küresel fonksiyonu seçti ve sınıf **yerinde** kaldı. Ters eğik çizgi küresel ad alanını açıkça seçer.

Ad alanı sınıf çakışmasına karşı korudu, ama fonksiyon çağrısını kendiliğinden korumadı. Aynı düzenek, çağrının nasıl yazıldığına göre ya korur ya korumaz. Ölçülen yine tanımın kendisi değil, kullananın neye dokunduğu.

#### Özet

- Bu dersin ölçü sınıfı **yerinde**. `ad-alani` gözleminde dış kod aynı adlı sınıfı kendi ad alanında getirdi, bir dosya değişti, çıktı `uygulama` kaldı.
- Karşıtı `kuresel-ad` **kıran**: ad alanı olmadan aynı ekleme programı 255 çıkışıyla durdurdu. İki durumda da bir dosya değişti; fark adın yaşadığı yerdi.
- `namespace` adlara önek verir, `use` tam nitelikli adı kısa adla anılabilir yapar, `as` çakışan kısa adlara takma ad verir.
- Sınırlayıcı ölçüm: nitelenmemiş fonksiyon çağrısı küresele düşer. `strlen` çağrısı `Uygulama\strlen`'e yönelince `5` → `0`, sınıf **örtük**; `\strlen` ile **yerinde**.
- Ad alanı klasör değil ad önekidir: sınıfın hangi dosyada durduğunu söylemez. Nitelenmemiş, nitelenmiş ve tam nitelikli ad ayrı çözülür; `use function` ve `use const` fonksiyon ile sabiti içe alır; `namespace` bildirimi dosyanın ilk deyimidir.

#### Sonraki Adım

Bu derste sınıfı bulmak `require` ile elle yapıldı. Sınıf dosyası başka bir kök dizine taşınırsa kaç dosya değişir ve kullanan bundan etkilenir mi? Otomatik Yükleme dersi bu soruyu, yükleyiciyi tek bir bağlantı noktası olarak ölçerek yanıtlıyor.

### Otomatik Yükleme

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/otomatik-yukleme

spl_autoload_register ile otomatik yükleyici; ad alanı önekinden dizine eşleme ve PSR-4; sınıf dosyasının taşınmasında dosya sayısının sınıfı belirlemediği; yükleyici güncellenmeden yapılan taşımanın programı durdurması; eşlemenin dosya adındaki harf uyumuna bağlılığı.

Ad Alanları dersinde her sınıf `require` ile elle çekildi. Bu, kullananın sınıfın hangi dosyada durduğunu bildiği anlamına gelir; sınıf dosyası taşınırsa bilgi her `require` satırında eskir. Bu derste aynı bilgi tek bir yere toplanıyor: **otomatik yükleyici**. Soru yine kurs boyunca sorulan soru: **bu değişiklik nereye ulaşıyor?**

M09/K02 kursu bir adın bağlandığı yeri arıyordu. Yükleyici, ad ile dosya yolu arasındaki bağın kurulduğu yerdir. Bu derste o bağ değişince kaç dosyanın düzenlendiği ve düzenlenmemiş kodun ne yaşadığı ölçülür.

#### Elle Yükleme ve Bedeli

Yükleyiciden önce her sınıf dosyası `require_once` ile elle çekilirdi. Bu yöntemin iki bedeli vardır. Birincisi, kullanan dosya kullandığı her sınıfın yolunu bilmek zorundadır; yol değişirse her `require_once` satırı eskir. İkincisi, satırlar sınıf kullanılsın ya da kullanılmasın çalışır: bir programın yalnız bir sınıfa ihtiyacı olsa da dördünü birden yükler. Sınıfların kendi içinde de başka sınıflara ihtiyaç duyması, bu satırları zincire çevirir ve zincirin hangi sırayla kurulacağı da kullananın yükü olur.

Bedelin ikinci yarısı sayılabilir. Aşağıdaki sürücü dört boş sınıf dosyası yazar ve iki ayrı program kurar: biri dört dosyayı elle çeker, öteki bir yükleyici kaydeder. İkisi de yalnız `A` sınıfını kullanır ve sonunda kaç dosyanın yüklendiğini basar.

```php
<?php
// zincir.php — elle yukleme her dosyayi yukler, yukleyici yalniz kullanilani
mkdir('olcum/zincir/src', 0777, true);
foreach (['A', 'B', 'C', 'D'] as $ad) {
    file_put_contents("olcum/zincir/src/$ad.php", "<?php\nclass $ad {}\n");
}

$elle = <<<'PHP'
    <?php
    require_once 'src/A.php';
    require_once 'src/B.php';
    require_once 'src/C.php';
    require_once 'src/D.php';
    new A();
    echo count(get_included_files()) - 1, "\n";
    PHP;
$otomatik = <<<'PHP'
    <?php
    spl_autoload_register(fn (string $sinif) => require __DIR__ . "/src/$sinif.php");
    new A();
    echo count(get_included_files()) - 1, "\n";
    PHP;
file_put_contents('olcum/zincir/elle.php', $elle);
file_put_contents('olcum/zincir/otomatik.php', $otomatik);

foreach (['elle', 'otomatik'] as $ad) {
    echo str_pad($ad, 9), exec("php olcum/zincir/$ad.php"), " dosya\n";
}
```

```
elle     4 dosya
otomatik 1 dosya
```

Elle yazılan sürüm program yalnız `A` istediği hâlde dört dosya yükledi. Yükleyicili sürüm bir dosya yükledi: `B`, `C` ve `D` hiç istenmedi, hiç açılmadı. Sınıf sayısı büyüdükçe fark da büyür; üç yüz sınıflı bir uygulamada her istek yalnız dokunduğu sınıfların dosyasını okur.

#### Yükleyici Ne Zaman Çağrılır

`spl_autoload_register` bir fonksiyonu yükleyici olarak kaydeder. PHP bilmediği bir sınıf adıyla karşılaşınca bu fonksiyonu sınıfın tam nitelikli adıyla çağırır; fonksiyon dosyayı yüklemekle yükümlüdür. Sınıf zaten tanımlıysa ya da çağrı yükleyiciyi atlıyorsa fonksiyon çalışmaz.

```php
<?php
// gozlem.php — yukleyici yalniz bilinmeyen sinif icin cagrilir
spl_autoload_register(function (string $sinif): void {
    echo "istenen: $sinif\n";
});

var_dump(class_exists('Magaza\Model\Urun'));
var_dump(class_exists('Magaza\Model\Urun', false));
echo "bitti\n";
```

```
istenen: Magaza\Model\Urun
bool(false)
bool(false)
bitti
```

İlk `class_exists` yükleyiciyi tam nitelikli adla çağırdı. Bu örnekte yükleyici hiçbir dosya yüklemediği için sınıf yine bulunamadı ve sonuç `false` oldu. İkinci çağrı ikinci argümanla yükleyiciyi atladı: fonksiyon hiç çalışmadı. Yükleyici bir bildirim değil bir geri çağrıdır; PHP onu yalnız eksik sınıf için ve yalnız o an çağırır.

#### Yükleyiciyi Hangi İşlemler Çağırır

Yükleyici, PHP'nin bir sınıf adını çözmesi gereken ve sınıfı henüz bilmediği her yerde çağrılır. Bu, kabaca sınıfın var olmasına gerçekten ihtiyaç duyan işlemlerdir: `new`, statik çağrı, sınıf sabiti, `class_exists`. Sınıfın var olmasını gerektirmeyen işlemler ise yükleyiciyi çağırmaz: `instanceof`, `::class` ve parametre tip bildirimi. Aşağıdaki sürücü yükleyiciyi sınıf yüklemeyen bir fonksiyon olarak kaydeder, böylece her işlemden sonra yükleyicinin çağrılıp çağrılmadığı görülür. Bulunamayan sınıf `Error` fırlatır; sürücü hatayı yakalayıp devam eder.

```php
<?php
// tetik.php — yukleyiciyi hangi islemler cagirir
spl_autoload_register(function (string $sinif): void {
    echo "  yukleyici: $sinif\n";
});

function dene(string $ad, callable $islem): void
{
    echo "$ad\n";
    try {
        $islem();
    } catch (Error) {
    }
}

dene('new', fn () => new Bir());
dene('statik cagri', fn () => Iki::calistir());
dene('sinif sabiti', fn () => Uc::SABIT);
dene('class_exists', fn () => class_exists('Dort'));
dene('instanceof', fn () => new stdClass() instanceof Bes);
dene('::class', fn () => Alti::class);
dene('tip bildirimi', function () {
    (fn (Yedi $parametre) => 1)(1);
});
```

```
new
  yukleyici: Bir
statik cagri
  yukleyici: Iki
sinif sabiti
  yukleyici: Uc
class_exists
  yukleyici: Dort
instanceof
::class
tip bildirimi
```

İlk dört işlem yükleyiciyi çağırdı ve sınıf adını aldı. Son üç işlem çağırmadı. `instanceof` karşılaştırılan sınıf yoksa nesnenin o sınıftan olamayacağını biliyor, sınıfı yüklemeye gerek duymuyor. `::class` yalnız derleme anında çözülmüş adı döndürüyor. Tip bildirimi ise kontrolü nesnenin sınıf adıyla yapıyor; yanlış tipte bir değer gelirse `TypeError` fırlatır ama yükleyici yine çağrılmaz. Sonuç şu: bir sınıf yalnız ilk gerçekten kullanıldığında yüklenir. Bu da bir kod yolunun hiç çalışmadığı bir istekte, o yoldaki sınıfların hiç yüklenmediği anlamına gelir.

#### Birden Çok Yükleyici

`spl_autoload_register` birden çok kez çağrılabilir. Kayıtlı yükleyiciler kayıt sırasıyla çağrılır ve sınıf tanımlanır tanımlanmaz zincir durur. Üçüncü argüman `true` ise yükleyici sıranın başına eklenir. Bir uygulama kendi yükleyicisine ek olarak bağımlılıkların kendi yükleyicisini de taşıyabilir; bu yüzden her yükleyici yalnız kendi önekini işlemeli, tanımadığı adı hata vermeden bırakmalıdır.

```php
<?php
// sira.php — yukleyiciler kayit sirasiyla cagrilir, sinif bulununca zincir durur
spl_autoload_register(function (string $sinif): void { echo "birinci: $sinif\n"; });
spl_autoload_register(function (string $sinif): void {
    echo "ikinci: $sinif\n";
    if ($sinif === 'Hedef') {
        class_alias(stdClass::class, 'Hedef');
    }
});
spl_autoload_register(function (string $sinif): void { echo "ucuncu: $sinif\n"; });
spl_autoload_register(function (string $sinif): void { echo "basa: $sinif\n"; }, true, true);

echo "-- Hedef\n";
class_exists('Hedef');
echo "-- Yok\n";
class_exists('Yok');
```

```
-- Hedef
basa: Hedef
birinci: Hedef
ikinci: Hedef
-- Yok
basa: Yok
birinci: Yok
ikinci: Yok
ucuncu: Yok
```

`basa` en son kaydedildi ama `true` ile en öne alındığı için ilk o çağrıldı. `Hedef` için zincir ikinci yükleyicide durdu: o `Hedef` adını tanımladı ve `ucuncu` hiç çağrılmadı. `Yok` için hiçbir yükleyici sınıfı tanımlamadı, bu yüzden dördü de sırayla çağrıldı ve sınıf yine bulunamadı. Her yükleyicinin çağrılması bir bedeldir; öneki tanımayan yükleyicinin ilk satırda dönmesi bu yüzden önemlidir.

#### Önekten Dizine Eşleme

Yükleyicinin işi tam nitelikli adı dosya yoluna çevirmektir. PHP topluluğunun standart önerisi **PSR-4** bunu kısa bir kurala bağlar: ad alanının bir öneki bir temel dizine karşılık gelir, önekten sonra kalan ad alanı bölümleri alt dizin olur, son bölüm dosya adıdır ve sonuna `.php` eklenir. `Magaza\Model\Urun` sınıfı, `Magaza\` önekine `src/` dizini eşlenmişse `src/Model/Urun.php` dosyasından yüklenir.

**ST2.** Bu ders yükleyiciyi tek önek ve tek temel dizinle kurar. Birden çok önekli ya da yedek dizinli eşlemeler ve ad alanı olmayan eski sınıf adları ölçülmez.

Eşleme dört adımda okunur: önek çıkarılır, kalan göreli ad alınır, ad alanı ayracı dizin ayracına çevrilir ve sona `.php` eklenir. Aşağıdaki sürücü bu dört adımı `Magaza\Model\Urun` adı üzerinde ayrı ayrı basar.

```php
<?php
// psr4.php — PSR-4 eslemesi adim adim
$onek = 'Magaza\\';
$temel = 'src/';
$sinif = 'Magaza\Model\Urun';

echo "tam ad       : $sinif\n";
$goreli = substr($sinif, strlen($onek));
echo "goreli ad    : $goreli\n";
$yol = str_replace('\\', '/', $goreli);
echo "dizin ayraci : $yol\n";
echo "dosya        : $temel$yol.php\n";
```

```
tam ad       : Magaza\Model\Urun
goreli ad    : Model\Urun
dizin ayraci : Model/Urun
dosya        : src/Model/Urun.php
```

Her satır bir önceki satırdan türüyor ve hiçbirinde dosya sistemine bakılmıyor: eşleme salt bir dize işlemidir. Sınıfın dosyada gerçekten tanımlı olup olmadığını yalnız `require` sonrası bilebiliriz. Bu yüzden iyi bir yükleyici `require`'dan önce dosyanın varlığına bakar; dosya yoksa hata vermez ve zincirdeki sonraki yükleyiciye şans tanır. Bu dersin `yukleyici.php`'si ölçümü sade tutmak için bu denetimi yapmaz.

#### Ölçüm Düzeneği

`yayilim.php` önceki derste olduğu gibi `olc()` fonksiyonunu taşır: aynı programı değişiklikten önce ve sonra kurup koşturur. Sınıfı sırayla sorar: çıkış 0 değilse **kıran**, çıktı farklıysa **örtük**, düzenlenen dosya bağlantı listesindeyse **bağlantıda**, hiçbiri değilse **yerinde**. Önceki sürüm 0 ile çıkmalıdır ve kullanan `main.php` düzenlenmez.

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

#### Taşıma: Üç Dosya, Bağlantıda

Gözlem `otomatik-yukleme`: `Magaza\Model\Urun` sınıfı `src/Model/Urun.php` dosyasında duruyor ve `yukleyici.php` onu `src/` altında arıyor. Sınıf `kaynak/` temel dizinine taşınıyor. Üç dosya değişiyor: eski dosya siliniyor (değeri `null`), yenisi ekleniyor, yükleyici yeni dizini gösterecek biçimde güncelleniyor. Dördüncü argüman bağlantı listesidir ve `yukleyici.php`'yi bağlantı noktası olarak adlandırır. Karşıtı `yukleyicisiz`: aynı taşıma, yükleyici güncellenmeden.

Tabandaki `yukleyici.php` eşlemeyi üç adımda kurar. Gelen ad `Magaza\` ile başlıyorsa işleme girer, aksi hâlde başka bir yükleyiciye bırakır. Önekten sonra kalan `Model\Urun` kısmındaki ters eğik çizgiler düz eğik çizgiye çevrilir ve sona `.php` eklenir. Ortaya çıkan `Model/Urun.php` yolu, `src/` temel dizininin altında aranır. Temel dizin yükleyicide tek bir dizgi olarak durur; sınıf dosyalarının nereden yükleneceğine dair bütün bilgi orada toplanmıştır. Bu yüzden taşıma, kullananda değil yalnız bu dizgide görünür.

```php
<?php
// yukleme.php — sinif dosyasi tasinir, yukleyici guncellenir ya da guncellenmez
require_once 'yayilim.php';

// 9. Sinif dosyasi baska bir kok dizine tasinir
$siniflar[] = olc('otomatik-yukleme', [
    'src/Model/Urun.php' => <<<'PHP'
        <?php
        namespace Magaza\Model;
        class Urun
        {
            public function ad(): string { return 'kalem'; }
        }
        PHP,
    'yukleyici.php' => <<<'PHP'
        <?php
        spl_autoload_register(function (string $sinif): void {
            $onek = 'Magaza\\';
            if (str_starts_with($sinif, $onek)) {
                require 'src/' . str_replace('\\', '/', substr($sinif, strlen($onek))) . '.php';
            }
        });
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'yukleyici.php';
        echo (new Magaza\Model\Urun())->ad(), "\n";
        PHP,
], [
    'src/Model/Urun.php' => null,
    'kaynak/Model/Urun.php' => <<<'PHP'
        <?php
        namespace Magaza\Model;
        class Urun
        {
            public function ad(): string { return 'kalem'; }
        }
        PHP,
    'yukleyici.php' => <<<'PHP'
        <?php
        spl_autoload_register(function (string $sinif): void {
            $onek = 'Magaza\\';
            if (str_starts_with($sinif, $onek)) {
                require 'kaynak/' . str_replace('\\', '/', substr($sinif, strlen($onek))) . '.php';
            }
        });
        PHP,
], ['yukleyici.php']);

echo kostur('olcum/otomatik-yukleme/once')[1], ' -> ', kostur('olcum/otomatik-yukleme/sonra')[1], "\n";

$once = oku('olcum/otomatik-yukleme/once');
olc('yukleyicisiz', $once, [
    'src/Model/Urun.php' => null,
    'kaynak/Model/Urun.php' => $once['src/Model/Urun.php'],
]);
```

```
otomatik-yukleme   3 dosya  cikis=0   cikti ayni   -> baglantida
kalem -> kalem
yukleyicisiz       2 dosya  cikis=255 cikti farkli -> kiran
```

İlk satır `otomatik-yukleme`: üç dosya değişti, çıkış 0 kaldı, çıktı `kalem` iken `kalem` kaldı. Çıktı değişmediği ve düzenlenen dosyalardan biri bağlantı listesinde olduğu için sınıf **bağlantıda**: değişiklik tek bir noktada karşılandı ve `main.php` düzenlenmedi. İkinci satır karşıtı: yükleyici güncellenmeden yapılan aynı taşıma iki dosyayla bitti, ama çıkış 255 verdi ve sınıf **kıran** oldu; yükleyici sınıfı hâlâ `src/` altında arıyordu.

Sonuç şu: dosya sayısı sınıfı belirlemez. Üç dosya değişen taşıma sorunsuzdu, iki dosya değişen taşıma programı durdurdu. Belirleyen, kullananın düzenlenip düzenlenmediği ve bağın tutulduğu tek noktanın güncel olup olmadığıdır.

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

Eşleme, sınıf adının harflerini dosya adına olduğu gibi aktarır. `Urun` sınıfı için yükleyici `Urun.php` dosyasını ister. Sınıf dosyası `urun.php` diye küçük harfle yeniden adlandırılırsa ne olur? Sürücü bu değişikliği aynı tabanda, yükleyiciye dokunmadan ölçer.

```php
<?php
// harf.php — dosya adindaki harf uyumu eslemenin parcasidir
require_once 'yayilim.php';

$once = oku('olcum/otomatik-yukleme/once');
olc('kucuk-harf', $once, [
    'src/Model/Urun.php' => null,
    'src/Model/urun.php' => $once['src/Model/Urun.php'],
]);
```

```
kucuk-harf         2 dosya  cikis=255 cikti farkli -> kiran
```

Sınıf **kıran** oldu: iki dosya değişti, çıkış 255. Yükleyici `Urun.php` istedi, dizinde yalnız `urun.php` vardı. Bu, dilin değil dosya sisteminin sonucudur: sandbox'ın dosya sistemi büyük ve küçük harfi ayırır. Büyük-küçük harf ayırmayan bir dosya sisteminde aynı değişiklik çalışabilir; ders bunu ölçmedi ve yalnız sandbox sonucunu basıyor. Eşleme bu yüzden dosya adının sınıf adıyla harfi harfine aynı olmasını da şart koşar.

#### Özet

- Bu dersin ölçü sınıfı **bağlantıda**. `otomatik-yukleme` gözleminde sınıf dosyası başka kök dizine taşındı, üç dosya değişti, çıktı `kalem` kaldı ve değişiklik `yukleyici.php` noktasında karşılandı.
- Karşıtı `yukleyicisiz` **kıran**: yükleyici güncellenmeden iki dosyalık taşıma çıkış 255 verdi. Dosya sayısı sınıfı belirlemez.
- Yükleyici bilinmeyen sınıf için tam nitelikli adla çağrılır; PSR-4 öneki bir dizine eşler, kalan ad alanı bölümleri alt dizin, son bölüm dosya adı olur.
- Yükleyici yalnız sınıfın var olmasını gerektiren işlemlerde (`new`, statik çağrı, sınıf sabiti, `class_exists`) çağrılır; `instanceof`, `::class` ve tip bildirimi onu çağırmaz. Birden çok yükleyici kayıt sırasıyla çalışır ve sınıf bulununca zincir durur.
- Sınırlayıcı ölçüm: dosya `urun.php` diye yeniden adlandırılınca sandbox'ta sınıf **kıran** oldu; bu dosya sisteminin sonucudur.

#### Sonraki Adım

Yükleyici, sınıfın nereden geldiğini tek bir yerde topladı. Sınıfın içi ise hâlâ bir arayüzün arkasında değişebilir: kullanan bir arayüze bağlıysa ve gerçekleştirim değişirse kaç yer düzenlenir? Ortak Arayüz Standartları dersi bunu ölçüyor.

### Ortak Arayüz Standartları

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/ortak-arayuz-standartlari

PSR-3 gibi ortak arayüzler; kullananın arayüze bağlı olduğu ve gerçekleştirimin kurulum dosyasında değiştiği durum; arayüzün imzayı denetleyip anlamı denetlememesi; yer tutucuyu doldurmayan gerçekleştirimin çıktıyı sessizce değiştirmesi.

Otomatik Yükleme dersi sınıfın nereden geldiğini tek bir yerde topladı. Bu derste soru sınıfın içine dönüyor: kullanan bir **ortak arayüze** bağlıysa ve gerçekleştirim değişirse **bu değişiklik nereye ulaşıyor?** Aynı soru, aynı sürücü, aynı sınıf ölçüsü.

**Ortak arayüz standardı**, birçok kitaplığın ve uygulamanın aynı imzayı kabul etmesidir. PHP topluluğunun kayıt tutma için önerdiği **PSR-3** bunun bilinen örneğidir: bir kaydediciyi iletiyle ve bir bağlam dizisiyle çağırırsınız, ileti içindeki `{anahtar}` yer tutucuları bağlam değerleriyle değiştirilir. Bu derste PSR-3'ün tamamı değil, aynı fikrin küçük bir hâli kullanılıyor: tek yöntemli `Kaydedici` arayüzü ve `{no}` gibi yer tutucular.

#### Standart Öneri Ne Demektir

PSR, PHP topluluğunun yayımladığı **standart önerilerin** kısaltmasıdır ve numarayla anılır: PSR-3 kayıt tutmayı, PSR-4 otomatik yüklemeyi tanımlar. Adındaki "öneri" sözü gerçektir: dil bir önerinin uygulanıp uygulanmadığını denetlemez. Bir kitaplığın PSR-3'ü benimsemesi o kitaplığın kararıdır. Değeri uyguladıkça doğar: iki kitaplık aynı arayüzü benimserse birbirinden haberi olmadan aynı uygulamada çalışabilir.

#### Kim Neyi Bilir

Ortak arayüzün asıl işi, birbirini tanımayan iki kod parçasını buluşturmaktır. Aşağıdaki sürücüde bir **kitaplık** ödeme alır ve bunu kaydetmek ister; bir **uygulama** ise kaydın nereye gideceğine karar verir. Kitaplık yalnız `Kaydedici` arayüzünü bilir. Uygulama arayüzü bilir ve iki gerçekleştirim yazar. İkisi yalnız arayüzde buluşur: kitaplık dosyası hiçbir gerçekleştirimin adını içermez.

```php
<?php
// kimbilir.php — kitaplik arayuzu bilir, uygulama gerceklestirimi secer
mkdir('olcum/kimbilir', 0777, true);
file_put_contents('olcum/kimbilir/kitaplik.php', <<<'PHP'
    <?php
    interface Kaydedici
    {
        public function kaydet(string $ileti): void;
    }
    class OdemeServisi
    {
        public function __construct(private Kaydedici $kaydedici) {}
        public function al(int $tutar): void { $this->kaydedici->kaydet("odeme alindi: $tutar"); }
    }
    PHP);
file_put_contents('olcum/kimbilir/uygulama.php', <<<'PHP'
    <?php
    class EkranKaydedici implements Kaydedici
    {
        public function kaydet(string $ileti): void { echo $ileti, "\n"; }
    }
    class BuyukHarfKaydedici implements Kaydedici
    {
        public function kaydet(string $ileti): void { echo strtoupper($ileti), "\n"; }
    }
    PHP);

$kitaplik = file_get_contents('olcum/kimbilir/kitaplik.php');
$anar = str_contains($kitaplik, 'Ekran') || str_contains($kitaplik, 'Buyuk');
echo 'kitaplik gerceklestirimi anar mi: ', $anar ? 'evet' : 'hayir', "\n";

require 'olcum/kimbilir/kitaplik.php';
require 'olcum/kimbilir/uygulama.php';
(new OdemeServisi(new EkranKaydedici()))->al(50);
(new OdemeServisi(new BuyukHarfKaydedici()))->al(50);
```

```
kitaplik gerceklestirimi anar mi: hayir
odeme alindi: 50
ODEME ALINDI: 50
```

İlk satır kitaplığın gerçekleştirimlerden habersiz olduğunu gösteriyor. Sonraki iki satırda aynı `OdemeServisi` iki farklı kaydediciyle çalıştı ve çıktısı ona göre değişti. Kitaplık tek satır değişmedi. Bu, önceki bölümün ölçtüğü şeyin nedenidir: bağlantı noktası kitaplığın içinde değil, gerçekleştirimi seçen `kurulum.php` benzeri yerdedir. Arayüz sınırı çizer, sınırın iki yanındaki kod birbirinin değişimine kapalı kalır.

#### Kaydedici Verilmediğinde

Kitaplık arayüze bağlıysa uygulamanın bir gerçekleştirim vermemesi de düşünülmelidir. Yaygın çözüm, hiçbir şey yapmayan bir gerçekleştirimi varsayılan olarak kullanmaktır; PSR-3 bunun için ayrıca bir boş kaydedici sınıfı da önerir. Bu sayede kitaplığın her çağrıda `null` denetlemesi yapması gerekmez ve uygulama kaydı istemiyorsa hiçbir şey bağlamaz.

```php
<?php
// bos.php — kaydedici verilmezse kitaplik sessiz gerceklestirimle calisir
interface Kaydedici
{
    public function kaydet(string $ileti): void;
}
class SessizKaydedici implements Kaydedici
{
    public function kaydet(string $ileti): void {}
}
class EkranKaydedici implements Kaydedici
{
    public function kaydet(string $ileti): void { echo $ileti, "\n"; }
}
class RaporServisi
{
    private Kaydedici $kaydedici;

    public function __construct(?Kaydedici $kaydedici = null)
    {
        $this->kaydedici = $kaydedici ?? new SessizKaydedici();
    }

    public function uret(): int
    {
        $this->kaydedici->kaydet('rapor uretildi');
        return 3;
    }
}

$sessiz = (new RaporServisi())->uret();
echo "kaydedicisiz: $sessiz\n";
$ekranli = (new RaporServisi(new EkranKaydedici()))->uret();
echo "ekranli: $ekranli\n";
```

```
kaydedicisiz: 3
rapor uretildi
ekranli: 3
```

İki çağrı da aynı değeri döndürdü; yalnız ikincisi bir kayıt satırı yazdı. Kayıt tutmak kitaplığın asıl işine karışmıyor. Bu yüzden kayıt arayüzü çoğu zaman kitaplığın yanında değil, ayrı ve çok küçük bir pakette dağıtılır: kitaplıklar yalnız arayüze, uygulamalar da arayüzü gerçekleştiren pakete bağlanır. Bağımlılıklar arayüze doğru akar, birbirine doğru akmaz.

#### Arayüz Bir Sınırdır

Arayüzler dersinde (M08/K03) arayüzün söz değil sınır olduğu görüldü: PHP imzayı denetler, davranışı denetlemez. Bu derste o ayrım bir ölçüme bağlanıyor. Önce arayüzün iyi çalıştığı durum, sonra denetlemediği durum.

**ST3.** Ders `Kaydedici` arayüzünü PSR-3'ün tamamına değil, tek `kaydet` yöntemine indirgenmiş hâliyle kullanır. Seviye adları, istisna bağlamı ve `Stringable` iletiler ölçülmez.

#### PSR-3: Seviyeler ve Bağlam Dizisi

PSR-3 arayüzü her seviye için bir yöntem tanımlar ve genel bir `log` yöntemi ekler. Seviyeler en ağırdan en hafife doğru sekizdir: `emergency`, `alert`, `critical`, `error`, `warning`, `notice`, `info`, `debug`. İleti bir dizgidir ve içinde `{anahtar}` biçiminde yer tutucular taşıyabilir; ikinci argüman olan bağlam dizisi bu yer tutucuların değerlerini verir. Bağlamda `exception` anahtarı bir istisna nesnesi için ayrılmıştır. Standart hangi seviyenin yazılıp hangisinin atılacağını söylemez; bu bir gerçekleştirim kararıdır.

Aşağıdaki sürücü küçük bir gerçekleştirimi modeller: ağırlık sırası dizi sırasıdır ve bir eşik seviyesi eşiğin altındaki, yani daha hafif kayıtları atar. Aynı dört kayıt iki ayrı eşikle yazılır.

```php
<?php
// seviyeler.php — sekiz seviye, esik ve baglam dizisi
$seviyeler = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug'];

function yaz(array $seviyeler, string $esik, string $seviye, string $ileti, array $baglam): void
{
    if (array_search($seviye, $seviyeler) > array_search($esik, $seviyeler)) {
        return;
    }
    $yerler = [];
    foreach ($baglam as $anahtar => $deger) {
        $yerler['{' . $anahtar . '}'] = $deger;
    }
    echo str_pad($seviye, 10), strtr($ileti, $yerler), "\n";
}

$kayitlar = [
    ['critical', 'veritabani {sunucu} yanit vermiyor', ['sunucu' => 'db1']],
    ['warning', 'siparis {no} gecikti', ['no' => 42]],
    ['info', 'kullanici {kisi} giris yapti', ['kisi' => 'ayse']],
    ['debug', 'sorgu suresi {sure} ms', ['sure' => 12]],
];
foreach (['warning', 'debug'] as $esik) {
    echo "-- esik: $esik\n";
    foreach ($kayitlar as [$seviye, $ileti, $baglam]) {
        yaz($seviyeler, $esik, $seviye, $ileti, $baglam);
    }
}
```

```
-- esik: warning
critical  veritabani db1 yanit vermiyor
warning   siparis 42 gecikti
-- esik: debug
critical  veritabani db1 yanit vermiyor
warning   siparis 42 gecikti
info      kullanici ayse giris yapti
debug     sorgu suresi 12 ms
```

`warning` eşiğinde yalnız `critical` ve `warning` kayıtları yazıldı; `info` ve `debug` atıldı. `debug` eşiğinde dördü de yazıldı. İletiler her iki durumda yer tutucuları doldurarak çıktı: `{sunucu}` yerine `db1`, `{no}` yerine `42`. İletiyi bir kez, sabit bir kalıpla yazıp değişken kısmı bağlamda taşımanın yararı, aynı kalıbın farklı gerçekleştirimlerce işlenebilmesidir: biri düz metin, biri yapılandırılmış kayıt üretebilir.

#### Ölçüm Düzeneği

`yayilim.php` önceki derslerle aynıdır: aynı programı değişiklikten önce ve sonra kurup koşturur. Sınıfı sırayla sorar: çıkış 0 değilse **kıran**, çıktı farklıysa **örtük**, düzenlenen dosya bağlantı listesindeyse **bağlantıda**, hiçbiri değilse **yerinde**. Önceki sürüm 0 ile çıkmalıdır ve kullanan `main.php` düzenlenmez.

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

#### Gerçekleştirimi Değiştirmek: Bağlantıda

Gözlem `ortak-arayuz`: `Siparis` sınıfı yalnız `Kaydedici` arayüzünü tanır ve ona bir sipariş iletisi yazdırır. Hangi gerçekleştirimin kullanılacağını `kurulum.php` seçer; `main.php` onu `require` ile alıp `Siparis`'e verir. Tabanda `EkranKaydedici` iletiyi doğrudan ekrana basıyor. Değişiklik tek dosya: `kurulum.php` artık iletiyi bir tamponda biriktiren `TamponKaydedici` döndürüyor ve program sonunda hepsini basıyor. Dördüncü argüman bağlantı listesidir ve `kurulum.php`'yi bağlantı noktası olarak adlandırır.

`Siparis` yer tutucuyu kendisi doldurmaz. `{no}` yer tutucusunu dolduran `yerlestir()` fonksiyonu arayüzle aynı dosyada durur ve iki gerçekleştirim de onu çağırır; `Siparis` yalnız `kaydet` yönteminu bilir.

```php
<?php
// kurulum.php degisimi — gerceklestirim kurulum dosyasinda degisir
require_once 'yayilim.php';

// 10. Ortak bir kayit arayuzunun gerceklestirimi degistirilir
$siniflar[] = olc('ortak-arayuz', [
    'kaydedici.php' => <<<'PHP'
        <?php
        interface Kaydedici
        {
            public function kaydet(string $ileti, array $baglam = []): void;
        }
        function yerlestir(string $ileti, array $baglam): string
        {
            $yerler = [];
            foreach ($baglam as $anahtar => $deger) {
                $yerler['{' . $anahtar . '}'] = $deger;
            }
            return strtr($ileti, $yerler);
        }
        class EkranKaydedici implements Kaydedici
        {
            public function kaydet(string $ileti, array $baglam = []): void
            {
                echo yerlestir($ileti, $baglam), "\n";
            }
        }
        PHP,
    'kurulum.php' => <<<'PHP'
        <?php
        return new EkranKaydedici();
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'kaydedici.php';
        class Siparis
        {
            public function __construct(private Kaydedici $kaydedici) {}
            public function ver(int $no): void { $this->kaydedici->kaydet('siparis {no} alindi', ['no' => $no]); }
        }
        (new Siparis(require 'kurulum.php'))->ver(7);
        PHP,
], [
    'kurulum.php' => <<<'PHP'
        <?php
        class TamponKaydedici implements Kaydedici
        {
            private array $satirlar = [];
            public function kaydet(string $ileti, array $baglam = []): void
            {
                $this->satirlar[] = yerlestir($ileti, $baglam);
            }
            public function __destruct() { echo implode("\n", $this->satirlar), "\n"; }
        }
        return new TamponKaydedici();
        PHP,
], ['kurulum.php']);

echo kostur('olcum/ortak-arayuz/once')[1], "\n";
echo kostur('olcum/ortak-arayuz/sonra')[1], "\n";
$adlar = array_keys(oku('olcum/ortak-arayuz/sonra'));
sort($adlar);
echo implode(', ', $adlar), "\n";
```

```
ortak-arayuz       1 dosya  cikis=0   cikti ayni   -> baglantida
siparis 7 alindi
siparis 7 alindi
kaydedici.php, kurulum.php, main.php
```

İlk satır ölçümün sonucu: bir dosya değişti, çıkış 0 kaldı, çıktı aynı kaldı ve değişen dosya bağlantı listesinde olduğu için sınıf **bağlantıda**. Sonraki iki satır programın iki sürümünün çıktısıdır: ekrana yazan gerçekleştirim de, tamponlayan gerçekleştirim de aynı `siparis 7 alindi` satırını verdi. Son satır `olcum/ortak-arayuz/sonra` dizinindeki dosyaların sıralı listesidir: `main.php` listede var ve ölçüm sırasında hiç düzenlenmedi.

Sonuç şu: kullanan arayüze bağlı olduğu için gerçekleştirim değişince `Siparis` ve `main.php` yerinde kaldı. Değişiklik `kurulum.php` noktasında karşılandı. Ekrana basan gerçekleştirimden tamponlayana geçmek tek dosyalık bir iş oldu ve çıktı aynı kaldı.

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

Arayüz imzayı denetler, anlamı denetlemez. Aynı tabana bir gerçekleştirim daha uygulanıyor: `kaydet` yöntemi arayüzü imza olarak eksiksiz karşılar, ama bağlamı kullanmaz ve iletiyi yer tutucusu doldurulmadan basar. Sürücü ayrı bir ölçümdür ve `olcum/yer-tutucusuz` altında çalışır; taban önceki ölçümün tabanıdır.

```php
<?php
// yer-tutucusuz.php — imzayi karsilayan ama anlami tasimayan gerceklestirim
require_once 'yayilim.php';

$taban = oku('olcum/ortak-arayuz/once');
olc('yer-tutucusuz', $taban, [
    'kurulum.php' => <<<'PHP'
        <?php
        class DuzKaydedici implements Kaydedici
        {
            public function kaydet(string $ileti, array $baglam = []): void
            {
                echo $ileti, "\n";
            }
        }
        return new DuzKaydedici();
        PHP,
], ['kurulum.php']);

echo kostur('olcum/yer-tutucusuz/once')[1], ' -> ', kostur('olcum/yer-tutucusuz/sonra')[1], "\n";
```

```
yer-tutucusuz      1 dosya  cikis=0   cikti farkli -> ortuk
siparis 7 alindi -> siparis {no} alindi
```

Sınıf **örtük** oldu: bir dosya değişti, çıkış 0 kaldı, ama çıktı `siparis 7 alindi` iken `siparis {no} alindi` oldu. `DuzKaydedici` arayüzü imza olarak eksiksiz karşılıyor; PHP bir şey söylemedi, program çökmedi. Değişen dosya bağlantı listesinde olsa da çıktı değiştiği için sınıf **örtük** sayıldı, çünkü sıralamada çıktı farkı bağlantıdan önce gelir. Arayüz sınırı korudu, anlamı korumadı. Yer tutucuyu doldurma sözü yalnız belgede ve ortak standardın metninde duruyor; onu denetleyen bir ölçüm ya da test gerekir.

#### Özet

- Bu dersin ölçü sınıfı **bağlantıda**. `ortak-arayuz` gözleminde gerçekleştirim `kurulum.php` dosyasında değişti, `Siparis` ve `main.php` düzenlenmedi, çıktı `siparis 7 alindi` kaldı.
- Ortak arayüz (PSR-3 gibi) kullananı gerçekleştirimden ayırır; değişiklik kurulum dosyasındaki bağlantı noktasında karşılanır.
- Sınırlayıcı ölçüm `yer-tutucusuz` **örtük**: imzayı karşılayan ama `{no}` yer tutucusunu doldurmayan gerçekleştirim çıkış 0 verdi, çıktı `siparis {no} alindi` oldu.
- Kitaplık yalnız arayüzü bilir, uygulama gerçekleştirimi seçer; PSR-3 sekiz seviye ve `{anahtar}` yer tutuculu bir bağlam dizisi tanımlar, hangi seviyenin yazılacağına gerçekleştirim karar verir.
- Arayüz imzayı denetler, anlamı denetlemez; anlam için ayrı ölçüm gerekir.

#### Sonraki Adım

Kurulum dosyası gerçekleştirimi seçti. Bir bağımlılık ise başka birinin yayımladığı kodsa ve o kod güncellenirse kaç dosya düzenlenir? Bağımlılık Yöneticisi dersi bunu ölçüyor.

### Bağımlılık Yöneticisi

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/bagimlilik-yoneticisi

Bildirim dosyası, sürüm kısıtı ve kilit dosyası; kilitsiz bir bağımlılığın alt sürümünün çıktıyı sessizce değiştirmesi; kilit dosyasının sürümü sabitlemesi; ana sürüm sınırının kısıt tarafından korunması ve anlamlı sürümlemenin bir söz olması.

Ortak Arayüz Standartları dersinde gerçekleştirim, kodu bizim yazdığımız bir sınıftı. Bu derste gerçekleştirim başka birinin yayımladığı bir pakettir ve değişiklik sizin deponuzda değil, paketin yayım sürümünde olur. Soru aynı: **bu değişiklik nereye ulaşıyor?**

Bir **paket yöneticisi** üç şeyi birlikte tutar: hangi paketlere bağlı olduğunuzu söyleyen **bildirim dosyası**, hangi sürüm aralığını kabul ettiğinizi söyleyen **sürüm kısıtı** ve çözülen kesin sürümleri kaydeden **kilit dosyası**. PHP'nin yaygın paket yöneticisi de bu üç parçayla çalışır. Bu derste yöneticinin kendisi koşturulmuyor; ağa çıkılmaz. Ölçüm, aynı üç parçayı yerel bir dizinde taklit eden küçük bir çözücüyle yapılır.

**ST4.** Ölçümdeki çözücü `^1.4` biçimli tek kısıtı anlar: `1.4`'ten büyük ya da eşit, `2.0.0`'dan küçük. `^` ile `~` karşılaştırması ayrı bir fonksiyonla modellenir. Gerçek bir yöneticinin sıfırlı ana sürümler (`0.x`) için daha dar çalışan kuralı, birden çok pakete bağlı çözümleme ve bağımlılığın bağımlılıkları modellenmez.

#### Ölçüm Düzeneği

`yayilim.php` önceki derslerle aynıdır: aynı programı değişiklikten önce ve sonra kurup koşturur. Sınıfı sırayla sorar: çıkış 0 değilse **kıran**, çıktı farklıysa **örtük**, düzenlenen dosya bağlantı listesindeyse **bağlantıda**, hiçbiri değilse **yerinde**. Önceki sürüm 0 ile çıkmalıdır ve kullanan `main.php` düzenlenmez.

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

#### Kilitsiz Bağımlılık: Örtük

Gözlem `kilitsiz-surum`: `bagimliliklar.json` `{"tarih": "^1.4"}` diyor, yani `Tarih` paketinin 1.4 ve üstünü, 2.0.0'ın altında kabul ediyor. `cozucu.php` paketler dizinindeki sürümlerden kısıta uyan en yükseğini seçip `kaynak.php` dosyasını yükler. `main.php` tarihi `Tarih::bicimle(2026, 10, 15)` ile biçimlendirir. Tabanda yalnız `paketler/tarih-1.4.0` var ve çözücü onu seçiyor. Değişiklik: paketin 1.5.0 sürümü yayımlanıyor. Sizin deponuzda hiçbir dosya düzenlenmiyor; yalnız `paketler/tarih-1.5.0/kaynak.php` ekleniyor. Kilit dosyası yok.

```php
<?php
// kilitsiz.php — kilit dosyasi olmayan bagimlilik yeni alt surumu alir
require_once 'yayilim.php';

// 11. Bagimliligin yeni bir alt surumu yayimlanir; kilit dosyasi yok
$siniflar[] = olc('kilitsiz-surum', [
    'bagimliliklar.json' => '{"tarih": "^1.4"}',
    'paketler/tarih-1.4.0/kaynak.php' => <<<'PHP'
        <?php
        class Tarih
        {
            public static function bicimle(int $yil, int $ay, int $gun): string
            {
                return sprintf('%04d-%02d-%02d', $yil, $ay, $gun);
            }
        }
        PHP,
    'cozucu.php' => <<<'PHP'
        <?php
        function surumSec(string $paket, string $kisit): string
        {
            $alt = ltrim($kisit, '^');
            $ust = ((int) explode('.', $alt)[0] + 1) . '.0.0';
            $adaylar = [];
            foreach (glob("paketler/$paket-*") as $dizin) {
                $surum = substr($dizin, strlen("paketler/$paket-"));
                if (version_compare($surum, $alt, '>=') && version_compare($surum, $ust, '<')) {
                    $adaylar[] = $surum;
                }
            }
            usort($adaylar, 'version_compare');
            return end($adaylar);
        }
        $istek = json_decode(file_get_contents('bagimliliklar.json'), true);
        $kilit = is_file('kilit.json') ? json_decode(file_get_contents('kilit.json'), true) : [];
        foreach ($istek as $paket => $kisit) {
            require "paketler/$paket-" . ($kilit[$paket] ?? surumSec($paket, $kisit)) . '/kaynak.php';
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'cozucu.php';
        echo Tarih::bicimle(2026, 10, 15), "\n";
        PHP,
], [
    'paketler/tarih-1.5.0/kaynak.php' => <<<'PHP'
        <?php
        class Tarih
        {
            public static function bicimle(int $yil, int $ay, int $gun): string
            {
                return sprintf('%02d.%02d.%04d', $gun, $ay, $yil);
            }
        }
        PHP,
]);

echo kostur('olcum/kilitsiz-surum/once')[1], ' -> ', kostur('olcum/kilitsiz-surum/sonra')[1], "\n";
$adlar = array_keys(oku('olcum/kilitsiz-surum/once'));
sort($adlar);
echo implode(', ', $adlar), "\n";
```

```
kilitsiz-surum     1 dosya  cikis=0   cikti farkli -> ortuk
2026-10-15 -> 15.10.2026
bagimliliklar.json, cozucu.php, main.php, paketler/tarih-1.4.0/kaynak.php
```

Sınıf **örtük**: bir dosya eklendi, çıkış 0 kaldı, ama çıktı `2026-10-15` iken `15.10.2026` oldu. Sizin deponuzda tek satır düzenlenmedi, program çökmedi ve tarih biçimi değişti. Çözücü 1.5.0'ı kısıta uygun en yüksek sürüm olarak seçti; kısıt bunu yasakladığı için değil izin verdiği için oldu. Son satır tabanın dosya listesidir: `kilit.json` listede yok.

#### Kilit Dosyası: Yerinde

Karşıtı `kilitli`: aynı yayım, ama taban `kilit.json` dosyası taşıyor ve dosya `tarih` paketini `1.4.0` sürümüne sabitliyor. Çözücü kilit dosyasında kayıt varsa kısıta bakmadan o sürümü yükler. Değişiklik yine paketin 1.5.0 sürümünün yayımlanmasıdır.

```php
<?php
// kilitli.php — kilit dosyasi sabitlenen surumu korur
require_once 'yayilim.php';

$taban = oku('olcum/kilitsiz-surum/once');
$taban['kilit.json'] = '{"tarih": "1.4.0"}';
$sonra = oku('olcum/kilitsiz-surum/sonra');
olc('kilitli', $taban, [
    'paketler/tarih-1.5.0/kaynak.php' => $sonra['paketler/tarih-1.5.0/kaynak.php'],
]);

echo kostur('olcum/kilitli/once')[1], ' -> ', kostur('olcum/kilitli/sonra')[1], "\n";
```

```
kilitli            1 dosya  cikis=0   cikti ayni   -> yerinde
2026-10-15 -> 2026-10-15
```

Sınıf **yerinde**: bir dosya eklendi, çıkış 0 kaldı, çıktı `2026-10-15` kaldı. Kilit dosyası olmayan ölçümde aynı yayım çıktıyı değiştirmişti; burada kilit çözücüyü 1.4.0'da tuttu. Fark tek dosyadır: sabitleme kararı kilit dosyasında durur ve yeni sürümü ancak o dosyanın güncellenmesi alır.

#### Kısıtın İki Yazılışı: Şapka ve Dalga

Kısıt iki ayrı işaretle yazılabilir ve ikisi aynı şeyi söylemez. `^` işareti sol baştaki sıfır olmayan sürüm hanesini sabitler: `^1.4.0`, 1.4.0 ve üstünü, 2.0.0'ın altında kabul eder. `~` işareti ise yazılan hane sayısına bakar. Üç haneli `~1.4.0`, 1.4.0 ve üstünü yalnız 1.5.0'ın altında kabul eder; iki haneli `~1.4` ise `^1.4` ile aynı aralığı verir. Aralığı hesaplayan yardımcı dosya aşağıda; sonraki iki ölçüm onu kullanır. Bu bir ölçüm değil kaynaktır, çıktı bloğu yoktur.

```php
<?php
// surum.php — ^ ve ~ kisitlarini aralik olarak okur
function aralik(string $kisit): array
{
    $isaret = $kisit[0];
    $parcalar = array_map('intval', explode('.', substr($kisit, 1)));
    $alt = implode('.', array_pad($parcalar, 3, 0));
    if ($isaret === '^' || count($parcalar) < 3) {
        $ust = ($parcalar[0] + 1) . '.0.0';
    } else {
        $ust = $parcalar[0] . '.' . ($parcalar[1] + 1) . '.0';
    }
    return [$alt, $ust];
}

function enYuksek(string $kisit, array $surumler): ?string
{
    [$alt, $ust] = aralik($kisit);
    $uygun = array_filter(
        $surumler,
        fn(string $s) => version_compare($s, $alt, '>=') && version_compare($s, $ust, '<')
    );
    usort($uygun, 'version_compare');
    return $uygun === [] ? null : end($uygun);
}
```

Aynı beş yayım üç kısıtla sınanıyor. Çıktıdaki ilk sütun kısıt, sonrası seçilen sürümdür.

```php
<?php
// ayrim.php — ayni yayimlar uc kisitla farkli surum secer
require_once 'surum.php';

$yayimlar = ['1.4.0', '1.4.7', '1.5.0', '1.9.2', '2.0.0'];
foreach (['^1.4.0', '~1.4.0', '~1.4'] as $kisit) {
    printf("%-7s -> %s\n", $kisit, enYuksek($kisit, $yayimlar));
}
```

```
^1.4.0  -> 1.9.2
~1.4.0  -> 1.4.7
~1.4    -> 1.9.2
```

Üç kısıt da 1.4.0 ile başlıyor, ama `~1.4.0` yama sürümlerinde kalıyor ve 1.4.7'de duruyor; `^1.4.0` alt sürümleri de alıyor ve 1.9.2'ye çıkıyor. Hane sayısını bir eksiltmek, yani `~1.4.0` yerine `~1.4` yazmak, kısıtı yeniden `^` genişliğine getiriyor. Hiçbiri 2.0.0'ı almıyor. Ölçüm, `kilitsiz-surum` gözlemindeki riskin bir kısıt seçimiyle daraltılabileceğini gösterir; ama risk ortadan kalkmaz, yalnız yama sürümlerine iner.

#### Bir Ekibin Dört Adımı

Kilit dosyasının neden deponuza girdiğini bir akış anlatır. Bildirim `^1.4.0`, paket 1.4.0'da ve kilit dosyası henüz yok. Ekipte iki kişi var: siz ve bir arkadaşınız. Akış dosya sistemi kullanmaz; bildirimi, kilit kaydını ve paketin yayım listesini üç değişkende tutar.

```php
<?php
// akis.php — ekipte kilit dosyasi depoda ise herkes ayni surumu alir
require_once 'surum.php';

function kur(string $kisit, array $yayimlar, ?string $kilit): array
{
    if ($kilit !== null) {
        return [$kilit, $kilit, 'kilitten'];
    }
    $surum = enYuksek($kisit, $yayimlar);
    return [$surum, $surum, 'cozuldu'];
}

$kisit = '^1.4.0';
$yayimlar = ['1.4.0'];

[$surum, $kilit, $kaynak] = kur($kisit, $yayimlar, null);
printf("1 ilk kurulum        %s  %s\n", $surum, $kaynak);

$yayimlar[] = '1.4.7';
[$surum, , $kaynak] = kur($kisit, $yayimlar, $kilit);
printf("2 arkadas, kilitli   %s  %s\n", $surum, $kaynak);
[$surum, , $kaynak] = kur($kisit, $yayimlar, null);
printf("2 arkadas, kilitsiz  %s  %s\n", $surum, $kaynak);

[$surum, $kilit, $kaynak] = kur($kisit, $yayimlar, null);
printf("3 guncelleme         %s  %s\n", $surum, $kaynak);

[$surum, , $kaynak] = kur($kisit, $yayimlar, $kilit);
printf("4 arkadas tekrar     %s  %s\n", $surum, $kaynak);
```

```
1 ilk kurulum        1.4.0  cozuldu
2 arkadas, kilitli   1.4.0  kilitten
2 arkadas, kilitsiz  1.4.7  cozuldu
3 guncelleme         1.4.7  cozuldu
4 arkadas tekrar     1.4.7  kilitten
```

İlk kurulumda kilit yoktu, çözücü 1.4.0'ı seçti ve kilit kaydı `1.4.0` oldu. Paket 1.4.7'yi yayımlayınca kilidi depodan alan arkadaşınız yine 1.4.0'ı kurdu; kilit olmasaydı 1.4.7'yi kurardı ve iki makinede iki sürüm çalışırdı. Güncelleme, kilidi bilerek yok sayan adımdır: çözücü 1.4.7'yi seçti ve kilit kaydını yeniden yazdı. Arkadaşınız bu yeni kaydı depodan alınca 1.4.7'de buluştu.

Kilit dosyasının depoya girmesinin sebebi budur: sabitleme kararı kodla birlikte dağıtılmazsa her makine kendi çözümünü yapar. Güncellemek de ayrı bir düzenlemedir; kilit dosyasındaki fark, hangi sürümlerin değiştiğini bir commit içinde gösterir.

#### Anlamlı Sürümlemenin Üç Hanesi

`^` ve `~` işaretlerinin ayırt ettiği haneler birer sözdür. **Ana** hane, geriye uyumsuz bir değişiklikte artar. **Alt** hane, geriye uyumlu bir özellik eklendiğinde artar. **Yama** hanesi, geriye uyumlu bir hata düzeltmesinde artar. `^` bu sözün ilk ikisine güvenir: ana hane sabit kaldıkça kodunuzun çalışmaya devam edeceğini varsayar. `~1.4.0` yalnız üçüncüsüne güvenir.

`kilitsiz-surum` ölçümünde 1.4.0'dan 1.5.0'a geçiş alt hane artışıydı ve söz, çıktının değişmemesini gerektirirdi. Tarih biçimi değiştiğine göre ya paket sözü tutmadı ya da sizin kodunuz, sözün kapsamadığı bir şeye, bu durumda çıktının metnine dayanıyordu. Bu ayrım pratikte önemlidir: söz bozulduysa sorun paketin sahibine bildirilir ve kısıt daraltılır; sizin koddan kaynaklandıysa düzeltme sizin deponuzda yapılır. İki durumda da önce hangisi olduğunu bilmek gerekir, bunun için de değişikliğin ne zaman geldiğini bilmek gerekir. Çözücü hangisi olduğunu bilemez. Bilmesi de gerekmez: kilit dosyası bu belirsizliği ortadan kaldırmaz, ama değişikliğin ne zaman ve sizin onayınızla geleceğini kesinleştirir.

Bu yüzden iki karar ayrı verilir. Kısıt, kodunuzun hangi aralıkta çalışmasını beklediğinizi söyler ve nadiren değişir. Kilit dosyası, o aralıkta bugün hangi sürümü kullandığınızı söyler ve her güncellemede değişir. Birincisini yanlış seçerseniz kilit dosyası sizi korur; kilit dosyasını depoya koymazsanız hiçbir kısıt sizi aynı sürümde tutmaz. Sınırlayıcı ölçüm, kilit yokken kısıtın neye karşı yine de koruduğunu sınar.

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

Kısıt, ana sürüm değişikliğine karşı korur. Bu ölçüm onu doğruluyor: kilit dosyası yok, taban `kilitsiz-surum` tabanı, ve yayımlanan paket 2.0.0. `^1.4` kısıtı 2.0.0'ı dışarıda bırakıyor.

```php
<?php
// ana-surum.php — kisit ana surum degisikligine karsi korur
require_once 'yayilim.php';

$taban = oku('olcum/kilitsiz-surum/once');
olc('ana-surum', $taban, [
    'paketler/tarih-2.0.0/kaynak.php' => <<<'PHP'
        <?php
        class Tarih
        {
            public static function bicimle(int $yil, int $ay, int $gun): string
            {
                return "$gun/$ay/$yil";
            }
        }
        PHP,
]);

echo kostur('olcum/ana-surum/once')[1], ' -> ', kostur('olcum/ana-surum/sonra')[1], "\n";
```

```
ana-surum          1 dosya  cikis=0   cikti ayni   -> yerinde
2026-10-15 -> 2026-10-15
```

Sınıf **yerinde**: 2.0.0 yayımlandı, çözücü yine 1.4.0'ı seçti ve çıktı `2026-10-15` kaldı. `^1.4` kısıtı ana sürüm değişikliğine karşı korur; ama alt sürümdeki davranış değişikliğine karşı korumaz, `kilitsiz-surum` ölçümü bunu gösterdi. Alt sürümün çıktıyı değiştirmeyeceği **anlamlı sürümleme** denen bir sözdür: paketin sahibi verir, çözücü denetlemez. Söz tutulursa ana sürüm sınırı yeter; tutulmazsa yalnız kilit dosyası korur.

#### Özet

- Bu dersin ölçü sınıfı **örtük**. `kilitsiz-surum` gözleminde paketin 1.5.0 sürümü yayımlandı, deponuzda hiçbir dosya düzenlenmedi, çıkış 0 kaldı, çıktı `2026-10-15` iken `15.10.2026` oldu.
- Karşıtı `kilitli` **yerinde**: `kilit.json` sürümü `1.4.0`'da tuttu, çıktı değişmedi.
- Sınırlayıcı ölçüm `ana-surum` **yerinde**: `^1.4` kısıtı 2.0.0'ı dışarıda bıraktı. Kısıt ana sürüme karşı korur, alt sürümün davranışına karşı korumaz.
- `ayrim` ve `akis` ölçümleri iki ayrımı gösterdi: `~1.4.0` yama sürümünde, `^1.4.0` alt sürümde durur; kilit dosyası depodaysa ekibin herkesi aynı sürümü kurar, güncelleme kilidi bilerek yeniden yazar.
- Bildirim dosyası ne istediğinizi, kilit dosyası ne aldığınızı kaydeder; sürüm kısıtı bir aralık, anlamlı sürümleme bir sözdür.

#### Sonraki Adım

Paket yöneticisi, bağımlılığın hangi sürümünün yükleneceğini tek yerde topladı. Sınıfın bağımlılığını ise kim kuruyor? Bağımlılık değişince hangi dosya düzenleniyor? Bağımlılık Enjeksiyonu dersi bunu ölçüyor.

### Bağımlılık Enjeksiyonu

Source: https://academia.sh/tr/kurslar/php-nesne-modeli/bagimlilik-enjeksiyonu

Yapıcıdan enjeksiyon, arayüze bağımlılık ve el ile bağlama; bir hizmetin bağımlılığının yalnız kurulum dosyasında değişmesi; bağımlılığı içeride kurmanın değişikliği hizmetlere taşıması; bağlantı noktasının sözleşme korunduğu sürece geçerli olması; on iki dersin ölçü tablosu.

Bağımlılık Yöneticisi dersi, kodun bağlı olduğu paketin hangi sürümünün kurulacağını tek bir kilit dosyasında topladı. Sınıfın bağımlılığını ise kim kuruyor? Bu derste soru şu: bir hizmetin ihtiyaç duyduğu nesne değişirse **bu değişiklik nereye ulaşıyor** ve hangi dosya düzenleniyor? M10/K04 bir bileşenin **neye ihtiyaç duyduğunu** sordu; burada ölçülen, o gereksinim değiştiğinde değişikliğin kullananlara ulaşıp ulaşmadığıdır.

#### Bağımlılığı Kim Kurar

Bir hizmetin ihtiyaç duyduğu nesneye **bağımlılık** (dependency) denir. Hizmet bağımlılığını iki yoldan edinir: ya kendi içinde `new` ile kurar ya da kurulmuş hâlde dışarıdan alır. İkinci yola **bağımlılık enjeksiyonu** (dependency injection) denir ve PHP'de bir biçimi **yapıcıdan enjeksiyondur**: nesne bağımlılığını yapıcı işlevinin parametresi olarak ister.

İkinci karar, parametrenin tipidir. Hizmet somut bir sınıfın adını değil bir **arayüzün** adını yazarsa, o arayüzü gerçekleştiren her sınıf kabul edilir. Hizmet artık bağımlılığının hangi sınıf olduğunu bilmez, yalnız hangi sözleşmeyi karşıladığını bilir. Nesneleri bir araya getiren işe **bağlama** (wiring) denir ve el ile yapıldığında tek bir yerde, genellikle bir kurulum dosyasında toplanır; bu dosya **bağlantı noktasıdır**.

**ST5.** Ders yalnız yapıcıdan enjeksiyonu ölçer. Özellik ya da yöntem üzerinden enjeksiyon, bir nesne kabının bağımlılıkları kendiliğinden çözmesi ve nesnelerin paylaşılıp paylaşılmaması modellenmez. Ölçümdeki bağlama, bir fonksiyon dizisidir.

#### Ölçüm Düzeneği

`yayilim.php` önceki derslerle aynıdır: aynı programı değişiklikten önce ve sonra kurup koşturur. Sırayla sorar: çıkış 0 değilse **kıran**, çıktı farklıysa **örtük**, düzenlenen dosya bağlantı listesindeyse **bağlantıda**, hiçbiri değilse **yerinde**. Önceki sürüm 0 ile çıkmalıdır ve kullanan `main.php` düzenlenmez.

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

#### El ile Bağlama: Bağlantıda

Gözlem `el-ile-baglama`: `Profil` hizmeti bir `Depo` ister ve bir adı yazıp geri okur. Tabanda üç dosya var. `depo.php` arayüzü ve `DiziDepo` gerçekleştirimini taşır. `main.php` içindeki `Profil` yapıcısında `Depo` tipli bir parametre ister ve hangi depoyu alacağını bilmez. `kurulum.php` ise bir fonksiyon dizisi döndürür: `depo` anahtarı çağrıldığında bir `DiziDepo` kurar.

O dizi **en küçük nesne kabıdır** (IoC container). Bir ad verirsiniz, kurulmuş nesne alırsınız; nesnenin nasıl kurulduğu yalnız bu dosyada yazılıdır. Aşağıdaki küçük blok, her çağrının yeni bir örnek kurduğunu ve dizinin gerçekleştirimin sınıf adını yalnız burada verdiğini gösterir.

```php
<?php
// kap.php — fonksiyon dizisi en kucuk nesne kabidir
interface Depo
{
    public function oku(string $anahtar): ?string;
}
class DiziDepo implements Depo
{
    public function oku(string $anahtar): ?string { return null; }
}
$kurulum = ['depo' => fn () => new DiziDepo()];
$a = $kurulum['depo']();
$b = $kurulum['depo']();
echo get_class($a), "\n";
echo $a === $b ? 'ayni ornek' : 'ayri ornek', "\n";
```

```
DiziDepo
ayri ornek
```

Değişiklik tek dosyada: `kurulum.php` artık depoyu, yazmaları sayan bir `SayanDepo` ile sarıyor. `Profil` ve `main.php` yerinde duruyor. Sonraki blok tabanı `taban.php` olarak diske yazar; sonraki sürücüler onu çeker. Dördüncü argüman bağlantı listesidir ve `kurulum.php`'yi bağlantı noktası olarak adlandırır.

```php
<?php
// taban.php — Profil, Depo ve kurulum dosyasi
return [
    'depo.php' => <<<'PHP'
        <?php
        interface Depo
        {
            public function yaz(string $anahtar, string $deger): void;
            public function oku(string $anahtar): ?string;
        }
        class DiziDepo implements Depo
        {
            private array $veri = [];
            public function yaz(string $anahtar, string $deger): void { $this->veri[$anahtar] = $deger; }
            public function oku(string $anahtar): ?string { return $this->veri[$anahtar] ?? null; }
        }
        PHP,
    'kurulum.php' => <<<'PHP'
        <?php
        return ['depo' => fn () => new DiziDepo()];
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'depo.php';
        class Profil
        {
            public function __construct(private Depo $depo) {}
            public function adAta(string $ad): string { $this->depo->yaz('ad', $ad); return $this->depo->oku('ad'); }
        }
        $kurulum = require 'kurulum.php';
        echo (new Profil($kurulum['depo']()))->adAta('ece'), "\n";
        PHP,
];
```

```php
<?php
// baglama.php — depo kurulum dosyasinda sarilir
require_once 'yayilim.php';

olc('el-ile-baglama', require 'taban.php', [
    'kurulum.php' => <<<'PHP'
        <?php
        class SayanDepo implements Depo
        {
            public int $yazma = 0;
            public function __construct(private Depo $ic) {}
            public function yaz(string $anahtar, string $deger): void { $this->yazma++; $this->ic->yaz($anahtar, $deger); }
            public function oku(string $anahtar): ?string { return $this->ic->oku($anahtar); }
        }
        return ['depo' => fn () => new SayanDepo(new DiziDepo())];
        PHP,
], ['kurulum.php']);

echo kostur('olcum/el-ile-baglama/once')[1], ' -> ', kostur('olcum/el-ile-baglama/sonra')[1], "\n";
```

```
el-ile-baglama     1 dosya  cikis=0   cikti ayni   -> baglantida
ece -> ece
```

İlk satır ölçümün sonucu: bir dosya değişti, çıkış 0 kaldı, çıktı aynı kaldı ve değişen dosya bağlantı listesinde olduğu için sınıf **bağlantıda**. İkinci satır iki sürümün çıktısıdır: `ece` önce de sonra da. `Profil` yeni bir bağımlılık aldığını bilmedi, çünkü yalnız `Depo` tipini tanıyordu. Sayma özelliği, yani hizmete eklenen yeni bir davranış, `Profil` düzenlenmeden geldi.

#### Karşıt: Bağımlılığı İçeride Kurmak

Aynı hizmetler bağımlılıklarını kendi yapıcılarında `new DiziDepo()` ile kursaydı, ilk bakışta fark görünmezdi. Fark değişiklikte çıkar. Aşağıdaki tabanda `Profil` ve `Sepet` adlı iki hizmet depolarını kendileri kuruyor ve `DiziDepo`'nun yapıcısı parametresiz. Değişiklik tek dosyada ve yalnız `DiziDepo`'ya dokunuyor: yapıcı bir anahtar öneki istiyor.

```php
<?php
// icerde.php — iki hizmet bagimliligini kendi icinde kuruyor
require_once 'yayilim.php';

olc('icerde-new', [
    'depo.php' => <<<'PHP'
        <?php
        class DiziDepo
        {
            private array $veri = [];
            public function yaz(string $anahtar, string $deger): void { $this->veri[$anahtar] = $deger; }
            public function oku(string $anahtar): ?string { return $this->veri[$anahtar] ?? null; }
        }
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'depo.php';
        class Profil
        {
            private DiziDepo $depo;
            public function __construct() { $this->depo = new DiziDepo(); }
            public function adAta(string $ad): string { $this->depo->yaz('ad', $ad); return $this->depo->oku('ad'); }
        }
        class Sepet
        {
            private DiziDepo $depo;
            public function __construct() { $this->depo = new DiziDepo(); }
            public function urunAta(string $urun): string { $this->depo->yaz('urun', $urun); return $this->depo->oku('urun'); }
        }
        echo (new Profil())->adAta('ece'), ' ', (new Sepet())->urunAta('kalem'), "\n";
        PHP,
], [
    'depo.php' => <<<'PHP'
        <?php
        class DiziDepo
        {
            private array $veri = [];
            public function __construct(private string $onek) {}
            public function yaz(string $anahtar, string $deger): void { $this->veri[$this->onek . $anahtar] = $deger; }
            public function oku(string $anahtar): ?string { return $this->veri[$this->onek . $anahtar] ?? null; }
        }
        PHP,
]);

echo kostur('olcum/icerde-new/once')[1], "\n";
```

```
icerde-new         1 dosya  cikis=255 cikti farkli -> kiran
ece kalem
```

Sınıf **kıran**: bir dosya değişti, çıkış 255. Son satır önceki sürümün çıktısıdır; sonraki sürüm hiçbir şey basamadı. `Profil` ve `Sepet` düzenlenmedi ama yapıcıdaki `new DiziDepo()` çağrısı yeni zorunlu parametreyi veremedi ve program durdu. Aynı kalıp her hizmette yinelendiği için bağımlılığın değişimi bu iki hizmetin ikisine de ulaştı. Birincisinde bağımlılığı kuran yer tek bir dosyaydı; burada kuran yer her hizmetin kendi gövdesidir, yani bağlantı noktası hizmet sayısı kadardır.

**ST6.** Karşıt ölçümde kıran sonucu, `DiziDepo` yapıcısına eklenen zorunlu parametreden gelir. Parametre isteğe bağlı olsaydı ya da yapıcı hiç değişmeseydi ölçüm başka sonuç verirdi; ders bunu ölçmez.

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

Bağlantı noktası değişikliği karşıladı, ama her değişikliği karşılamaz. Bağlantının geçerliliği sözleşmeye bağlıdır: `Profil` yapıcısı `Depo` tipini ister. Bu ölçümde `kurulum.php`, `yaz` ve `oku` yöntemlerini taşıyan ama `Depo`'yu gerçekleştirmeyen bir nesne döndürür. Yöntem adları aynıdır; yalnız `implements Depo` yoktur. Taban `el-ile-baglama` tabanıdır ve değişiklik yine yalnız `kurulum.php`'dedir.

```php
<?php
// sozlesme.php — kurulum, Depo'yu gerceklestirmeyen bir nesne dondurur
require_once 'yayilim.php';

olc('sozlesmesiz', require 'taban.php', [
    'kurulum.php' => <<<'PHP'
        <?php
        class SozlukDepo
        {
            private array $veri = [];
            public function yaz(string $anahtar, string $deger): void { $this->veri[$anahtar] = $deger; }
            public function oku(string $anahtar): ?string { return $this->veri[$anahtar] ?? null; }
        }
        return ['depo' => fn () => new SozlukDepo()];
        PHP,
], ['kurulum.php']);
```

```
sozlesmesiz        1 dosya  cikis=255 cikti farkli -> kiran
```

Aynı dosya, aynı bağlantı listesi, ama sınıf **kıran** oldu: çıkış 255. Değişen dosya bağlantı listesindeydi; bu yetmedi, çünkü sıralamada çıkış kodu bağlantıdan önce gelir. `Profil` yapıcısındaki tip bildirimi `SozlukDepo`'yu geri çevirdi. Bu ölçümde ileti basılmadı; durduran düzenek Tip Bildirimleri dersindeki aynı tip denetimidir. `SozlukDepo` doğru yöntemlere sahip olsa da sözleşmeyi bildirmedi. Bağlantı noktası yalnız sözleşme korunduğu sürece değişikliği tek dosyada tutar; sözleşmeyi bozan bir bağlama kullananı durdurur.

**ST7.** `sozlesmesiz` ölçümü bağlama kodunun hatalı olduğu durumu modeller: bir tip bildirimi var ve bağlayan yanlış nesneyi veriyor. Tip bildirimi olmasaydı `Profil` aynı yöntem adlarıyla çalışabilirdi; ders bu durumu ölçmez.

#### Özet

- Bağımlılık enjeksiyonu, bir hizmetin ihtiyaç duyduğu nesneyi içeride `new` ile kurmak yerine yapıcısından almasıdır; arayüz tipi hizmeti gerçekleştirimden ayırır.
- Bu dersin ölçü sınıfı **bağlantıda**. `el-ile-baglama` gözleminde depo `kurulum.php` içinde sarıldı, bir dosya değişti, çıkış 0 kaldı ve çıktı `ece` kaldı. Fonksiyon dizisi en küçük nesne kabıdır.
- Karşıtı `icerde-new` **kıran**: iki hizmet depoyu kendi içinde kurduğundan `DiziDepo` yapıcısına parametre eklemek ikisini de durdurdu, çıkış 255.
- Sınırlayıcı ölçüm `sozlesmesiz` **kıran**: `kurulum.php` `Depo`'yu gerçekleştirmeyen bir nesne döndürünce `Profil` yapıcısındaki tip bildirimi durdurdu, çıkış 255.
- Bağlantı noktası yalnız sözleşme korunduğu sürece değişikliği tek dosyada tutar.

#### Kurs Kapanışı

Kurs tek bir soruyu on iki kez sordu: **bu değişiklik nereye ulaşıyor?** Her ders çekirdekten tek bir değişiklik aldı ve dört sınıftan birine yerleştirdi. Aşağıdaki tablo her dersin sınıfını `## Özet` bölümünden, sınırlayıcı ölçümünü dersin `## Sınırlayıcı Ölçüm` bölümünden alır.

| Ders | Ölçülen değişiklik | Sınıf | Sınırlayıcı ölçüm |
|---|---|---|---|
| Sınıflar ve Nesneler | özel özelliğin adı | yerinde | public özelliğe yazan kullanan: yerinde, `E_DEPRECATED` |
| Kalıtım ve Soyut Sınıflar | üst sınıf iç çağrıyı bıraktı | örtük | bileşimle sayaç: yerinde, `3` |
| Arayüzler | arayüze yöntem eklendi | kıran | gövdeli soyut sınıf: yerinde, `120` |
| Trait'ler | trait'e çakışan ad eklendi | kıran | sınıfın kendi yöntemi: yerinde |
| Statik Üyeler ve Sabitler | yapıcı imzası, statik fabrika | yerinde | sabit değeri `20` → `18`: örtük, `120` → `118` |
| Sihirli Yöntemler | `__get` anahtarının adı | örtük | bilinmeyen adda istisna: kıran |
| Tip Bildirimleri | parametreye `int` eklendi | kıran | `?int` ve `'5'`: yerinde, `float(0)` ve `float(6)` |
| Ad Alanları | dış kod aynı adlı sınıf ekledi | yerinde | nitelenmemiş `strlen`: örtük, `5` → `0` |
| Otomatik Yükleme | sınıf dosyası taşındı, 3 dosya | bağlantıda | `urun.php` küçük harf: kıran |
| Ortak Arayüz Standartları | gerçekleştirim değişti | bağlantıda | `{no}` doldurulmuyor: örtük |
| Bağımlılık Yöneticisi | alt sürüm yayımlandı, kilitsiz | örtük | `2.0.0` yayımlandı: yerinde |
| Bağımlılık Enjeksiyonu | depo kurulumda sarıldı | bağlantıda | `Depo` olmayan nesne: kıran |

Derslerin dağılımı **3 yerinde · 3 bağlantıda · 3 kıran · 3 örtük**tür. Her ders çekirdekten tek bir gözlem taşıdığı için bu dağılım çekirdeğin on iki değişikliğinin dağılımıyla aynıdır; çekirdeğin kendisi hiçbir derste koşturulup basılmadı, dağılım derslerin sınıf hücrelerinden sayılmıştır.

Tablodan çıkan iki gözlem kursun tezidir. Birincisi, sınıf değişikliğin büyüklüğünden değil, **kullananın neye dokunduğundan** gelir: aynı tanım değişikliği okuyan kullananı örtük, yazan kullananı yerinde bıraktı; aynı yöntem eklemesi gövdesizse kıran, gövdeliyse yerinde oldu. İkincisi, en tehlikeli sınıf **örtük**tür: kıran değişiklik ilk koşumda kendini ele verir, örtük değişiklik ancak sonuca bakılınca görülür. Sınırlayıcı ölçümler her derste sınıfın bir koşula bağlı olduğunu gösterdi; dört sınıf bir tanımın değil, tanımla kullananın arasındaki bağın ölçüsüdür.

Sıradaki kurs M12/K03, Web ile PHP: Oturum, Veri ve Güvenlik. O kurs oturum ve çerezlerin durumu isteklere nasıl taşıdığını, veritabanı erişimini ve parametreli sorguyu, ardından web güvenliğini konu alır. Bu kursun getirdiği ayrım, değişikliğin nereye ulaştığını sınıflandıran dört ad, orada da bir değişikliğin kullananı nasıl etkilediğini okurken işe yarar.

