---
title: "PHP'de Kalite, Başarım ve Dağıtım"
source: 'https://academia.sh/tr/kurslar/php-uretim'
language: tr
updated: '2026-10-21T05:03:35+00:00'
license: 'CC BY-SA 4.0'
lessons: 10
outcomes:
    - 'Bir davranışı geliştirme ve üretim profilinde ayrı süreçlerde koşturarak hangi yanda durduğunu ölçer'
    - 'Kodu çalıştırmadan okuyan bir denetleyiciyle koşumun yakalayamadığı kusurları bulur'
    - 'İddiaların, hata gösteriminin ve istisna izinin üretimde neden kapatıldığını açıklar'
    - 'Kod önbelleğinin yerinde değişen dosyada ve sembolik bağ geçişinde eski sürümü koşturduğunu gösterir ve önbelleği geçersiz kılar'
    - 'Bellek sınırını, çağrı sayısını ve yerleşik sunucunun istekler arası durumunu iki profilde ölçer'
---

# PHP'de Kalite, Başarım ve Dağıtım

PHP iki ayar dosyası dağıtır, biri geliştirme, biri üretim için, ve ikisi yedi yönergede bilerek ayrışır. Yazdığın kodun bir davranışı üretime çıkıyor mu? Kimi davranış iki yanda da görülür, kimi yalnız geliştiricinin makinesinde kalır, kimi yalnız üretimde ortaya çıkar, kimi hiçbir koşuma girmez ve onu yalnız kodu çalıştırmadan okuyan bir araç bulur. Kurs statik çözümlemeyi, stil denetimini, testleri, hata ayıklamayı, kod önbelleğini, profil çıkarmayı, bellek yönetimini, yerleşik sunucuyu, ini yönergelerini ve dağıtımı tek bir soruyla ölçer — bu davranış üretime çıkıyor mu?

## Kalite Araçları

Statik çözümleme, kod stili denetimi, test çerçeveleri ve hata ayıklayıcı — her biri bir davranışın koşumun içinde mi dışında mı görüldüğünü ve üretime çıkıp çıkmadığını gösteren bir araç olarak.

### Statik Çözümleme

Source: https://academia.sh/tr/kurslar/php-uretim/statik-cozumleme

Kodu çalıştırmadan okuyan araçların ne bulduğunu, ne bulamadığını ve bir davranışın üretime çıkıp çıkmadığını tek bir soruyla ölçmeyi öğrenin.

Web Uygulama Çerçevesi kursu kodu ve ayarı sabit tuttu, bir parçanın istekte koşup koşmadığını ölçtü. Bu kursta kod ve istek sabittir; değişen, kodun koştuğu yerdir. Aynı betik geliştiricinin makinesinde bir davranış, üretimde başka bir davranış gösterebilir. Çoğu zaman bu fark kusur değil, tasarımdır: PHP iki ayrı ayar dosyası dağıtır ve ikisi bilerek ayrışır. Kursun her dersi tek bir soru sorar: bu davranış üretime çıkıyor mu?

İlk ders kodu çalıştırmadan okuyan araçlarla açılır. Statik çözümleme (static analysis), bir programı koşturmadan kaynağını inceleyerek kusur arar. Bu yüzden eksende ayrı bir yerde durur: koşumun içinde hiç görünmeyen kusurları, yalnız koşumun dışındaki bir araç ortaya çıkarabilir.

#### İki Ayar Dosyası, Yedi Fark

PHP kaynak dağıtımı iki örnek ayar dosyası içerir: `php.ini-development` ve `php.ini-production`. İkisi de aynı yönergelerin büyük bölümünü aynı değerle taşır. Yedi yönergede bilerek ayrışırlar: hata gösterimi (`display_errors`), başlangıç hatalarının gösterimi (`display_startup_errors`), hata düzeyi (`error_reporting`), iddialar (`zend.assertions`), istisna izindeki argümanlar (`zend.exception_ignore_args`), argüman dizgesinin uzunluğu (`zend.exception_string_param_max_len`) ve bellek istatistiği (`mysqlnd.collect_memory_statistics`). Üretim profili buna bir de kod önbelleği ekler.

Bu fark bir hata değildir. Üretim, hata iletisini yanıta basmaz, iddiaları derlemez, istisna izine argüman koymaz ve hata düzeyinden kullanımdan kaldırma (deprecation) bildirimlerini çıkarır. Ders bunu kapatmaya çalışmaz; her davranışın hangi yanda durduğunu adlandırır. DevOps ve Platform Mühendisliği müfredatında geliştirme ile üretim arasındaki her sapma gizli kusur sayıldı ve kapatılmaya çalışıldı. Burada farkın çoğu kasıtlıdır ve PHP'nin kendisi tarafından dağıtılır.

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

Kurs her davranışı aynı iki dosyayla ölçer. Birincisi `yan.php`'dir ve kursun bütün derslerinde aynen kalır.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Dosyanın dört parçası vardır. `PROFILLER` sabiti iki ayar dosyasının yedi farkını birebir taşır. Üretimdeki son iki satır dağıtım dosyasında yorum satırıdır; kurs onları açıkça kurar, çünkü kod önbelleği komut satırında varsayılan olarak kapalıdır. `calistir()` bir yoklama betiğini ayrı bir süreçte koşturur ve son satırın `evet` olup olmadığına bakar. `yan()` iki boolean değerden dört yandan birini çıkarır. `yanRaporu()` bunu bir satır ve bir ayrıntı satırı olarak basar.

İki kural bu dosyada doğar. Birincisi `-n` bayrağıdır: alt süreç makinenin ayar dosyasını hiç yüklemez, yalnız profilin yönergeleriyle koşar. Böylece ölçüm, o makinede hangi ayar dosyasının bulunduğuna bağlı kalmaz. İkincisi alt sürecin `'php'` adıyla, PATH üzerinden başlatılmasıdır. `PHP_BINARY` kullanılmaz, çünkü makinenin ikilisinin yoluna güvenilmez: o yol, ikilinin dayandığı kitaplıklar yanında durmayan bir yere işaret edebilir ve süreç hiç başlamayabilir.

Dört yan kurs boyunca değişmez: davranış iki profilde de görülürse **iki yanda da**, yalnız geliştirmede görülürse **yalnız geliştirmede**, yalnız üretimde görülürse **yalnız üretimde**, hiçbirinde görülmezse **koşuma girmiyor**. Yan, etiket olarak değil kural olarak verilir. Her yoklama davranışı gördüğünde `evet`, görmediğinde `hayır` basar. `calistir()` iki profilde de koşar, `yan()` iki sonuçtan adı üretir.

#### On İki Davranış, Dört Yan

İkinci dosya `davranislar.php` on iki küçük yoklamayı bu çekirdekten geçirir. Yoklama metinleri tek tırnaklı nowdoc içindedir; böylece değişken ve kaçış karakterleri sürücüye karışmaz.

```php
<?php
// davranislar.php — on iki davranış, dört yan
declare(strict_types=1);
require 'yan.php';

// Her yoklama, davranış görüldüyse son satıra "evet", görülmediyse "hayır" basar.
$davranislar = [
    'bellek sınırını aşan istek durur' => <<<'PHP'
        <?php
        ini_set('memory_limit', '32M');
        register_shutdown_function(function () {
            $son = error_get_last();
            echo ($son['type'] ?? 0) === E_ERROR ? "evet\n" : "hayır\n";
        });
        $buyuk = str_repeat('x', 64 * 1024 * 1024);
        PHP,
    'istek bitince yıkıcı koşar' => <<<'PHP'
        <?php
        $baglanti = new class {
            public function __destruct() { echo "evet\n"; }
        };
        PHP,
    'uyarı günlüğe düşer' => <<<'PHP'
        <?php
        $gunluk = __DIR__ . '/gunluk.txt';
        @unlink($gunluk);
        ini_set('log_errors', '1');
        ini_set('error_log', $gunluk);
        ob_start();
        echo $tanimsiz;
        ob_end_clean();
        echo str_contains((string) @file_get_contents($gunluk), 'Warning') ? "evet\n" : "hayır\n";
        PHP,
    'yanlış tipteki argüman durdurur' => <<<'PHP'
        <?php
        declare(strict_types=1);
        function adet(int $n): int { return $n; }
        try { adet('3'); echo "hayır\n"; } catch (TypeError) { echo "evet\n"; }
        PHP,
    'tutmayan iddia isteği durdurur' => <<<'PHP'
        <?php
        try { assert(1 === 2); echo "hayır\n"; } catch (AssertionError) { echo "evet\n"; }
        PHP,
    'uyarı yanıta basılır' => <<<'PHP'
        <?php
        ob_start();
        echo $tanimsiz;
        $yanit = ob_get_clean();
        echo str_contains($yanit, 'Warning') ? "evet\n" : "hayır\n";
        PHP,
    'kullanımdan kaldırılmış çağrı günlüğe düşer' => <<<'PHP'
        <?php
        $gunluk = __DIR__ . '/gunluk.txt';
        @unlink($gunluk);
        ini_set('log_errors', '1');
        ini_set('error_log', $gunluk);
        ob_start();
        strlen(null);
        ob_end_clean();
        echo str_contains((string) @file_get_contents($gunluk), 'Deprecated') ? "evet\n" : "hayır\n";
        PHP,
    'istisna izi argüman değerini taşır' => <<<'PHP'
        <?php
        function ode(string $kart) { throw new RuntimeException('red'); }
        try { ode('kart-0042'); } catch (RuntimeException $e) {
            echo count($e->getTrace()[0]['args'] ?? []) > 0 ? "evet\n" : "hayır\n";
        }
        PHP,
    'yerinde değişen dosyanın eski sürümü koşar' => <<<'PHP'
        <?php
        $surum = __DIR__ . '/surum.php';
        file_put_contents($surum, "<?php return 1;\n");
        touch($surum, time() - 60);
        $once = include $surum;
        file_put_contents($surum, "<?php return 2;\n");
        touch($surum, time() - 30);
        $sonra = include $surum;
        echo $sonra === $once ? "evet\n" : "hayır\n";
        PHP,
    'sembolik bağ geçişinden sonra eski sürüm koşar' => <<<'PHP'
        <?php
        foreach ([1, 2] as $n) {
            @mkdir("surum-$n");
            file_put_contents("surum-$n/selam.php", "<?php return $n;\n");
            touch("surum-$n/selam.php", time() - 60);
        }
        @unlink('guncel');
        symlink('surum-1', 'guncel');
        $once = include 'guncel/selam.php';
        @unlink('guncel-yeni');
        symlink('surum-2', 'guncel-yeni');
        rename('guncel-yeni', 'guncel');
        $sonra = include 'guncel/selam.php';
        echo $sonra === $once ? "evet\n" : "hayır\n";
        PHP,
    'çağrılmayan koldaki tanımsız fonksiyon bildirilir' => <<<'PHP'
        <?php
        function rapor(bool $ozet): string { return $ozet ? ozetle() : 'tam'; }
        rapor(false);
        echo error_get_last() !== null ? "evet\n" : "hayır\n";
        PHP,
    'biçimsiz kod bildirilir' => <<<'PHP'
        <?php
        file_put_contents(__DIR__ . '/bicimsiz.php', "<?php\nfunction  topla(\$a,\$b){return \$a+\$b;}   \n");
        include __DIR__ . '/bicimsiz.php';
        topla(1, 2);
        echo error_get_last() !== null ? "evet\n" : "hayır\n";
        PHP,
];

$sayim = array_fill_keys(['iki yanda da', 'yalnız geliştirmede', 'yalnız üretimde', 'koşuma girmiyor'], 0);
foreach ($davranislar as $ad => $kod) {
    file_put_contents('yoklama.php', $kod);
    $sayim[yanRaporu($ad, 'yoklama.php')]++;
}
echo "\n";
foreach ($sayim as $sonYan => $adet) {
    echo "$sonYan: $adet\n";
}
```

Çıktının tamamı şudur:

```text
bellek sınırını aşan istek durur → iki yanda da
    geliştirme evet · üretim evet
istek bitince yıkıcı koşar → iki yanda da
    geliştirme evet · üretim evet
uyarı günlüğe düşer → iki yanda da
    geliştirme evet · üretim evet
yanlış tipteki argüman durdurur → iki yanda da
    geliştirme evet · üretim evet
tutmayan iddia isteği durdurur → yalnız geliştirmede
    geliştirme evet · üretim hayır
uyarı yanıta basılır → yalnız geliştirmede
    geliştirme evet · üretim hayır
kullanımdan kaldırılmış çağrı günlüğe düşer → yalnız geliştirmede
    geliştirme evet · üretim hayır
istisna izi argüman değerini taşır → yalnız geliştirmede
    geliştirme evet · üretim hayır
yerinde değişen dosyanın eski sürümü koşar → yalnız üretimde
    geliştirme hayır · üretim evet
sembolik bağ geçişinden sonra eski sürüm koşar → yalnız üretimde
    geliştirme hayır · üretim evet
çağrılmayan koldaki tanımsız fonksiyon bildirilir → koşuma girmiyor
    geliştirme hayır · üretim hayır
biçimsiz kod bildirilir → koşuma girmiyor
    geliştirme hayır · üretim hayır

iki yanda da: 4
yalnız geliştirmede: 4
yalnız üretimde: 2
koşuma girmiyor: 2
```

Sayılar bağlayıcıdır ve kursun on dersinin hiçbiri bunları değiştirmez: on iki davranışın dördü iki yanda da, dördü yalnız geliştirmede, ikisi yalnız üretimde, ikisi koşuma girmiyor. Dört grup şöyle okunur.

İki yanda da duran dört davranış (bellek sınırı, yıkıcının istek sonunda koşması, açık günlük, `strict_types` altındaki `TypeError`) ayar dosyasından bağımsızdır. Üretimde ekrana basma kapalı olduğu için bir hatayı görmenin başlıca yolu günlüktür ve günlük iki yanda da aynı yere düşer.

Yalnız geliştirmede duran dört davranış, üretimin bilerek sustuğu yerlerdir: tutmayan iddia, yanıta basılan uyarı, günlüğe düşen kullanımdan kaldırma bildirimi ve argüman taşıyan istisna izi. Üretimde `zend.assertions=-1` iddiayı hiç derlemez.

Yalnız üretimde duran iki davranış geliştiricinin hiç görmediği davranışlardır: kod önbelleği dosyayı bir kez derler ve bir daha diske bakmaz, bu yüzden üzerine yazılan dosya da çevrilen sembolik bağ da eski sürümü koşturur.

Koşuma girmeyen iki davranış, koşumun yakalayamadığı kusurlardır. Dersin geri kalanı bunlardan ilkini inceler.

#### Koşumun Bulamadığı Kusur

On iki yoklamadan on birincisi, çağrılmayan koldaki tanımsız fonksiyondur. Kesit şudur.

```php
<?php
// rapor.php — bir kolda tanımsız fonksiyon çağıran fonksiyon
declare(strict_types=1);

function rapor(bool $ozet): string {
    return $ozet ? ozetle() : 'tam';
}
```

`ozetle()` hiçbir yerde tanımlı değildir, ama `rapor(false)` çağrıldığında o kola hiç girilmez. PHP bir fonksiyon adını çağrı anında çözer; çağrılmayan kol için ne derleme ne koşum aşamasında bildirim üretir. Bu davranış iki profilde de aynıdır ve `error_get_last()` `null` kalır. Web Uygulama Çerçevesi kursunun sorusu, parçanın bu istekte çağrılıp çağrılmadığıydı. Burada parça her zaman çağrılır; sorulan, çağrılan parçanın davranışının iki profilde aynı olup olmadığıdır. Bu kusurda cevap iki profilde de aynıdır: sessizlik.

PHP'nin kendi denetimi olan `php -l` bu kusuru da göremez. Komut yalnız söz dizimini denetler; yani dosyanın PHP olarak ayrıştırılıp ayrıştırılamayacağına bakar ve hiçbir fonksiyonu çözmez.

```bash
# kontrol.sh — php -l yalnız söz dizimine bakar
printf '<?php\nfunction rapor( {\n' > bozuk.php
php -n -l bozuk.php >/dev/null 2>&1
echo "bozuk dosya: $?"
printf '<?php\nfunction rapor() { return 1; }\n' > saglam.php
php -n -l saglam.php >/dev/null 2>&1
echo "sağlam dosya: $?"
php -n -l rapor.php >/dev/null 2>&1
echo "tanımsız fonksiyonlu dosya: $?"
```

```text
bozuk dosya: 255
sağlam dosya: 0
tanımsız fonksiyonlu dosya: 0
```

Bozuk dosya 255 verir. Sağlam dosya ile tanımsız fonksiyon çağıran dosya aynı kodu, 0 verir. Komut, hata iletisini ve dosya yolunu içerir; ders yalnız çıkış kodunu basar.

Tokenizer eklentisi bir adım ileri gider. `PhpToken::tokenize()` kaynağı belirteçlere (token) ayırır: anahtar sözcük, ad, değişken, işleç. Bu, kodu çalıştırmadan okumanın en küçük biçimidir. Aşağıdaki denetleyici, tanımlanan fonksiyon adlarını çağrılan adlarla karşılaştırır. Bir ad, kaynakta `function` sözcüğünden hemen sonra geliyorsa tanımlıdır. Bir ad, ardından `(` geliyorsa ve önünde `->`, `::` ya da `new` yoksa çağrılmıştır. Tanımsız sayılan, ne kaynakta tanımlı ne de PHP'nin kendi `function_exists()` denetimini geçen çağrıdır.

```php
<?php
// denetle.php — çağrılan adları tanımlı adlarla karşılaştıran küçük denetleyici
declare(strict_types=1);

/** Kaynağı belirteçlere ayırır; boşluk ve yorumları atar. */
function belirtecler(string $kaynak): array {
    $liste = PhpToken::tokenize($kaynak);
    return array_values(array_filter($liste, fn(PhpToken $b) => !$b->isIgnorable()));
}

/** Tanımlanan ve çağrılan fonksiyon adlarını toplar. */
function adlariTopla(string $kaynak): array {
    $b = belirtecler($kaynak);
    $tanimli = [];
    $cagrilan = [];
    foreach ($b as $i => $belirtec) {
        if (!$belirtec->is(T_STRING)) {
            continue;
        }
        $onceki = $b[$i - 1] ?? null;
        $sonraki = $b[$i + 1] ?? null;
        if ($onceki !== null && $onceki->is(T_FUNCTION)) {
            $tanimli[] = $belirtec->text;
        } elseif ($sonraki !== null && $sonraki->text === '('
            && ($onceki === null || !$onceki->is([T_OBJECT_OPERATOR, T_DOUBLE_COLON, T_NEW]))) {
            $cagrilan[] = $belirtec->text;
        }
    }
    return ['tanimli' => $tanimli, 'cagrilan' => $cagrilan];
}

/** Tanımı bulunamayan çağrıların adlarını döndürür. */
function tanimsizCagrilar(string $kaynak): array {
    $adlar = adlariTopla($kaynak);
    $eksik = [];
    foreach (array_unique($adlar['cagrilan']) as $ad) {
        if (!in_array($ad, $adlar['tanimli'], true) && !function_exists($ad)) {
            $eksik[] = $ad;
        }
    }
    return $eksik;
}
```

Sürücü önce kusurun iki profilde sessiz kaldığını yoklamayla ölçer, sonra denetleyiciyi aynı dosyaya uygular ve son olarak kolu gerçekten çalıştırır.

```php
<?php
// cozumleme.php — koşum mu, okuma mı
declare(strict_types=1);
require 'yan.php';
require 'denetle.php';

file_put_contents('yoklama.php', <<<'PHP'
    <?php
    include 'rapor.php';
    rapor(false);
    echo error_get_last() !== null ? "evet\n" : "hayır\n";
    PHP);
yanRaporu('çağrılmayan koldaki tanımsız fonksiyon bildirilir', 'yoklama.php');

echo 'denetleyicinin bulduğu: ', implode(', ', tanimsizCagrilar(file_get_contents('rapor.php'))), "\n";

include 'rapor.php';
try {
    rapor(true);
} catch (Error $e) {
    echo 'kola girilince: ', get_class($e), "\n";
}
```

```text
çağrılmayan koldaki tanımsız fonksiyon bildirilir → koşuma girmiyor
    geliştirme hayır · üretim hayır
denetleyicinin bulduğu: ozetle
kola girilince: Error
```

Koşum iki profilde de sessizdir: yan, koşuma girmiyor. Denetleyici `ozetle` adını bulur. Kola girildiğinde ise PHP bir `Error` atar; yani kusur gerçektir, yalnız koşum onu o isteğe kadar saklar.

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

Statik çözümleme koşumun göremediğini görür, ama onun da bir sınırı vardır: adın kaynakta okunabilir olması gerekir. Aşağıdaki kod aynı tanımsız fonksiyonu çağırır, ama adı bir değişkenden alır.

```php
<?php
// sinir.php — adı bir değişkende taşınan çağrıyı denetleyici görür mü
declare(strict_types=1);
require 'denetle.php';

file_put_contents('dinamik.php', <<<'PHP'
    <?php
    $ad = 'ozetle';
    $ad();
    PHP);

echo 'denetleyicinin bulduğu tanımsız çağrı: ', count(tanimsizCagrilar(file_get_contents('dinamik.php'))), "\n";
try {
    include 'dinamik.php';
} catch (Error $e) {
    echo 'doğrudan koşum: ', get_class($e), "\n";
}
```

```text
denetleyicinin bulduğu tanımsız çağrı: 0
doğrudan koşum: Error
```

Denetleyici hiçbir şey bulamaz, doğrudan koşum ise `Error` atar. Çağrının adı belirteç düzeyinde `ozetle` sözcüğü bile değildir; bir dize sabitidir (`T_CONSTANT_ENCAPSED_STRING`) ve `$ad` bir değişkendir. Kaynağı okuyan her araç bu noktada ya adı çözmeye çalışan bir çıkarım katmanı gerektirir ya da sınırını kabul eder. Ölçümün öğrettiği, "denetleyici temiz çıktı verdi" cümlesinin "kusur yok" anlamına gelmediğidir: yalnız "bu araç, bu okuma biçimiyle kusur görmedi" anlamına gelir.

#### Özet

- Statik çözümleme kodu koşturmadan okur; bu yüzden ölçü eksenindeki yanı **koşuma girmiyor**'dur.
- PHP'nin geliştirme ve üretim ayar dosyaları yedi yönergede bilerek ayrışır: hata gösterimi, başlangıç hatalarının gösterimi, hata düzeyi, iddialar, istisna izindeki argümanlar, argüman dizgesi uzunluğu ve bellek istatistiği.
- Ölçü sınıfı tek sorudur, bu davranış üretime çıkıyor mu; dört yanıt (iki yanda da, yalnız geliştirmede, yalnız üretimde, koşuma girmiyor) kurs boyunca değişmez ve on iki davranışın dağılımı 4, 4, 2, 2'dir.
- `php -n -l` yalnız söz dizimini denetler: bozuk dosya 255, sağlam dosya ve tanımsız fonksiyonlu dosya 0 verir.
- Belirteçleri okuyan küçük bir denetleyici çağrılan adları tanımlı adlarla karşılaştırıp `ozetle` adını bulur, ama dize olarak taşınan adı göremez.

#### Sonraki Adım

Bu derste koşumun göremediği kusuru, kodu okuyan bir araç gösterdi. Aynı sınıfın ikinci davranışı da koşuma girmez: biçimsiz kod iki profilde sorunsuz koşar. Sonraki ders, kodun biçiminin koşumu değiştirip değiştirmediğini sorar ve aynı belirteç okumasını boşluk, satır sonu ve girinti için kullanır.

### Kod Stili Denetimi

Source: https://academia.sh/tr/kurslar/php-uretim/kod-stili-denetimi

Biçimsiz kodun iki profilde de aynı koştuğunu, yalnız bir stil denetleyicisinin onu bildirdiğini ve biçimlendiricinin anlamı nerede değiştirebileceğini ölçün.

Önceki derste koşumun göremediği bir kusuru, kodu çalıştırmadan okuyan bir araç buldu. Kodun biçimi de aynı sınıfa girer mi? Fazladan boşluk, satır sonunda unutulmuş bir sekme ya da yarısı sekme yarısı boşlukla girintilenmiş bir dosya, PHP için hiçbir şey ifade etmeyebilir. Bu ders bu varsayımı ölçer ve ölçü eksenindeki aynı soruyu sorar: bu davranış üretime çıkıyor mu?

Stil denetleyicisi (style checker), kaynağı kod standardına göre okuyup sapmaları bildiren araçtır. Standart önerisi (PSR) kavramı Nesneye Dayalı PHP ve Standartlar kursunda tanıtıldı; kod standardı (coding standard) ise kodun nasıl yazılacağını belirleyen kurallar bütünüdür. Burada standardın içeriği değil, onu uygulayan aracın koşumdaki yeri ölçülür.

#### Biçimsiz Kod İki Profilde de Koşar

Dersin ölçüm çekirdeği önceki dersle aynıdır. `yan.php` hiç değişmez; bu derste yalnız kendi yoklamasını `yanRaporu()` ile basar.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Ölçülecek kod, bilerek biçimsiz yazılmış küçük bir dosyadır: fonksiyon adından sonra iki boşluk, satır sonunda artık boşluk, bir satırda sekme ve bir satırda dört boşlukla girinti. Dosya sürücü tarafından kaçış dizileriyle üretilir; böylece görünmeyen karakterler dersin kaynağında da görünür kalır.

#### Küçük Bir Stil Denetleyicisi

Denetleyici önceki dersteki gibi belirteçlere bakar. PHP'nin kaynağı belirteçlere ayırması, boşluğu da bir belirteç yapar: `T_WHITESPACE`. Boşluk belirteci satır sonlarını ve ardından gelen girintiyi tek parça taşır. Denetleyici bu parçayı satır sonlarında böler: her satır sonundan önce kalan boşluk, satır sonu boşluğudur; satır sonu içermeyen ve en az iki karakterlik boşluk ardışık boşluktur; son satır sonundan sonra kalan boşluk girintidir ve sekme içeriyorsa sekme girintisi, içermiyorsa boşluk girintisi olarak sayılır.

Biçimlendirici (formatter) aynı bölmeyi kullanır, ama sayı çıkarmak yerine boşluğu yeniden yazar: satır sonu boşluğunu siler, ardışık boşluğu tek boşluğa indirir ve her sekmeyi dört boşlukla değiştirir. Boşluk dışındaki her belirteç, metniyle aynen geri yazılır. `ciktisi()` fonksiyonu, bir dosyayı profilin yönergeleriyle ayrı bir süreçte koşturup çıktısını geri verir.

```php
<?php
// stil.php — belirteçlere bakan stil denetleyicisi ve biçimlendirici
declare(strict_types=1);

/** Boşluk belirteçlerinden dört sayı çıkarır; dize ve yorum içindeki boşluğa bakmaz. */
function stilSayimlari(string $kaynak): array {
    $sayim = ['satirSonu' => 0, 'ardisik' => 0, 'sekmeGirinti' => 0, 'bosluk' => 0];
    foreach (PhpToken::tokenize($kaynak) as $belirtec) {
        if (!$belirtec->is(T_WHITESPACE)) {
            continue;
        }
        $parcalar = explode("\n", $belirtec->text);
        $son = array_pop($parcalar);
        foreach ($parcalar as $parca) {
            if ($parca !== '') {
                $sayim['satirSonu']++;
            }
        }
        if ($parcalar === []) {
            if (strlen($son) >= 2) {
                $sayim['ardisik']++;
            }
        } elseif ($son !== '') {
            $sayim[str_contains($son, "\t") ? 'sekmeGirinti' : 'bosluk']++;
        }
    }
    return $sayim;
}

/** Yalnız boşluk belirteçlerini yeniden yazar; öteki belirteçler aynen kalır. */
function bicimlendir(string $kaynak): string {
    $cikti = '';
    foreach (PhpToken::tokenize($kaynak) as $belirtec) {
        if (!$belirtec->is(T_WHITESPACE)) {
            $cikti .= $belirtec->text;
            continue;
        }
        $parcalar = explode("\n", $belirtec->text);
        $son = array_pop($parcalar);
        if ($parcalar === []) {
            $cikti .= ' ';
            continue;
        }
        $cikti .= str_repeat("\n", count($parcalar)) . str_replace("\t", '    ', $son);
    }
    return $cikti;
}

/** Betiği verilen yönergelerle ayrı bir süreçte koşturur ve çıktısını döndürür. */
function ciktisi(string $betik, array $yonergeler): string {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    return $cikti;
}
```

Sürücü biçimsiz dosyayı üretir, aynı dosyanın biçimlendirilmiş halini de yazar ve üç şeyi ölçer. Birincisi, biçimsiz kodun iki profilde bildirim üretip üretmediği; ikincisi, denetleyicinin ve biçimlendiricinin sayıları; üçüncüsü, dört koşumun çıktısı: biçimsiz ve düzgün dosya, geliştirme ve üretim profili.

```php
<?php
// bicim.php — biçim koşumu değiştirmez
declare(strict_types=1);
require 'yan.php';
require 'stil.php';

$bicimsiz = "<?php\n"
    . "function  topla(\$a,\$b){return \$a+\$b;}   \n"
    . "function  carp(\$a,\$b){\n"
    . "\t\$sonuc = \$a * \$b;\n"
    . "    return \$sonuc;\n"
    . "}\n"
    . "echo topla(2, 3), carp(2, 3), \"\\n\";\n";
file_put_contents('bicimsiz.php', $bicimsiz);
file_put_contents('duzgun.php', bicimlendir($bicimsiz));

file_put_contents('yoklama.php', <<<'PHP'
    <?php
    ob_start();
    include 'bicimsiz.php';
    ob_end_clean();
    echo error_get_last() !== null ? "evet\n" : "hayır\n";
    PHP);
yanRaporu('biçimsiz kod bildirilir', 'yoklama.php');

function sayimSatiri(string $kaynak): string {
    $s = stilSayimlari($kaynak);
    return "satır sonu boşluğu {$s['satirSonu']} · ardışık boşluk {$s['ardisik']}"
        . " · sekme girintisi {$s['sekmeGirinti']} · boşluk girintisi {$s['bosluk']}";
}
echo 'biçimsiz: ', sayimSatiri($bicimsiz), "\n";
echo 'düzgün: ', sayimSatiri(file_get_contents('duzgun.php')), "\n";

$sonuclar = [];
foreach (['bicimsiz.php', 'duzgun.php'] as $dosya) {
    foreach (PROFILLER as $yonergeler) {
        $sonuclar[] = trim(ciktisi($dosya, $yonergeler));
    }
}
echo 'dört koşumun çıktısı: ', implode(' ', $sonuclar), "\n";
echo 'hepsi aynı: ', count(array_unique($sonuclar)) === 1 ? 'evet' : 'hayır', "\n";
$duzgun = file_get_contents('duzgun.php');
echo 'ikinci geçiş dosyayı değiştirir mi: ', bicimlendir($duzgun) === $duzgun ? 'hayır' : 'evet', "\n";
```

```text
biçimsiz kod bildirilir → koşuma girmiyor
    geliştirme hayır · üretim hayır
biçimsiz: satır sonu boşluğu 1 · ardışık boşluk 2 · sekme girintisi 1 · boşluk girintisi 1
düzgün: satır sonu boşluğu 0 · ardışık boşluk 0 · sekme girintisi 0 · boşluk girintisi 2
dört koşumun çıktısı: 56 56 56 56
hepsi aynı: evet
ikinci geçiş dosyayı değiştirir mi: hayır
```

Yan, koşuma girmiyor: biçimsiz dosya iki profilde de bildirim üretmeden koşar. Denetleyici ise dört sapma türünü ayrı ayrı sayar; biçimlendirilmiş dosyada satır sonu boşluğu, ardışık boşluk ve sekme girintisi sıfıra iner, boşluk girintisi ikiye çıkar. Dört koşumun çıktısı birebir aynıdır. Biçimlendirici yalnız boşluk belirteçlerine dokunduğu için dosyanın anlamı değişmez; bunu kanıtlayan, dört çıktının eşitliğidir.

#### Denetim ile Düzeltme Ayrı İşlerdir

Çıktının son satırı iki aracın farkını gösterir. Denetleyici okur ve bildirir; hiçbir dosyayı değiştirmez. Biçimlendirici yazar; bu yüzden aynı dosyaya ikinci kez uygulandığında dosyayı artık değiştirmemesi gerekir, aksi halde her çalıştırma bir sonraki farkı üretir. Ölçüm bunu doğrular: ikinci geçiş dosyayı değiştirmez.

İki aracın ortak noktası, ikisinin de koşumun dışında durmasıdır. İkisinin de üretime gönderilen pakete girmesi gerekmez; geliştiricinin makinesinde ve dağıtımdan önceki adımda koşarlar. Bu, önceki dersin statik çözümleyicisiyle aynı konumdur ve ölçü eksenindeki yanı aynı kılar: koşuma girmiyor. Fark, kusurun sınıfındadır. Statik çözümleme koşumun yakalamadığı bir yanlışı bulur; stil denetimi ise koşumun yanlış saymadığı bir sapmayı bulur. PHP biçimsiz kodu da, satır sonundaki boşluğu da hata saymaz, çünkü ikisi de dilin kurallarına uyar.

Bu yüzden stil denetiminin gerekçesi doğruluk değil, okunurluk ve tutarlılıktır. Satır sonu boşluğu bir sürüm farkında görünmez bir değişiklik olarak belirir; karışık girinti, aynı dosyanın farklı editörlerde farklı hizalanmasına yol açar; ardışık boşluk, bir değişikliğin gerçekte neyi değiştirdiğini gizler. Standardın uygulanmasını insanın dikkatine bırakmak yerine bir araca devretmek, bu üç sapmayı gözden kaçırılabilir olmaktan çıkarır.

#### Biçimin Koşumu Değiştirdiği Yer

Şimdiye dek biçim koşumu değiştirmedi: boşluk, girinti ve satır sonu boşluğu, çıktıyı bir karakter bile oynatmadı. PHP'de bu kuralın bir istisnası vardır ve istisna, stil denetiminin neden yalnız estetik bir iş sayılmaması gerektiğini gösterir. Dosya `<?php` ile açılır, `?>` ile kapanır; kapanış etiketinden sonra gelen her karakter, PHP kodu değil düz metin olarak çıktıya yazılır. Tek bir istisna vardır: kapanış etiketini hemen izleyen tek satır sonu yutulur.

Ölçüm aynı adlı dosyayı üç ayrı içerikle üretir ve her birini bir çıktı tamponunda dahil eder. Dosyanın yazdığı tek karakter `a` harfidir; tamponun uzunluğu bundan fazlaysa, kapanış etiketinden sonraki boşluk çıktıya sızmış demektir.

```php
<?php
// kapanis.php — kapanış etiketinden sonraki karakter çıktıya sızar mı
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'tek satır sonu yutulur' => <<<'PHP'
        <?php
        $kapat = '?' . '>';
        file_put_contents('parca.php', "<?php echo 'a'; $kapat\n");
        ob_start();
        include 'parca.php';
        echo strlen(ob_get_clean()) === 1 ? "evet\n" : "hayır\n";
        PHP,
    'ikinci satır sonu sızar' => <<<'PHP'
        <?php
        $kapat = '?' . '>';
        file_put_contents('parca.php', "<?php echo 'a'; $kapat\n\n");
        ob_start();
        include 'parca.php';
        echo strlen(ob_get_clean()) === 2 ? "evet\n" : "hayır\n";
        PHP,
    'kapanıştan sonraki boşluk sızar' => <<<'PHP'
        <?php
        $kapat = '?' . '>';
        file_put_contents('parca.php', "<?php echo 'a'; $kapat ");
        ob_start();
        include 'parca.php';
        echo strlen(ob_get_clean()) === 2 ? "evet\n" : "hayır\n";
        PHP,
];

foreach ($yoklamalar as $ad => $kod) {
    file_put_contents('yoklama.php', $kod);
    yanRaporu($ad, 'yoklama.php');
}
```

```text
tek satır sonu yutulur → iki yanda da
    geliştirme evet · üretim evet
ikinci satır sonu sızar → iki yanda da
    geliştirme evet · üretim evet
kapanıştan sonraki boşluk sızar → iki yanda da
    geliştirme evet · üretim evet
```

Üç yoklama da iki yandadır: sızıntı bir ayar farkına bağlı değildir, dilin kuralıdır. Bu yüzden önceki üç sapmadan ayrılır. Satır sonu boşluğu hiçbir şeyi değiştirmezdi; kapanış etiketinden sonraki boşluk ise çıktıyı değiştirir. Gerçek bir uygulamada bu, bir dosyanın sonundaki fazladan bir satırın, o dosyayı dahil eden kodun çıktısına bir karakter eklemesi demektir; çıktısı karakter karakter karşılaştırılan bir sınama ya da ikili bir yanıt, bu yüzden bozulur. Ölçü sınıfı aynıdır: koşuma girmiyor değil, iki yanda da. Saf PHP dosyalarında kapanış etiketini hiç yazmamak, bu sorun sınıfını bütünüyle ortadan kaldırır; PSR-12 gibi stil standartları da saf PHP dosyalarında kapanış etiketinin yazılmamasını kural olarak koyar.

#### Dosyanın İlk Deyimi

Biçimle ilgili ikinci nokta, `declare(strict_types=1)` deyiminin yeridir. Bu deyim dosyanın ilk deyimi olmak zorundadır; öncesinde bir `echo` varsa PHP dosyayı derlemeyi reddeder. Bir stil denetleyicisi bu sırayı bir kural olarak tutabilir, ama sıra yalnız stil değildir; yanlış yerleştirilmiş bir deyim dosyayı çalıştırılamaz kılar. Aşağıdaki sürücü iki küçük dosya yazar, her birini ayrı bir PHP sürecinde koşturur ve çıkış kodunun sıfırdan farklı olup olmadığını sorar.

```php
<?php
// sira.php — declare deyiminin yeri koşumu değiştirir mi
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'declare ilk deyim değilse dosya koşmaz' => <<<'PHP'
        <?php
        file_put_contents('parca.php', "<?php\necho 1;\ndeclare(strict_types=1);\n");
        exec('php -n parca.php 2>/dev/null', $cikti, $kod);
        echo $kod !== 0 ? "evet\n" : "hayır\n";
        PHP,
    'declare ilk deyimse dosya koşar' => <<<'PHP'
        <?php
        file_put_contents('parca.php', "<?php\ndeclare(strict_types=1);\necho 1;\n");
        exec('php -n parca.php 2>/dev/null', $cikti, $kod);
        echo $kod === 0 ? "evet\n" : "hayır\n";
        PHP,
];

foreach ($yoklamalar as $ad => $kod) {
    file_put_contents('yoklama.php', $kod);
    yanRaporu($ad, 'yoklama.php');
}
```

```text
declare ilk deyim değilse dosya koşmaz → iki yanda da
    geliştirme evet · üretim evet
declare ilk deyimse dosya koşar → iki yanda da
    geliştirme evet · üretim evet
```

Sonuç yine iki yandadır. Derleme hatası bir ayara değil, dilin söz dizimine bağlıdır; geliştirmede de, üretimde de aynı şekilde oluşur. Geliştirme profilinde hata yanıta basılırken üretimde yalnız günlüğe gider, ama derlenmeyen dosya iki yanda da koşmaz. Bu iki ölçüm birlikte okunduğunda stil denetimi iki sınıfa ayrılır: koşumu hiç etkilemeyen sapmalar (satır sonu boşluğu, girinti, ardışık boşluk) ve koşumu etkileyen sapmalar (kapanış etiketinden sonraki karakter, ilk deyimin yeri). İkinci sınıf, stil denetleyicisinin bulmasının bir kolaylık değil, bir güvence olduğu yerdir.

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

Boşluk her zaman boşluk değildir. Bir dize ya da nowdoc içinde satır sonundaki boşluk, dizenin içeriğidir; silinirse program başka bir değer üretir. Bu yüzden belirteç tipine bakmayan bir düzeltme tehlikelidir. Aşağıdaki kaynak, satır sonunda üç boşluk taşıyan bir nowdoc içerir. Aynı dosya üç biçimde koşturulur: özgün haliyle, her satır sonundaki boşluğu silen düzenli ifadeyle düzeltilmiş haliyle ve yalnız `T_WHITESPACE` belirteçlerine dokunan biçimlendiriciyle.

```php
<?php
// sinir.php — belirteç tipine bakmayan düzeltme anlamı değiştirir
declare(strict_types=1);
require 'yan.php';
require 'stil.php';

$kaynak = "<?php\n"
    . "\$metin = <<<'METIN'\n"
    . "ilk   \n"
    . "ikinci\n"
    . "METIN;\n"
    . "echo strlen(\$metin), \"\\n\";\n";

$surumler = [
    'özgün' => $kaynak,
    'belirteçsiz düzeltme' => preg_replace('/[ \t]+$/m', '', $kaynak),
    'belirteçli biçimlendirme' => bicimlendir($kaynak),
];
foreach ($surumler as $ad => $metin) {
    file_put_contents('metin.php', $metin);
    echo "$ad: ", trim(ciktisi('metin.php', PROFILLER['geliştirme'])), "\n";
}
echo 'belirteçli denetleyicinin saydığı satır sonu boşluğu: ', stilSayimlari($kaynak)['satirSonu'], "\n";
echo 'belirteçsiz düzenli ifadenin bulduğu: ', preg_match_all('/[ \t]+$/m', $kaynak), "\n";
```

```text
özgün: 13
belirteçsiz düzeltme: 10
belirteçli biçimlendirme: 13
belirteçli denetleyicinin saydığı satır sonu boşluğu: 0
belirteçsiz düzenli ifadenin bulduğu: 1
```

Özgün dosya ile biçimlendirilmiş dosya aynı uzunlukta (13) bir dize üretir; belirteçsiz düzeltme ise nowdoc içindeki üç boşluğu da silip dizeyi kısaltır. Belirteçli denetleyici nowdoc içeriğini boşluk saymaz, düzenli ifade sayar. Aradaki fark, stil aracının kaynağı metin olarak değil belirteç dizisi olarak okuması gerektiğini gösterir. Biçimlendiricinin güvencesi de buradan gelir: yalnız `T_WHITESPACE` belirteçlerini yeniden yazan bir araç dize içeriğine erişemez. Sınır ise şudur: bir dize ya da yorum içindeki boşluğun gereksiz olup olmadığı, belirteç okumasının cevaplayabileceği bir soru değildir.

#### Özet

- Biçimsiz kod iki profilde de bildirim üretmeden aynı çıktıyla koşar; bu davranışın yanı **koşuma girmiyor**'dur ve onu yalnız stil denetleyicisi bildirir.
- Boşluk da bir belirteçtir (`T_WHITESPACE`): satır sonu boşluğu, ardışık boşluk, sekme girintisi ve boşluk girintisi bu belirteçten sayılır.
- Biçimlendirici yalnız boşluk belirteçlerini yeniden yazdığında anlamı değiştirmez; biçimsiz ve düzgün dosya dört koşumda aynı çıktıyı verir.
- Belirteç tipine bakmayan düzeltme, nowdoc ya da dize içindeki satır sonu boşluğunu da siler ve çıktıyı değiştirir.
- Boşluk biçimi için cevap iki profilde de aynıdır: biçim farkı koşuma girmez.
- Kapanış etiketinden sonraki karakter çıktıya sızar ve `declare(strict_types=1)` ilk deyim değilse dosya derlenmez; ikisi de dilin kuralıdır (ölçü sınıfı: iki yanda da).

#### Sonraki Adım

Bu derste koşumun hiç görmediği ikinci davranış, stil denetleyicisinin elinde bildirime dönüştü. Bir sonraki ders, koşuma giren ama profile göre davranışı değişen bir araca geçer: iddia. Tutmayan bir iddia geliştirmede bir `AssertionError` atar; aynı iddia üretim profilinde derlenmez bile. Test çerçevelerinin dayandığı bu mekanizmanın hangi yanda durduğunu ve bir testin kendi yardımcısı üzerine kurulduğunda neyin değiştiğini ölçeceğiz.

### Test Çerçeveleri

Source: https://academia.sh/tr/kurslar/php-uretim/test-cerceveleri

assert() tabanlı bir sınamanın hangi profilde tutmayan iddiayı yakaladığını, hangi profilde her şeyi geçirdiğini ve kendi karşılaştırma yardımcısının neden iki yanda da çalıştığını ölçün.

Önceki ders, kodun biçiminin koşumu değiştirmediğini ve bu yüzden stil denetiminin koşumun dışında durduğunu gösterdi. Test koşumun dışında mı durur, içinde mi? Bu sorunun cevabı, testin neye dayandığına bağlıdır. PHP'de en küçük sınama aracı `assert()` fonksiyonudur ve onun davranışı, iki ayar dosyasının yedi farkından birine bağlıdır: `zend.assertions`.

Üç tür test, sınanan şeyin büyüklüğüne göre ayrılır. Birim testi (unit test) tek bir fonksiyonu ya da sınıfı yalıtılmış olarak sınar. Tümleşim testi (integration test) birkaç parçanın birlikte çalışmasını, örneğin bir fonksiyonun bir dosya ya da veritabanıyla birlikte davranışını sınar. Davranış testi (behavior test) sistemi dışarıdan, kullanıcının gördüğü sonuç üzerinden sınar. Test çerçevesi (test framework) bu testleri yazmak ve koşturmak için ortak bir iskelet sağlar: testleri bulur, her birini ayrı koşturur, sonuçları toplar ve geçen ile kalanı raporlar. Bu ders hiçbir test çerçevesinin adını anmaz ve onun yerine test çerçevelerinin dayandığı mekanizmayı ölçer: iddia (assertion).

Rust müfredatının beşinci kursunda (M10/K05) güvencenin nerede bittiği arandı. Burada güvencenin sınırı değil, iddianın hangi profilde derlendiği ölçülür.

#### assert() ile Sınama

Ölçüm çekirdeği önceki derslerle aynıdır. `yan.php` hiç değişmez.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Sınanan kod, bilerek yanlış yazılmış bir toplama fonksiyonudur: `topla()` iki sayıyı toplamak yerine çıkarır. `iddiaYakalandi()` fonksiyonu `assert(topla(2, 3) === 5)` iddiasını bir `try` bloğunda koşturur ve `AssertionError` fırlatılırsa `true` döndürür. `say()` fonksiyonu ise her çağrıldığında sayacı artırır: durumu değiştiren bir çağrının, bir iddianın içinde nasıl davrandığını ölçmek için vardır. Üçüncü yardımcı, `esitOlmali()`, `assert()` kullanmaz; iki değeri katı eşitlik karşılaştırmasıyla kıyaslar ve ayrışırlarsa `RuntimeException` fırlatır.

```php
<?php
// sinama.php — bilerek yanlış fonksiyon ve üç sınama yardımcısı
declare(strict_types=1);

function topla(int $a, int $b): int {
    return $a - $b;
}

function iddiaYakalandi(): bool {
    try {
        assert(topla(2, 3) === 5);
        return false;
    } catch (AssertionError) {
        return true;
    }
}

$sayac = 0;

function say(): bool {
    global $sayac;
    $sayac++;
    return true;
}

function esitOlmali(mixed $beklenen, mixed $gelen): void {
    if ($beklenen !== $gelen) {
        throw new RuntimeException('beklenen ile gelen ayrıştı');
    }
}
```

Sürücü üç yoklamayı iki profilde koşturur. Birincisi tutmayan iddianın yakalanıp yakalanmadığını, ikincisi iddia içindeki çağrının koşup koşmadığını, üçüncüsü `esitOlmali()` ile yazılmış aynı sınamanın yanlışı yakalayıp yakalamadığını sorar.

```php
<?php
// sinamalar.php — üç sınama, iki profil
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'tutmayan iddia yakalanır' => <<<'PHP'
        <?php
        require 'sinama.php';
        echo iddiaYakalandi() ? "evet\n" : "hayır\n";
        PHP,
    'iddia içindeki çağrı koşar' => <<<'PHP'
        <?php
        require 'sinama.php';
        assert(say());
        echo $sayac === 1 ? "evet\n" : "hayır\n";
        PHP,
    'kendi karşılaştırma yardımcısı yanlışı yakalar' => <<<'PHP'
        <?php
        require 'sinama.php';
        try {
            esitOlmali(5, topla(2, 3));
            echo "hayır\n";
        } catch (RuntimeException) {
            echo "evet\n";
        }
        PHP,
];

foreach ($yoklamalar as $ad => $kod) {
    file_put_contents('yoklama.php', $kod);
    yanRaporu($ad, 'yoklama.php');
}
```

```text
tutmayan iddia yakalanır → yalnız geliştirmede
    geliştirme evet · üretim hayır
iddia içindeki çağrı koşar → yalnız geliştirmede
    geliştirme evet · üretim hayır
kendi karşılaştırma yardımcısı yanlışı yakalar → iki yanda da
    geliştirme evet · üretim evet
```

İlk iki yoklama yalnız geliştirmede çıkar. Geliştirme profilinde (`zend.assertions=1`) tutmayan iddia `AssertionError` fırlatır ve sınama kalır. Üretim profilinde (`zend.assertions=-1`) PHP `assert()` çağrısını hiç derlemez; iddia ne bir değer üretir ne bir istisna fırlatır. Aynı yanlış fonksiyon üretim profilinde sınamadan geçer. Bir testin geçmesi, kodun doğru olduğunu söylemez; testin hangi profilde koştuğunu da söyler.

İkinci yoklama bunun ayrıntısını gösterir. İddia derlenmediğinde içindeki ifade de değerlendirilmez. `assert(say())` içindeki çağrı, geliştirmede sayacı artırır, üretimde artırmaz. Aşağıdaki betik iki değeri doğrudan basar.

```bash
# sayac.sh — iddia içindeki çağrı hangi profilde koşar
cat > sayac.php <<'PHP'
<?php
require 'sinama.php';
assert(say());
echo "sayaç: $sayac\n";
PHP
echo "zend.assertions=1:"
php -n -d zend.assertions=1 sayac.php
echo "zend.assertions=-1:"
php -n -d zend.assertions=-1 sayac.php
```

```text
zend.assertions=1:
sayaç: 1
zend.assertions=-1:
sayaç: 0
```

Bu, iddiayı bir sınama aracı olarak kullanmanın bilinen tuzağıdır: iddianın içine bir işi yaptıran çağrı konursa iş yalnız bir profilde yapılır. İddia içindeki ifade yalnızca bir koşulu okumalı, durumu değiştirmemelidir.

#### Kendi Karşılaştırma Yardımcısı

Üçüncü yoklama farklı bir sonuç verir: `esitOlmali()` iki profilde de yanlışı yakalar. Yardımcı `assert()` çağırmaz ve `AssertionError`'a dayanmaz; kendi koşulunu kendisi değerlendirir ve kendi istisnasını kendisi fırlatır. Bu yüzden `zend.assertions` ayarı onu etkilemez. Test çerçevelerinin karşılaştırma yöntemleri de aynı sebeple `assert()` üzerine kurulmaz: bir testin üretim profilinde sessizce geçmesi, o testin hiçbir şey sınamadığı anlamına gelir.

Bunun bedeli küçüktür: iki satırlık bir yardımcı, `assert()` ile aynı işi yapar ve profilden bağımsızdır. Geriye bir ayrım kalır. `assert()` kodun içine yazılan, çalışma sırasında bir varsayımı sorgulayan bir iddiadır; geliştirmede fazladan güvence sağlar ve üretimde bedeli sıfıra indirilir. Test ise koşumun dışında, kodu çağıran ayrı bir programdır ve her profilde aynı sonucu üretmelidir.

#### Test Çerçevesinin Yaptığı İş

Bu dersin sürücüsü küçük bir test çerçevesidir. Yoklamaları bir listede toplar, her birini ayrı bir dosyaya yazar, her dosyayı ayrı bir süreçte koşturur ve sonucu tek satırda raporlar. Üç iş, gerçek test çerçevelerinin de çekirdeğidir. Testleri bulma, listeyi kurar. Yalıtma, bir testin bıraktığı durumun öbürünü etkilemesini engeller: ikinci yoklamadaki sayaç, birincideki sayaçtan bağımsızdır, çünkü her yoklama yeni bir süreçtir. Raporlama, geçen ile kalanı tek yerde toplar.

Yalıtmanın bedeli vardır. Ayrı süreç, testin gördüğü ayarları da ayrı kılar. Bu derste her yoklama, ayar dosyası olmadan yalnız profilin yönergeleriyle koşar; çünkü bir testin hangi ayarla koştuğu, sonucunu belirler. Aynı test paketi geliştirme ayarıyla geçip üretim ayarıyla kalabilir ya da tersi olabilir. Birinci yoklama tam olarak bunu gösterir: iddiaya dayanan bir sınama yalnız `zend.assertions=1` olduğunda yanlışı bulur.

Pratikte iki sonuç çıkar. Testler, üretime çıkacak yapılandırmaya yakın bir profilde de koşturulmalıdır; yoksa üretimde değişen bir davranış, hiç sınanmadan canlıya geçer. Öte yandan iddiaların kendisi geliştirmede koşmaya devam etmelidir, çünkü onların işi kodun içinde varsayımı denetlemektir. İki işi birbirinden ayırmak, yani sınamayı profilden bağımsız bir karşılaştırma yardımcısına, varsayımı ise `assert()`'e bırakmak, bu yüzden tutarlı bir ayrımdır.

#### Birim ve Tümleşim Sınaması

Başa dönüp birim testi ile tümleşim testi arasındaki farkı küçük bir örnekle ölçelim. Saf bir fonksiyon, `kdvli()`, yalnız aldığı değere bağlıdır: aynı girdi her koşumda aynı çıktıyı verir. Dosyaya yazan bir fonksiyon, `notEkle()`, ise dışarıdaki bir duruma bağlıdır: önceki koşumdan kalan bir dosya, sonucu değiştirir.

Sürücü dört yoklama koşturur. Birincisi saf fonksiyonu sınar. İkincisi dosyaya yazan fonksiyonu, dosyayı temizlemeden sınar. Üçüncüsü aynı sınamayı, başta dosyayı silerek yazar. Dördüncüsü, başarısızlık iletisinin iki profilde aynı olup olmadığını sorar. Sürücü, her yoklamadan önce dosyayı siler; yoklamanın içindeki iki koşum (geliştirme, sonra üretim) ise aynı dosyayı paylaşır.

```php
<?php
// birim.php — saf fonksiyon ile dosyaya yazan fonksiyon
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'birim: saf fonksiyon' => <<<'PHP'
        <?php
        require 'sinama.php';
        function kdvli(int $tutar): int {
            return intdiv($tutar * 118, 100);
        }
        try {
            esitOlmali(118, kdvli(100));
            echo "evet\n";
        } catch (RuntimeException) {
            echo "hayır\n";
        }
        PHP,
    'tümleşim: dosyayı temizlemeyen sınama' => <<<'PHP'
        <?php
        function notEkle(string $dosya, string $satir): void {
            file_put_contents($dosya, $satir . "\n", FILE_APPEND);
        }
        notEkle('not.txt', 'ilk');
        echo count(file('not.txt')) === 1 ? "evet\n" : "hayır\n";
        PHP,
    'tümleşim: başta temizleyen sınama' => <<<'PHP'
        <?php
        function notEkle(string $dosya, string $satir): void {
            file_put_contents($dosya, $satir . "\n", FILE_APPEND);
        }
        @unlink('not.txt');
        notEkle('not.txt', 'ilk');
        echo count(file('not.txt')) === 1 ? "evet\n" : "hayır\n";
        PHP,
    'başarısızlık iletisi aynıdır' => <<<'PHP'
        <?php
        require 'sinama.php';
        try {
            esitOlmali(5, topla(2, 3));
            echo "hayır\n";
        } catch (RuntimeException $e) {
            echo $e->getMessage() === 'beklenen ile gelen ayrıştı' ? "evet\n" : "hayır\n";
        }
        PHP,
];

foreach ($yoklamalar as $ad => $kod) {
    @unlink('not.txt');
    file_put_contents('yoklama.php', $kod);
    yanRaporu($ad, 'yoklama.php');
}
```

```text
birim: saf fonksiyon → iki yanda da
    geliştirme evet · üretim evet
tümleşim: dosyayı temizlemeyen sınama → yalnız geliştirmede
    geliştirme evet · üretim hayır
tümleşim: başta temizleyen sınama → iki yanda da
    geliştirme evet · üretim evet
başarısızlık iletisi aynıdır → iki yanda da
    geliştirme evet · üretim evet
```

Saf fonksiyonun sınaması iki yanda da geçer: girdisi ve çıktısı dışında hiçbir şeye bağlı değildir. Temizlemeyen tümleşim sınaması ise **yalnız geliştirmede** geçer. Bu bir profil farkı değildir; sürücünün koşum sırasının izidir. Geliştirme koşumu dosyayı ilk kez oluşturur ve tek satır görür; üretim koşumu aynı dosyaya ikinci satırı ekler ve iki satır görür. Koşum sırası tersine çevrilse sonuç da tersine dönerdi. Başta temizleyen sınama bu yüzden iki yanda da geçer.

Bu, tümleşim testlerinin tanımını da verir: dışarıdaki durumu kullanan test, o durumu kendi başına kurmalı ve temizlemelidir. Yoksa test sonucu, kodun doğruluğunu değil, testin hangi sırayla koştuğunu ölçer. Sonuç, yalıtmanın neden bir test çerçevesinin temel işi olduğunu gösterir. Dördüncü yoklama ise bir başka ayrıma bakar: `esitOlmali()` başarısızlık iletisini ayardan bağımsız üretir, bu yüzden test raporu iki profilde aynı metni taşır ve bir kalanın sebebi profile bakılmadan okunabilir.

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

`zend.assertions` üç değer alır. `1` iddiayı derler ve koşturur; `-1` derlemez; `0` ise derler ama koşturmaz. Ders boyunca üretim profili `-1` kullandı. Sorulacak soru şudur: `0` değeri de iddiayı susturur mu, ve iki değer arasında davranış farkı var mı? Çekirdek profil değiştirilmez; sürücü `yan.php`'deki `calistir()` fonksiyonunu üretim profilinin kopyasıyla çağırır ve yalnız `zend.assertions` değerini değiştirir.

```php
<?php
// sinir.php — zend.assertions için 0 ile -1 farkı
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'iddia yakalanır' => <<<'PHP'
        <?php
        require 'sinama.php';
        echo iddiaYakalandi() ? "evet\n" : "hayır\n";
        PHP,
    'çalışırken açılınca iddia yakalanır' => <<<'PHP'
        <?php
        require 'sinama.php';
        ini_set('zend.assertions', '1');
        echo iddiaYakalandi() ? "evet\n" : "hayır\n";
        PHP,
];

foreach (['0', '-1'] as $deger) {
    $profil = ['zend.assertions' => $deger] + PROFILLER['üretim'];
    foreach ($yoklamalar as $ad => $kod) {
        file_put_contents('yoklama.php', $kod);
        echo "zend.assertions=$deger · $ad: ", calistir('yoklama.php', $profil) ? 'evet' : 'hayır', "\n";
    }
}
```

```text
zend.assertions=0 · iddia yakalanır: hayır
zend.assertions=0 · çalışırken açılınca iddia yakalanır: evet
zend.assertions=-1 · iddia yakalanır: hayır
zend.assertions=-1 · çalışırken açılınca iddia yakalanır: hayır
```

`0` değerinde de tutmayan iddia yakalanmaz: iddia derlenmiş olsa bile koşturulmaz. İki değer arasındaki fark, çalışırken ortaya çıkar. `0` değerinde ayar `ini_set()` ile `1`'e çevrilebilir ve iddia o andan sonra yakalar; `-1` değerinde iddia hiç derlenmediği için aynı çağrı bir şey değiştirmez. Bu, iddiayı açıp kapatan sınırın yalnız ayarın değerinde değil, dosyanın derlendiği anda geçerli ayarda da yattığını gösterir. Üretim profilinin `-1` seçmesi bu yüzden bir sertleştirme (hardening) kararıdır: iddiayı bir istek içinde yeniden açmanın yolu kapalıdır.

#### Özet

- Test çerçevesi, testleri bulan, koşturan ve raporlayan iskelettir; birim, tümleşim ve davranış testi sınanan şeyin büyüklüğüne göre ayrılır.
- `assert()` tabanlı sınama **yalnız geliştirmede** tutmayan iddiayı yakalar; üretim profilinde (`zend.assertions=-1`) iddia derlenmez ve yanlış kod sınamadan geçer.
- İddia içindeki durumu değiştiren çağrı üretimde koşmaz: sayaç geliştirmede 1, üretimde 0 kalır.
- `AssertionError`'a dayanmayan kendi karşılaştırma yardımcısı iki yanda da çalışır; test, bu yüzden `assert()`'e değil böyle bir yardımcıya dayanır.
- `zend.assertions=0` iddiayı derler ama koşturmaz; çalışırken `1`'e çevrilebilir. `-1`'de bu yol kapalıdır.

#### Sonraki Adım

Bu derste bir iddiayı geliştirmede görünür kılan ayar, üretimde onu sessizce kaldırdı. Aynı yön, hata ayıklarken gördüğümüz bilgi için de geçerlidir: izdeki argüman değerleri, ekrana basılan uyarı ve bir hata ayıklayıcının durduğu satırdaki değişkenler. Sonraki ders, geliştirmede görülen bu bilginin üretime çıkıp çıkmadığını sorar ve bir değerin istisna izinde göründüğü ya da gizlendiği yeri ölçer.

### Hata Ayıklayıcı

Source: https://academia.sh/tr/kurslar/php-uretim/hata-ayiklayici

Hata ayıklarken görülen bilginin, yani istisna izindeki argüman değerinin, uyarı metninin ve çağrı yığıtının hangi profilde göründüğünü ve üretime çıkıp çıkmadığını ölçün.

Önceki ders, aynı iddianın geliştirmede yakalanıp üretimde sessizce geçebildiğini gösterdi. Bir hata ayıklarken gördüğümüz bilgi de aynı yönde ayrışır mı? Bir hatanın izinde hangi değerin göründüğü, ekrana hangi uyarının basıldığı ve çağrı yığıtının ne kadarının açıldığı, yine ayar dosyasındaki yönergelere bağlıdır. Bu derste hata ayıklamanın gördüğü bilginin üretime çıkıp çıkmadığı ölçülür.

Adım adım hata ayıklayıcı (step debugger), koşan bir programı satır satır durdurup içini gösteren araç sınıfıdır. Programın durmasını istediğimiz satıra bir kesme noktası (breakpoint) koyarız; program oraya gelince durur ve o andaki değişkenler, çağrı yığıtı ve değerler okunabilir. Adımlama (stepping) ise durmuş programı bir satır, bir fonksiyonun içine ya da fonksiyondan dışarı ilerletmektir. Bu araçlar geliştiricinin makinesinde, koşuma dışarıdan bağlanarak çalışır. Bu ders hiçbir eklenti adı anmaz, hiçbir şey kurmaz ve hiçbir bağlantı noktası açmaz; hata ayıklayıcının varlığı yalnız kavram olarak anlatılır, ölçülen şey PHP'nin kendi araçlarının gösterdiğidir.

Dokuzuncu müfredatın beşinci kursunda (M09/K05) bir bilginin hangi araçtan okunduğu soruldu. Burada soru değişir: aracın gösterdiği bilgi, üretimde de görünüyor mu?

#### Hata Ayıklamanın Gördüğü Üç Bilgi

PHP'nin kendi araçlarından üçü, hata ayıklayıcının gösterdiğine benzer bilgi üretir. İstisnanın izi (`getTrace()`, `getTraceAsString()`), istisnanın fırlatıldığı yere kadar çağrılan fonksiyonları ve her çağrıya verilen argümanları listeler. `debug_backtrace()` aynı yığıtı istenen anda, istisna olmadan döndürür. Uyarı ve bildirimler ise `display_errors` açıksa yanıta doğrudan basılır.

Ölçüm çekirdeği önceki derslerle aynıdır. `yan.php` hiç değişmez.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Nötr bir örnek kullanılır: `ode()` fonksiyonu `kart-0042` değerini alır ve bir `RuntimeException` fırlatır. Değer bir kart numarasını andırır, ama gerçek bir numara değildir; amaç, izde görünen bir argümanın başka bir yere sızabilecek bir değer olduğunu göstermektir. Üç yoklama aynı sürücüde koşar.

```php
<?php
// hata.php — hata ayıklamanın gördüğü üç bilgi, iki profil
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'istisna izi argüman değerini taşır' => <<<'PHP'
        <?php
        function ode(string $kart): void {
            throw new RuntimeException('red');
        }
        try {
            ode('kart-0042');
        } catch (RuntimeException $e) {
            echo str_contains($e->getTraceAsString(), 'kart-0042') ? "evet\n" : "hayır\n";
        }
        PHP,
    'debug_backtrace argüman değerini taşır' => <<<'PHP'
        <?php
        function ode(string $kart): array {
            return debug_backtrace();
        }
        $iz = ode('kart-0042');
        echo in_array('kart-0042', $iz[0]['args'] ?? [], true) ? "evet\n" : "hayır\n";
        PHP,
    'uyarı yanıta basılır' => <<<'PHP'
        <?php
        ob_start();
        echo $tanimsiz;
        $yanit = ob_get_clean();
        echo str_contains($yanit, 'Warning') ? "evet\n" : "hayır\n";
        PHP,
];

foreach ($yoklamalar as $ad => $kod) {
    file_put_contents('yoklama.php', $kod);
    yanRaporu($ad, 'yoklama.php');
}
```

```text
istisna izi argüman değerini taşır → yalnız geliştirmede
    geliştirme evet · üretim hayır
debug_backtrace argüman değerini taşır → iki yanda da
    geliştirme evet · üretim evet
uyarı yanıta basılır → yalnız geliştirmede
    geliştirme evet · üretim hayır
```

Birinci yoklamada ayar farkı doğrudan görülür. Geliştirme profilinde `zend.exception_ignore_args=0` olduğu için istisna izi her çağrının argümanlarını taşır ve `kart-0042` izde okunur. Üretim profilinde `zend.exception_ignore_args=1` olduğu için iz yalnız fonksiyon adlarını ve satırları taşır; argüman listesi boştur. Bu bir korumadır: hata günlüğüne ya da bir hata sayfasına düşen iz, kullanıcının verdiği değeri taşımaz. Üretimde sıkılaştırılan bir yönerge olduğu için bu yanın **yalnız geliştirmede** olması bir kusur değil, istenen sonuçtur.

Üçüncü yoklama da aynı yandadır. Geliştirme profilinde `display_errors=1` ve `error_reporting=32767` olduğu için tanımsız değişken uyarısı çıktıya basılır. Üretim profilinde `display_errors=0` olduğu için uyarı yanıta girmez. Hata iletisinin yanıta basılması, dosya yolunu, satır numarasını ve çoğu zaman bir değeri ziyaretçiye gösterir. Buna bilgi sızıntısı (information disclosure) denir ve üretimde kapalı tutulmasının sebebi budur.

İkinci yoklama bu tablonun dışında kalır. `debug_backtrace()` çağrısı iki profilde de argümanı döndürür. Sebep, `zend.exception_ignore_args` yönergesinin yalnız istisna izini ilgilendirmesidir; kodun kendisinin istediği yığıt dökümünü etkilemez. Koruma bir yönergedir, bir engel değildir: argümanı bir istisna izinden saklar ama koda yazılmış bir `debug_backtrace()` çağrısından saklamaz. Bu çağrı üretime çıkan bir dosyada kalmışsa, değer üretimde de okunur.

#### İletiye Yazılan Değer

Koruma argüman listesini kapsıyorsa, değer başka bir yere yazıldığında ne olur? Bir geliştirici hatayı anlaşılır kılmak için değeri istisnanın iletisine yazabilir: `new RuntimeException("red: $kart")`. Bu, hata ayıklarken sık yapılan bir kolaylıktır, çünkü ileti hem günlükte hem de ekranda görünür. Soru şudur: üretim profili bu değeri de gizler mi?

#### Üretimde Neden Durdurulmaz

Hata ayıklayıcının kesme noktası, programı durdurur ve içini okunur kılar. Bunun iki anlamı vardır. Durmuş bir program istek yanıtlamaz: üretimde bir kesme noktası, o isteği bekleten bir kilit olur. Ayrıca durmuş programın gösterdiği her değişken, hata ayıklayıcıya bağlanan kişiye açıktır; bu kişinin yetkisi, istek sahibinin yetkisinden geniştir. İkisi de hata ayıklayıcıyı geliştirme profiline ait bir araç yapar.

Üretimde bunun yerini daha dar araçlar alır: günlük, istisna izi ve metrik. Bunlar programı durdurmaz, ama gösterdikleri bilgi ayara bağlıdır. Bu derste ölçülen şey tam olarak budur. Geliştirmede ayrıntılı bilgi istenir, çünkü okuyan kişi hatayı çözecek olandır. Üretimde aynı bilgi, günlüğe, hata sayfasına ya da bir izleme servisine gidebilir ve orada kimin okuyacağı bilinmez. `zend.exception_ignore_args` bu yüzden üretim profilinde `1` olur, `display_errors` bu yüzden `0` olur.

Burada bir ayrım yapmak gerekir. Bu yönergeler hatayı gizlemez; hatanın nerede gösterileceğini belirler. Üretimde hata yine oluşur, yine günlüğe yazılır, yine istisna olarak fırlatılır. Değişen, yanıta ve izde hangi alanın bulunduğudur. Bu yüzden "üretimde hata yok" diye bir sonuç çıkmaz; yalnız "üretimde hatanın ayrıntısı görünmez" sonucu çıkar.

#### İz Biçimi ve Bildirim Düzeyi

İzin kendisini de ölçelim. `getTraceAsString()`, her çağrıyı bir satırda yazar ve satırın başında `#0`, `#1` gibi bir sıra numarası bulunur. `debug_print_backtrace()` aynı bilgiyi doğrudan çıktıya basar. İkisi de aynı korumaya uyar mı, ölçelim. İlk yoklama iz satırının çağrılan fonksiyonun adını taşıdığını, ikincisi `debug_print_backtrace()` çıktısının argüman değerini taşıyıp taşımadığını sorar. Üçüncüsü başka bir bilgiye bakar: kullanımdan kaldırma bildirimi. `ini_set('display_errors', '1')` ile ekrana basma açılsa bile, `error_reporting` değeri o bildirimi dışarıda bırakıyorsa bildirim hiç üretilmez. Üretim profilinde `error_reporting=24575` olduğu için `E_DEPRECATED` bayrağı kapalıdır.

```php
<?php
// iz.php — iz biçimi ve bildirim düzeyi
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'iz satırı fonksiyon adını taşır' => <<<'PHP'
        <?php
        function ode(string $kart): void {
            throw new RuntimeException('red');
        }
        try {
            ode('kart-0042');
        } catch (RuntimeException $e) {
            echo str_contains($e->getTraceAsString(), '#0') && str_contains($e->getTraceAsString(), 'ode(') ? "evet\n" : "hayır\n";
        }
        PHP,
    'debug_print_backtrace argüman değerini taşır' => <<<'PHP'
        <?php
        function ode(string $kart): void {
            debug_print_backtrace();
        }
        ob_start();
        ode('kart-0042');
        echo str_contains(ob_get_clean(), 'kart-0042') ? "evet\n" : "hayır\n";
        PHP,
    'ekrana basma açılsa da kullanımdan kaldırma bildirimi gelir' => <<<'PHP'
        <?php
        ini_set('display_errors', '1');
        ob_start();
        strlen(null);
        echo str_contains(ob_get_clean(), 'Deprecated') ? "evet\n" : "hayır\n";
        PHP,
];

foreach ($yoklamalar as $ad => $kod) {
    file_put_contents('yoklama.php', $kod);
    yanRaporu($ad, 'yoklama.php');
}
```

```text
iz satırı fonksiyon adını taşır → iki yanda da
    geliştirme evet · üretim evet
debug_print_backtrace argüman değerini taşır → yalnız geliştirmede
    geliştirme evet · üretim hayır
ekrana basma açılsa da kullanımdan kaldırma bildirimi gelir → yalnız geliştirmede
    geliştirme evet · üretim hayır
```

Okunuş şöyledir. İz biçimi ayardan bağımsızdır: iz satırı iki profilde de sıra numarasını ve fonksiyon adını taşır. İkinci satır beklenmedik bir ayrım gösterir: `debug_print_backtrace()` argüman değerini **yalnız geliştirmede** basar, üretimde basmaz. Bu farkı `zend.exception_ignore_args` yaratmaz; o yönerge yalnız istisna izini ilgilendirir. Fark `zend.exception_string_param_max_len` yönergesinden gelir: `debug_print_backtrace()` yığıtı metne çevirirken dize argümanları bu uzunlukta keser. Değer geliştirmede 15, üretimde 0 olduğu için `kart-0042` geliştirmede tam basılır, üretimde basılmaz. Bu derste daha önce ölçülen `debug_backtrace()` ise yığıtı metne çevirmeden dizi olarak döndürür ve argümanı iki yanda da verir. Aynı yığıtı okuyan iki fonksiyon, koruma açısından farklı davranır. Hangisinin çağrıldığı bu yüzden önemlidir.

Üçüncü yoklama da **yalnız geliştirmede** gelir: kullanımdan kaldırma bildirimi, üretim profilinde `display_errors` açılsa bile üretilmez, çünkü `error_reporting` o düzeyi kapatır. Bu, bir uyarının görünmemesinin iki ayrı sebebi olabileceğini gösterir: bildirim üretilmiş ama ekrana basılmıyor olabilir ya da hiç üretilmiyor olabilir. Üretimde ikinci durum, bir kullanımdan kaldırmanın yanıtta da, bu ölçümde de iz bırakmaması demektir. Kullanımdan kaldırılan bir çağrı geliştirmede fark edilmezse, üretimde de fark edilmez.

#### Kesme Noktasının Taklidi

Kesme noktası bir satırda durup çağıranı, argümanları ve yerel değişkenleri gösterir. `debug_backtrace()` bunun ne kadarını taklit edebilir? Üç yoklama, durdurulan noktadan ne okunabildiğini sorar: çağıran fonksiyonun adı, çağrının argümanı ve çağıran fonksiyonun yerel değişkeni.

```php
<?php
// taklit.php — debug_backtrace kesme noktasının neyini taklit eder
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'çağıranın adı okunur' => <<<'PHP'
        <?php
        function dur(): array {
            return debug_backtrace();
        }
        function ode(string $kart): array {
            $kalan = 'bakiye-77';
            return dur();
        }
        $iz = ode('kart-0042');
        echo ($iz[1]['function'] ?? '') === 'ode' ? "evet\n" : "hayır\n";
        PHP,
    'çağıranın argümanı okunur' => <<<'PHP'
        <?php
        function dur(): array {
            return debug_backtrace();
        }
        function ode(string $kart): array {
            $kalan = 'bakiye-77';
            return dur();
        }
        $iz = ode('kart-0042');
        echo in_array('kart-0042', $iz[1]['args'] ?? [], true) ? "evet\n" : "hayır\n";
        PHP,
    'çağıranın yerel değişkeni okunur' => <<<'PHP'
        <?php
        function dur(): array {
            return debug_backtrace();
        }
        function ode(string $kart): array {
            $kalan = 'bakiye-77';
            return dur();
        }
        $iz = ode('kart-0042');
        echo str_contains(json_encode($iz), 'bakiye-77') ? "evet\n" : "hayır\n";
        PHP,
];

foreach ($yoklamalar as $ad => $kod) {
    file_put_contents('yoklama.php', $kod);
    yanRaporu($ad, 'yoklama.php');
}
```

```text
çağıranın adı okunur → iki yanda da
    geliştirme evet · üretim evet
çağıranın argümanı okunur → iki yanda da
    geliştirme evet · üretim evet
çağıranın yerel değişkeni okunur → koşuma girmiyor
    geliştirme hayır · üretim hayır
```

Sonuç taklidin sınırını çizer. Çağıranın adı ve argümanı okunur; yerel değişken okunmaz. Gerçek bir kesme noktası durdurduğu satırda bütün yerel değişkenleri gösterir, `debug_backtrace()` ise yalnız çağrı zincirini ve argümanları verir. İki araç aynı ailede değildir: biri programı durdurup içine bakar, öbürü programın kendi kendine bıraktığı bir kaydı okur. Bu fark, hata ayıklayıcıyı üretimde neden kullanmadığımızın, ama `debug_backtrace()` çağrısının neden unutulduğunda tehlikeli olduğunun gerekçesidir: ikincisi üretime çıkar ve argümanları iki profilde de gösterir.

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

Sürücü iki istisnayı yan yana koşturur: biri değeri argüman olarak alır, öbürü değeri iletiye yazar. Her ikisinde de `kart-0042` değerinin istisna içinde bir yerde bulunup bulunmadığı, ayrı ayrı sorulur. İz için `getTraceAsString()`, ileti için `getMessage()` okunur.

```php
<?php
// sinir.php — değer argümanda mı, iletide mi
declare(strict_types=1);
require 'yan.php';

$yoklamalar = [
    'değer argümandaysa iz taşır' => <<<'PHP'
        <?php
        function ode(string $kart): void {
            throw new RuntimeException('red');
        }
        try {
            ode('kart-0042');
        } catch (RuntimeException $e) {
            echo str_contains($e->getTraceAsString(), 'kart-0042') ? "evet\n" : "hayır\n";
        }
        PHP,
    'değer iletideyse ileti taşır' => <<<'PHP'
        <?php
        function ode(string $kart): void {
            throw new RuntimeException("red: $kart");
        }
        try {
            ode('kart-0042');
        } catch (RuntimeException $e) {
            echo str_contains($e->getMessage(), 'kart-0042') ? "evet\n" : "hayır\n";
        }
        PHP,
    'değer iletideyse iz de taşır' => <<<'PHP'
        <?php
        function ode(string $kart): void {
            throw new RuntimeException("red: $kart");
        }
        try {
            ode('kart-0042');
        } catch (RuntimeException $e) {
            echo str_contains($e->getTraceAsString(), 'kart-0042') ? "evet\n" : "hayır\n";
        }
        PHP,
];

foreach ($yoklamalar as $ad => $kod) {
    file_put_contents('yoklama.php', $kod);
    yanRaporu($ad, 'yoklama.php');
}
```

```text
değer argümandaysa iz taşır → yalnız geliştirmede
    geliştirme evet · üretim hayır
değer iletideyse ileti taşır → iki yanda da
    geliştirme evet · üretim evet
değer iletideyse iz de taşır → yalnız geliştirmede
    geliştirme evet · üretim hayır
```

Sonuç korumanın sınırını gösterir. Değer argümandaysa üretim profili onu izden siler. Değer iletiye yazılmışsa `zend.exception_ignore_args` ona dokunmaz: `getMessage()` iki profilde de `kart-0042` değerini taşır ve ileti hata günlüğüne ya da hata sayfasına olduğu gibi gider. Koruma yalnız argüman listesini kapsar; ileti bir dizgedir ve PHP onun içine karışmaz. Üçüncü satır ayrımı tamamlar: değer iletideyse bile iz, argüman listesi boş olduğu için üretimde değeri taşımaz. Değer, izden değil iletiden sızar. Hangi alanın okunduğu ölçüyü belirler.

Bunun iki sonucu vardır. Birincisi, üretimde izin değer taşımaması tek başına bir güvence değildir; değerin iletiye yazılmadığı ayrıca doğrulanmalıdır. İkincisi, hata ayıklarken işe yarayan iletiyi, yani değeri taşıyan iletiyi, üretime çıkmadan önce gözden geçirmek gerekir.

#### Özet

- Adım adım hata ayıklayıcı, kesme noktasında durdurup değişkenleri ve çağrı yığıtını gösteren araç sınıfıdır; PHP'nin kendi karşılığı istisna izi, `debug_backtrace()` ve `display_errors`'tur.
- İstisna izindeki argüman değeri **yalnız geliştirmede** görünür; üretimde `zend.exception_ignore_args=1` onu gizler ve bu bir korumadır.
- Uyarının yanıta basılması da **yalnız geliştirmede** görülür; üretimde `display_errors=0` olduğu için bilgi sızıntısı kapanır.
- `debug_backtrace()` iki profilde de argümanı döndürür: koruma istisna izine özgüdür, koda yazılmış çağrıyı kapsamaz.
- Değer istisna iletisine yazılırsa üretim profili onu gizlemez; koruma yalnız argüman listesini kapsar.

#### Sonraki Adım

Bu dersle birlikte kalite araçlarının hepsi, bir davranışın iki profilde ayrışıp ayrışmadığı sorusuyla tarandı: statik çözümleme ve stil denetimi koşumun dışında kaldı, iddia ve ayrıntılı hata bilgisi yalnız geliştirmede göründü. Sıradaki derslerde soru dosyanın kendisine döner: bir dosya değiştiğinde PHP hangi sürümü koşturur, diskteki mi, önbellekteki mi? Yanıt yine ayar dosyasında yazılıdır ve üretim profilinde başka bir değer alır.

## Başarım ve İşletim

Kod önbelleği, profil çıkarma, bellek ve bağlantı yönetimi, süreç yöneticisi ve web sunucusu, ini yönergeleri ve dağıtım akışı — her biri geliştirme ile üretim profili arasında bir davranışın aynı kalıp kalmadığını belirleyen bir düzenek olarak.

### Kod Önbellekleme

Source: https://academia.sh/tr/kurslar/php-uretim/kod-onbellekleme

PHP'nin bayt kodunu önbellekte tutması üretimde dosya değişikliğini nasıl gizler: aynı yoldaki yeni sürüm ne zaman gelir, geçersiz kılma neyi çözer.

PHP bir dosyayı çalıştırmadan önce kaynağı bir ara biçime, yani bayt koduna (bytecode) çevirir, sonra bu biçimi çalıştırır. Kod önbelleği (opcode cache) çevrilmiş bayt kodunu bellekte tutar ve sonraki istek çeviri adımını atlar. PHP'de bu işi OPcache eklentisi yapar. Önbelleğin bir bedeli var: bellekteki kopya ile diskteki dosya ayrışabilir. Kodu yayına aldığınızda hangisinin koştuğu, önbelleğin diske ne zaman baktığına bağlıdır.

Bu dersin ölçü ekseni şu soru: bu davranış üretime çıkıyor mu? Aşağıdaki `yan.php` kursun ortak çekirdeğidir. Bir betiği önce geliştirme, sonra üretim yönergeleriyle ayrı süreçlerde koşturur ve davranışın hangi yanda görüldüğünü söyler. Dosya bu derste yalnız diske yazılır, çıktısı yoktur.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Önbelleğin neden bu kadar sıkı çalıştığını anlamak için iki işi ayırın. Birincisi dosyayı bulup okumak ve bayt koduna çevirmektir; ikincisi bayt kodunu çalıştırmaktır. Önbellek yalnız birinciyi atlar, ikincisi her istekte yinelenir. Bir uygulama yüzlerce dosyayı yüklüyorsa atlanan iş büyür, bu yüzden üretimde önbellek açık tutulur. Geliştirmede ise tersine, dosyayı her kaydedişinizde sonucun değişmesini istersiniz; önbelleğin eski kopyayı tutması işinize engel olur.

`opcache.validate_timestamps` bu ikisi arasındaki anahtardır. Açıkken önbellek, dosyanın değişme zamanına bakıp kopyanın güncel olup olmadığına karar verir. Kapalıyken kopya, süreç yeniden başlatılana ya da dosya açıkça geçersiz kılınana (invalidation) kadar geçerlidir. Üretimde kapatmanın nedeni, çalışan koda dağıtım dışında kimsenin dokunmamasıdır: kod değişecekse bunun bir dağıtım adımı olması beklenir. Bunun doğal sonucu şudur: dağıtımı bilmeyen bir önbellek, dosya değişse bile eski kodu sunar.

Üretim profilinde `opcache.validate_timestamps` değeri `0`dır: önbellek, dosyanın değişme zamanına bir daha bakmaz. Geliştirme profilinde kod önbelleği komut satırında kapalıdır, yani her koşum dosyayı diskten okur. Farkın görünür sonucunu ölçelim. Aşağıdaki betik iki yoklama dosyası yazar ve ikisini de `yan.php` ile iki profilde koşturur. OPcache son iki saniyede değişmiş bir dosyayı önbelleğe almadığı için (`opcache.file_update_protection`), yoklamalar dosyaların zamanını `touch()` ile geçmişe alır.

```php
<?php
// onbellek.php — yeniden yazılan dosya eski sürümle mi koşar?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('eski.php', <<<'PHP'
<?php
// eski.php — aynı yolu iki sürümle yazar, iki kez yükler
$yol = __DIR__ . '/surum.php';
file_put_contents($yol, '<?php return 1;');
touch($yol, time() - 60);
$ilk = include $yol;
file_put_contents($yol, '<?php return 2;');
touch($yol, time() - 30);
$sonra = include $yol;
echo $ilk === $sonra ? "evet\n" : "hayır\n";
PHP);

file_put_contents('gecersiz.php', <<<'PHP'
<?php
// gecersiz.php — geçersiz kılma yeni sürümü getirir mi?
$yol = __DIR__ . '/surum.php';
file_put_contents($yol, '<?php return 1;');
touch($yol, time() - 60);
include $yol;
file_put_contents($yol, '<?php return 2;');
touch($yol, time() - 30);
$dondu = opcache_invalidate($yol, true);
$sonra = include $yol;
echo $dondu === true && $sonra === 2 ? "evet\n" : "hayır\n";
PHP);

yanRaporu('yeniden yazılan dosya eski sürümle koşar', 'eski.php');
yanRaporu('geçersiz kılma true döner ve yeni sürüm gelir', 'gecersiz.php');
```

```
yeniden yazılan dosya eski sürümle koşar → yalnız üretimde
    geliştirme hayır · üretim evet
geçersiz kılma true döner ve yeni sürüm gelir → yalnız üretimde
    geliştirme hayır · üretim evet
```

Çıktıyı okumadan önce betiğin ne yaptığına satır satır bakalım. `eski.php` aynı yola `surum.php` adlı küçük bir dosya yazar; dosya yalnız bir sayı döndürür, ilk sürümde `1`, ikincisinde `2`. Her yazıştan sonra `touch()` ile dosyanın değişme zamanı geçmişe alınır, ilkinde altmış, ikincisinde otuz saniye öncesine. İki zamanın farklı olması önemlidir: aynı zamanı verseydik, denetim açık bir önbellek bile değişikliği ayırt edemezdi. Dosya iki kez `include` edilir ve iki dönüş değeri karşılaştırılır. Aynıysa, yani ikinci yükleme yeni sürümü görmediyse, yoklama `evet` yazar.

`gecersiz.php` ise ikinci yazıştan sonra, ikinci `include` öncesinde `opcache_invalidate($yol, true)` çağırır. İkinci parametre `true`, çağrının önbellekteki kopyayı dosyanın zamanına bakmadan geçersiz saymasını ister. Yoklama iki koşulu birlikte arar: çağrının `true` dönmesi ve ikinci yüklemenin `2` vermesi. Çağrı önbellek kapalıyken `false` döner, çünkü geçersiz kılınacak bir kopya yoktur; bu `false` bir hata değil, "yapılacak iş yok" cevabıdır.

Üretim yanında ilk yoklama `evet` verir (**BA1.** Ölçüm komut satırı sürecinde, tek süreçle yapıldı; bir süreç yöneticisi altında birden çok işçi süreç aynı kod önbelleğini paylaşır, bu derste o düzen kurulmadı): dosya yeniden yazıldığı hâlde ikinci yükleme birinciyle aynı değeri döndürdü, çünkü önbellek yolu zaten tanıyordu ve zamana bakmadı. Geliştirme yanında yeni sürüm hemen göründü. İkinci yoklama çıkış yolunu gösterir: `opcache_invalidate($yol, true)` üretimde `true` döner ve aynı istekte yeni sürüm gelir. Geliştirmede kod önbelleği kapalı olduğundan çağrı `false` döner ve dosya zaten hep yenidir. Yani bir dağıtım betiğinin değişen dosyaları geçersiz kılması gerekir; geliştirmede bu hiç fark edilmez.

#### Dosya Önbellekte mi?

Şimdiye kadar önbelleğin varlığını dolaylı gördük: dosya değişti ama sonuç değişmedi. Doğrudan sormanın yolu da var. `opcache_is_script_cached($yol)` verilen dosyanın şu anda önbellekte olup olmadığını bir mantıksal değerle söyler, `opcache_get_status(false)` ise eklentinin durumunu bir dizi olarak döndürür. Dizide sayı taşıyan alanlar da vardır, ama yoklama yalnız açık/kapalı bilgisini okur ve `evet` ya da `hayır` basar. Üç soru sorulur: eklenti açık mı, yüklenen dosya önbellekte mi, geçersiz kılınan dosya hâlâ önbellekte mi? Üçü de `evet` yönünde yazıldı. Üçüncüsünde `evet`, üretimde geçersiz kılmanın kopyayı önbellekten çıkardığı anlamına gelir; geliştirmede ne anlama geldiği çıktıdan sonra okunur.

```php
<?php
// durum.php — dosya önbellekte mi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('acik.php', <<<'PHP'
<?php
// acik.php — kod önbelleği bu süreçte açık mı?
$durum = opcache_get_status(false);
echo is_array($durum) && $durum['opcache_enabled'] === true ? "evet\n" : "hayır\n";
PHP);

file_put_contents('yuklenen.php', <<<'PHP'
<?php
// yuklenen.php — include edilen dosya önbelleğe girer mi?
$yol = __DIR__ . '/hedef.php';
file_put_contents($yol, '<?php return 1;');
touch($yol, time() - 60);
include $yol;
echo opcache_is_script_cached($yol) ? "evet\n" : "hayır\n";
PHP);

file_put_contents('atilan.php', <<<'PHP'
<?php
// atilan.php — geçersiz kılınan dosya önbellekten çıkar mı?
$yol = __DIR__ . '/hedef2.php';
file_put_contents($yol, '<?php return 1;');
touch($yol, time() - 60);
include $yol;
opcache_invalidate($yol, true);
echo !opcache_is_script_cached($yol) ? "evet\n" : "hayır\n";
PHP);

yanRaporu('kod önbelleği açık', 'acik.php');
yanRaporu('include edilen dosya önbelleğe girer', 'yuklenen.php');
yanRaporu('geçersiz kılınan dosya önbellekten çıkar', 'atilan.php');
```

```
kod önbelleği açık → yalnız üretimde
    geliştirme hayır · üretim evet
include edilen dosya önbelleğe girer → yalnız üretimde
    geliştirme hayır · üretim evet
geçersiz kılınan dosya önbellekten çıkar → iki yanda da
    geliştirme evet · üretim evet
```

Çıktının ilk satırı, eklentinin bu süreçte açık olup olmadığını söyler. Geliştirme yanında `hayır` çıkması, önceki bölümde söylediğimizle uyumludur: komut satırı sürecinde kod önbelleği varsayılan olarak kapalıdır ve geliştirme profili ona dokunmaz. Üretim yanında `evet` çıkması, profildeki `opcache.enable_cli=1` yönergesinin işidir. Bu satır yoklamanın zemin kontrolüdür: sonraki iki satırı okurken eklentinin gerçekten açık olduğunu bilmeniz gerekir, yoksa `hayır` yanıtlarını davranış sanabilirsiniz.

İkinci satır asıl soruya cevap verir: `include` ettiğimiz dosya önbelleğe girdi mi? Geliştirmede `hayır`, çünkü önbellek yok; üretimde `evet`. Burada ölçülen şey, dosyanın bellekte bir kopyası olduğudur. Bu kopya, birinci bölümdeki eski sürüm davranışının dayanağıdır: dosya bir kez girdikten sonra `validate_timestamps=0` ile bir daha diske bakılmaz. İki ölçümü birlikte okuyunca zincir tamamlanır. Önce dosya önbelleğe girer, sonra disk değişir, sonra önbellek diske bakmadığı için eski sürüm koşar.

Üçüncü satır iki yanda da `evet` verir ve bu satırı dikkatle okumak gerekir. Üretimde `evet`, `opcache_invalidate($yol, true)` çağrısının kopyayı önbellekten çıkardığını gösterir. Geliştirmede ise `evet` çıkması hiçbir şeyin kanıtı değildir: önbellek kapalı olduğundan dosya zaten hiç girmemişti, dolayısıyla "çıktı mı" sorusunun cevabı baştan olumludur. Bu yüzden satırın ölçü sınıfı `iki yanda da`dır ve bu sınıf burada "iki yanda aynı davranış" demek değil, "ölçüm yalnız üretimde bir şey söyler" demektir. Sınıfı doğru adlandırmak için sınıfın neyi ölçtüğünü bilmek gerekir; yoklamanın iki yanda aynı sonucu vermesi, davranışın iki yanda aynı olduğunu her zaman göstermez.

Gerçek bir üretim kurulumunda bu üç soru, dağıtım sonrası doğrulamanın temelidir. Eklenti açık mı sorusu, yönergenin sürece ulaştığını doğrular; yapılandırma dosyası doğru yerde olsa bile eklenti yüklenmemişse yönergeler etkisiz kalır. Dosya önbellekte mi sorusu ısınmanın işe yarayıp yaramadığını söyler: ilk istekten önce kritik dosyalar önbelleğe alınmışsa ilk kullanıcı çeviri bedelini ödemez. Geçersiz kılınan dosya çıktı mı sorusu ise dağıtım betiğinizin eski sürümü gerçekten bıraktığını teyit eder. Bu ders komut satırında, tek süreçte ölçtü; bir süreç yöneticisi altında her işçi sürecin bu üç cevabı aynı kod önbelleğinden alacağı için sonuçlar paylaşılır.


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

Üretim profiline `opcache.validate_timestamps=1` ve `opcache.revalidate_freq=0` eklersek önbellek her istekte dosyaya bakar. Eski sürüm sorunu kalkar mı? Aşağıdaki betik `eski.php` yoklamasını bu iki yönergeyle koşturur. `eski.php` önceki bloktan diskte duruyor.

```php
<?php
// sinir.php — üretime dosya denetimi eklenirse eski sürüm kalır mı?
declare(strict_types=1);
require __DIR__ . '/yan.php';

$denetimli = array_replace(PROFILLER['üretim'], [
    'opcache.validate_timestamps' => '1',
    'opcache.revalidate_freq' => '0',
]);

$eskiKaldi = calistir('eski.php', $denetimli);
echo 'eski sürüm kaldı mı: ', $eskiKaldi ? 'evet' : 'hayır', "\n";
```

```
eski sürüm kaldı mı: hayır
```

Yoklama `hayır` verdi: dosya denetimi açıkken önbellek her istekte değişme zamanına bakar, yeniden yazılan dosyanın zamanı değiştiği için yeni sürümü derler ve eski sürüm kalmaz. Eski sürüm sorunu bu ayarla kalkar. Kalkmasının bedeli başka yere taşınır: önbellek her istekte dosyanın değişme zamanına bakmak için diske gider. Bu bedel burada ölçülmedi, ölçülen yalnız davranıştır. Üretimde dosya denetimini kapalı tutup dağıtımda `opcache_invalidate()` çağırmak ya da süreci yeniden başlatmak, bedeli her isteğe değil dağıtım anına yükler. Hangisinin seçileceği, dağıtım düzeninizin neye izin verdiğine bağlıdır.


Bu ölçümün sınırını açıkça söylemek gerekir. Yoklama, dosya denetimi açıkken önbelleğin yeni sürümü gördüğünü gösterdi; ama yenileme sıklığı sıfır olduğu için her isteğin diske gitmesini istemiş olduk. Yenileme sıklığı sıfırdan büyük bir saniye değerine çekilirse, bir dağıtım sonrasında o süre boyunca eski sürümün koşması yeniden mümkün olur. Dersin sınıfı bu yüzden değişmez: eski sürüm davranışı yalnız üretim yanında, denetim kapalıyken ya da yenileme penceresi içindeyken görünür, geliştirme yanında hiç görünmez. Geliştirme profilinde sorunu hiç görmemiş bir ekip, üretime çıktığı gün ilk kez karşılaşır. Ölçümün öğrettiği alışkanlık şudur: yalnız üretimde görünen bir davranışı geliştirmede aramayın, dağıtım adımınıza o davranışı kapatan açık bir geçersiz kılma ya da yeniden başlatma koyun ve bu adımın çalıştığını yukarıdaki üç soruyla doğrulayın.

Bu dersin yoklamaları kasıtlı olarak küçük tutuldu. Dosyalar tek satırlık sayı döndürür, çünkü ölçülmek istenen şey dosyanın içeriği değil, hangi sürümün koştuğudur. Cevap `evet` ya da `hayır`dır. Bayt sayısı, bellek miktarı ya da zaman damgası makineden makineye değişir; hangi sürümün koştuğu ise değişmez, tekrar edilebilir olan da odur.

Son olarak sınırlayıcı ölçümün yanını gerekçelendirelim. Dosya denetimi eklenmiş üretim profili yine üretim yanındadır, ama ölçtüğü soru farklıdır: "eski sürüm kaldı mı" sorusuna `hayır` demesi, geliştirme profilinin davranışına yaklaştığı anlamına gelir. Bu yaklaşma bedelsiz değildir; ders boyunca söylediğimiz gibi bedel disk denetimine taşınır. Sınırlayıcı ölçümün işi, dersin ana iddiasının koşulunu göstermektir: eski sürüm sorunu, denetimin kapalı olmasına bağlıdır ve o koşul kalkınca sorun da kalkar.

#### Özet

- Bayt kodu, kaynağın çalıştırılmadan önce çevrildiği ara biçimdir; kod önbelleği bu biçimi bellekte tutar ve sonraki isteklerde çeviriyi atlar.
- Bu dersin ölçü sınıfı **yalnız üretimde**dir: `opcache.validate_timestamps=0` iken yeniden yazılan dosya aynı yolda eski sürümle koşar, geliştirme profilinde bu hiç görülmez.
- `opcache_invalidate($yol, true)` üretimde `true` döner ve aynı istekte yeni sürümü getirir; geliştirmede önbellek kapalı olduğundan `false` döner.
- Üretime `opcache.validate_timestamps=1` ve `opcache.revalidate_freq=0` eklemek eski sürüm sorununu giderir, ama dosya denetimini her isteğe geri taşır.
- Ölçümde dosyalar `touch()` ile geçmişe alınır, çünkü OPcache son iki saniyede değişen dosyayı önbelleğe almaz.
- `opcache_is_script_cached()` yoklaması dosyanın önbelleğe girdiğini üretimde gösterir ve geliştirmede `hayır` verir; geçersiz kılınan dosyanın çıktığını gösteren satır ise geliştirmede anlamsız olduğundan yalnız üretim yanında bir şey söyler (ölçü sınıfı: iki yanda da, sınırla).

#### Sonraki Adım

Kod önbelleği, hangi sürümün koştuğunu belirler; ne kadar iş yaptığını söylemez. Bir isteğin zamanı ve belleği nereye gittiğini görmek için profil çıkarmak gerekir. Bir sonraki ders, aynı işin iki profilde nasıl sayıldığını ve sayma işinin kendisinin hangi profilde sonucu değiştirebileceğini ölçer.

### Profil Çıkarma

Source: https://academia.sh/tr/kurslar/php-uretim/profil-cikarma

Bir işin hangi fonksiyonu kaç kez çağırdığını ve ne kadar bellek tuttuğunu iki profilde ölçmek: sayım aynı kalır, ama sayma kodu profile göre değişebilir.

Bir uygulama yavaş ya da şişkin göründüğünde ilk tepki tahmin etmektir. Profil çıkarma (profiling) tahmini ölçümle değiştirir: işin hangi fonksiyonu kaç kez çağırdığını ve çalışırken ne kadar bellek tuttuğunu kaydeder. En çok çağrılan ya da en çok bellek tutan yol, sıcak yol (hot path) diye anılır ve iyileştirmeye oradan başlanır. Bu derste süre ölçmüyoruz. Sayılar çağrı sayısı ve bellek farkıdır, çünkü bunlar makineden makineye değişmez; ölçülen şey, işin davranışıdır.

Ölçü ekseni bu kursun ortak sorusudur: bu davranış üretime çıkıyor mu? `yan.php` birebir aşağıdadır, bu derste de yalnız diske yazılır.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Ölçüme başlamadan önce neyi ölçeceğimizi sınırlayalım. Bu derste iki büyüklük var: bir fonksiyonun çağrı sayısı ve bir işin çalışırken bellekte bıraktığı fark. İkisi de sayıdır ve ikisi de aynı kodu aynı girdiyle koşturduğunuzda aynı çıkar. Süre bu özelliğe sahip değildir: aynı iş bir makinede başka, bir başkasında başka sürer, hatta aynı makinede iki koşum arasında bile değişir. Bu yüzden karşılaştırmayı sayılar üzerine kurmak, ölçümü tekrar edilebilir kılar. Aşağıdaki iki blok bu mantıkla yazıldı; ilki ölçülen işi ve sayacı tanımlar, ikincisi o işi iki profilde koşturup sonuçları karşılaştırır.

Önce ölçeceğimiz işi ve sayaç sınıfını yazalım. `ikiKat()` fonksiyonu 500 kez çağrılır ve her çağrıda kendini sayar; ardından 10000 elemanlı bir dizi kurulur ve bellek farkı alınır. Dosyanın çıktısı yoktur, yoklamalar onu yükler.

```php
<?php
// sayac.php — çağrı sayısı ve bellek farkı ölçen küçük profil çıkarıcı
declare(strict_types=1);

final class Sayac {
    private static array $sayilar = [];

    public static function say(string $ad): void {
        self::$sayilar[$ad] = (self::$sayilar[$ad] ?? 0) + 1;
    }

    public static function oku(string $ad): int {
        return self::$sayilar[$ad] ?? 0;
    }
}

function ikiKat(int $n): int {
    Sayac::say('ikiKat');
    return $n * 2;
}

/** @return array{cagri: int, toplam: int, bellekFarki: int} */
function isiYap(): array {
    $once = memory_get_usage();
    $toplam = 0;
    for ($i = 0; $i < 500; $i++) {
        $toplam += ikiKat($i);
    }
    $liste = range(1, 10000);
    $fark = memory_get_usage() - $once;
    return ['cagri' => Sayac::oku('ikiKat'), 'toplam' => $toplam, 'bellekFarki' => $fark];
}
```

Şimdi iki profilde aynı işi koşturan yoklamayı yazalım. Yoklama profili `zend.assertions` değerinden tanır (geliştirmede `1`, üretimde `-1`) ve sonucu profile özgü bir dosyaya yazar. Sürücü iki dosyayı okuyup karşılaştırır. Bellek için sorulan, farkın iki profilde eşit olup olmadığıdır.

```php
<?php
// profil.php — aynı iş iki profilde aynı sayıyı verir mi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('olcum.php', <<<'PHP'
<?php
// olcum.php — işi koşturur, sonucu profile özgü dosyaya yazar
require __DIR__ . '/sayac.php';
$profil = ini_get('zend.assertions') === '1' ? 'gelistirme' : 'uretim';
$sonuc = isiYap();
file_put_contents("olcum-$profil.json", json_encode($sonuc));
echo $sonuc['cagri'] === 500 && $sonuc['toplam'] === 249500 ? "evet\n" : "hayır\n";
PHP);

$yan = yanRaporu('500 çağrılık iş aynı sayıyı verir', 'olcum.php');

$gelistirme = json_decode(file_get_contents('olcum-gelistirme.json'), true);
$uretim = json_decode(file_get_contents('olcum-uretim.json'), true);
echo 'çağrı sayısı: ', $gelistirme['cagri'], ' / ', $uretim['cagri'], "\n";
echo 'toplam: ', $gelistirme['toplam'], ' / ', $uretim['toplam'], "\n";
echo 'bellek farkları eşit: ', $gelistirme['bellekFarki'] === $uretim['bellekFarki'] ? 'evet' : 'hayır', "\n";
```

```
500 çağrılık iş aynı sayıyı verir → iki yanda da
    geliştirme evet · üretim evet
çağrı sayısı: 500 / 500
toplam: 249500 / 249500
bellek farkları eşit: evet
```

İki profilde de çağrı sayısı 500 ve toplam 249500 çıktı, bellek farkları eşit kaldı. Profilin cevap verdiği soru "kaç kez ve ne kadar bellek" sorusudur ve bu soru iki yanda aynı cevabı alır. Dolayısıyla geliştirmede bulduğunuz sıcak yol, üretimde de sıcak yoldur. (**BA2.** Karşılaştırma tek süreçte ve tek bir koşumda yapıldı; bellek farkı, işin yaşadığı sürecin ayarlarına değil işin kendisine bağlıdır, bu derste ayrıca değiştirilmedi.)

#### Sıcak Yolu Bulmak

Şimdiye kadar tek bir fonksiyonu saydık. Gerçek bir iş ise fonksiyonların birbirini çağırdığı bir zincirdir ve sıcak yol, zincirin en çok çağrılan halkasıdır. Aşağıdaki yoklama üç fonksiyonlu bir zincir kurar: `ozetle()` bir döngüde `donustur()` çağırır, `donustur()` ise her çağrıda `oku()` fonksiyonunu iki kez çağırır. `ozetle(40)` beş kez koşar. Çağrı sayıları baştan hesaplanabilir: `ozetle()` 5, `donustur()` 40 çarpı 5 yani 200, `oku()` ise 200 çağrının her birinde ikişer kez olduğundan 400 olmalıdır. Betik her fonksiyonun girişinde bir sayaç artırır, sayıları büyükten küçüğe sıralar ve ilk sıradaki adın `oku` olup olmadığını söyler. Dağılım her profilde ayrı bir dosyaya yazılır; sürücü iki dosyayı karşılaştırır ve sıralı dağılımı basar. Sayıların elle hesaplanabilir olması önemlidir: ölçüm beklentimizle örtüşüyorsa sayacın doğru çalıştığını da anlamış oluruz.

```php
<?php
// sicakyol.php — çağrı dağılımı iki profilde aynı mı, sıcak yol hangisi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('sicak.php', <<<'PHP'
<?php
// sicak.php — çağrı dağılımını çıkarır, en çok çağrılan fonksiyonu bulur
$sayilar = [];
function say(string $ad): void {
    global $sayilar;
    $sayilar[$ad] = ($sayilar[$ad] ?? 0) + 1;
}
function oku(int $i): int {
    say('oku');
    return $i % 7;
}
function donustur(int $i): int {
    say('donustur');
    return oku($i) * 3 + oku($i + 1);
}
function ozetle(int $n): int {
    say('ozetle');
    $toplam = 0;
    for ($i = 0; $i < $n; $i++) {
        $toplam += donustur($i);
    }
    return $toplam;
}
for ($k = 0; $k < 5; $k++) {
    ozetle(40);
}
arsort($sayilar);
$profil = ini_get('zend.assertions') === '1' ? 'gelistirme' : 'uretim';
file_put_contents("sicak-$profil.json", json_encode($sayilar));
echo array_key_first($sayilar) === 'oku' ? "evet\n" : "hayır\n";
PHP);

yanRaporu('sıcak yol oku fonksiyonudur', 'sicak.php');

$gelistirme = file_get_contents('sicak-gelistirme.json');
$uretim = file_get_contents('sicak-uretim.json');
foreach (json_decode($uretim, true) as $ad => $sayi) {
    echo $ad, ': ', $sayi, "\n";
}
echo 'dağılımlar eşit: ', $gelistirme === $uretim ? 'evet' : 'hayır', "\n";
```

```
sıcak yol oku fonksiyonudur → iki yanda da
    geliştirme evet · üretim evet
oku: 400
donustur: 200
ozetle: 5
dağılımlar eşit: evet
```

Çıktıyı satır satır okuyalım. İlk iki satır yoklamanın standart sonucudur: `sıcak yol oku fonksiyonudur` iddiası iki profilde de `evet` verdi, yani sıralamanın başında beklediğimiz fonksiyon var; sınıf `iki yanda da`dır. Sonraki üç satır sıralı dağılımdır: `oku` 400, `donustur` 200, `ozetle` 5. Bu sayılar elle yaptığımız hesapla birebir örtüşür. Örtüşme, ölçümün güvenilir olduğunu söyleyen asıl işarettir: sayaç beklenenden farklı bir şey verseydi önce sayacı, sonra kodu sorgulardık. Son satır, iki profilin dağılım dosyalarının baytı baytına aynı olduğunu söyler. Dosyaları sayı sayı değil bütün olarak karşılaştırmak, hem sayıların hem de sıralamanın aynı olduğunu tek adımda doğrular.

Bu dağılımın nasıl yorumlanacağı profil çıkarmanın asıl işidir. `ozetle()` en dıştaki fonksiyondur ve yalnız beş kez çağrılır; kodu okuyan biri onu önemli sanıp optimize etmeye yönelebilir. Dağılım ise işin yükünün alttaki `oku()` fonksiyonunda toplandığını gösterir: dört yüz çağrı, dıştaki fonksiyonun seksen katı. Optimizasyon bu fonksiyonun içinde ya da çağrı biçiminde aranır, örneğin sonucu önbelleğe almak, çağrıyı döngüden çıkarmak ya da iki çağrıyı tek çağrıya indirmek gibi. Bu derste süre ölçmediğimiz için "dört yüz çağrının ne kadar sürdüğünü" söylemiyoruz; çağrı sayısı incelemeye hangi fonksiyondan başlanacağını, yani sırayı belirler; iyileştirme kararını ise süre ile birlikte okunduğunda verir.

Yanın neden `iki yanda da` olduğunu da söyleyelim. Sayaç sıradan bir fonksiyon çağrısıdır; `zend.assertions` ya da `error_reporting` gibi profil farklarına bağlı bir yol kullanmaz. Aynı zincir iki profilde aynı sırayla koşar ve aynı sayıları üretir. Gerçek üretimde bu şu işe yarar: sıcak yolu geliştirme makinenizde bulup üretimde aynı yolun sıcak olduğuna güvenebilirsiniz, ama yalnızca sayma kodunuz profil farkına bağlı olmadığı sürece. Bir sonraki bölüm bu şartın neden önemli olduğunu gösteriyor.


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

Profil çıkaran kodun kendisi de koddur ve iki profilde aynı davranmayabilir. `zend.assertions` değeri `-1` iken `assert()` çağrısının içindeki ifade hiç değerlendirilmez. Sayacı bir `assert()` içinde artırırsak iki profil aynı çağrı sayısını verir mi?

```php
<?php
// sinir.php — assert() içindeki sayaç iki profilde aynı sayıyı verir mi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('assertli.php', <<<'PHP'
<?php
// assertli.php — sayacı assert() içinde artırır
$sayac = 0;
function say(): bool {
    global $sayac;
    $sayac++;
    return true;
}
for ($i = 0; $i < 500; $i++) {
    assert(say());
}
$profil = ini_get('zend.assertions') === '1' ? 'gelistirme' : 'uretim';
file_put_contents("assertli-$profil.txt", (string) $sayac);
echo $sayac === 500 ? "evet\n" : "hayır\n";
PHP);

yanRaporu('assert() içindeki sayaç artar', 'assertli.php');
echo 'geliştirme: ', file_get_contents('assertli-gelistirme.txt'), "\n";
echo 'üretim: ', file_get_contents('assertli-uretim.txt'), "\n";
```

```
assert() içindeki sayaç artar → yalnız geliştirmede
    geliştirme evet · üretim hayır
geliştirme: 500
üretim: 0
```

Geliştirmede sayaç 500, üretimde 0 çıktı; yan **yalnız geliştirmede** olarak okundu. Üretim profilinde `assert()` çağrısı derlenmediği için içindeki `say()` hiç koşmadı. Bunun anlamı açıktır: ölçümü tutan kod, durumu değiştiren bir çağrıysa onu `assert()` içine koymayın. Aynı işi iki profilde ölçerken sayaç ya her iki profilde koşan bir ifadede ya da `assert()` dışında tutulmalıdır. Profil çıkarırken hangi profilde çıkardığınız önemlidir; üretim profilinde ölçüp geliştirme varsayımlarıyla yorumlamak bu yüzden yanlış sayıya götürebilir.

İki ölçümü yan yana okuyunca iş bölümü görülür. Sayaç sınıfı sıradan bir fonksiyon çağrısıyla sayar ve iki profilde de aynı sonucu verir; sayma `assert()` içine itilince davranış profile bağlanır. Profil çıkarıcı araçlar da aynı kuralla çalışır: bir kaydı tutan kod, ölçtüğü kodun bulunduğu ayarla koşar. Sıcak yolu bulduktan sonra iyileştirmeyi yine aynı ölçümle doğrulamak, yani işi değiştirmeden önce ve sonra aynı sayıyı karşılaştırmak, sağlam yöntemdir.

Bu ölçüm bir uyarıyı da taşır. Sıcak yolu bulan kişi bulduğu yolun iyileştirilmesi gerektiğini düşünür; oysa çağrı sayısı yüksek olan fonksiyon ucuz da olabilir. Dört yüz çağrılık bir fonksiyon her çağrıda tek bir işlem yapıyorsa, beş çağrılık bir fonksiyondan çok daha az sorun yaratabilir. Profil çıkarmanın sağlam kullanımı, çağrı sayısını bellek farkıyla ve (bu dersin dışında kalan) süre ölçümüyle birlikte okumaktır. Bu dersin iki ölçümü, yani çağrı dağılımı ile bellek farkı, tek başına bir karar vermek için yeterli değil, iyileştirme sırasını belirlemek için yeterlidir.

Gerçek bir projede bu sayma işini elle yapmazsınız; bir profil çıkarıcı eklenti çağrıları kaydeder. Ancak bu eklentilerin kendisi de sürece yüklenir ve bazıları üretim profilinde kapalıdır ya da yüklenmeden koşar. Elle sayma bu yüzden bir öğretim aracıdır: bir sayma kodunun iki profilde aynı sonucu verdiğinin kanıtı, ölçümü yapan aracın aynı sonucu verdiğinin kanıtı değildir. Aracınızı üretim yanında çalıştırmadan önce, bu dersteki gibi küçük bir iş üzerinde iki yanda da aynı sayıları verdiğini doğrulayın.

Sınırlayıcı ölçümün ardında bir ayrım var: sayma kodunun kendisini ölçülen işin parçası saymak. `assert()` bu ayrımın bozulduğu yerdir, çünkü asıl amacı geliştirme sırasında varsayımları denetlemektir ve üretimde yok sayılır. Sayacı oraya koyan kişi geliştirmede beklenen sayıyı görür, üretimde sıfır görür ve ikisini aynı kod sanır. Çözüm basittir: sayma, `assert()` dışında bir ifadede yapılır; `assert()` yalnız doğruluk denetimi için kullanılır. Bunu kodda görmek zordur, çünkü iki profilde de hata çıkmaz; yalnız sayı sessizce farklı olur. Bu tür sessiz farkları yakalamanın tek yolu, ölçümü iki yanda koşturup sonuçları karşılaştırmaktır.

#### Özet

- Profil çıkarma, "hangi fonksiyon kaç kez çağrıldı ve ne kadar bellek tutuldu" sorusunu ölçümle yanıtlar; en çok çağrılan yol sıcak yoldur.
- Bu dersin ölçü sınıfı **iki yanda da**dır: 500 çağrılık iş geliştirme ve üretim profillerinde aynı çağrı sayısını (500), aynı toplamı (249500) ve aynı bellek farkını verdi.
- Süre ve bayt sayısı yerine çağrı sayısı ile eşitlik karşılaştırması kullanıldı; bunlar makineden bağımsızdır.
- Sayaç `assert()` içinde artırılırsa sonuç profile bağlanır: geliştirmede 500, üretimde 0 çıkar, çünkü üretimde `assert()` içindeki ifade değerlendirilmez.
- Ölçümü tutan kod durumu değiştiren bir çağrıysa `assert()` içine konmaz.
- Üç fonksiyonlu zincirde çağrı dağılımı `oku` 400, `donustur` 200, `ozetle` 5 çıktı ve iki profilde baytı baytına aynı kaldı; sıcak yol en dıştaki değil en alttaki fonksiyondu (ölçü sınıfı: iki yanda da).

#### Sonraki Adım

Çağrı sayısı ve bellek farkı, işin ne yaptığını gösterir; ama bellek sınırı aşıldığında ne olduğunu göstermez. Sonraki ders `memory_limit` aşılınca isteğin nasıl durduğunu, bağlantı nesnelerinin istek sonunda nasıl kapandığını ve döngüsel bir referansın yıkıcıyı ne zaman geciktirdiğini ölçer.

### Bellek ve Bağlantı Yönetimi

Source: https://academia.sh/tr/kurslar/php-uretim/bellek-ve-baglanti-yonetimi

Bellek sınırı aşılınca isteğin nasıl durduğunu, bağlantı nesnesinin yıkıcısının ne zaman koştuğunu ve döngüsel referansın temizliği nasıl geciktirdiğini ölçmek.

Bir PHP isteği ayrılmış bir bellek payıyla çalışır. Pay `memory_limit` yönergesiyle belirlenir; aşılırsa istek kurtarılamaz bir hatayla durur. Bellek kadar önemli bir kaynak da bağlantıdır: veritabanı ya da önbellek sunucusuna açılan her bağlantı, bir yerde kapatılana kadar karşı tarafta yer tutar. Bu ders, ikisinin de istek ömrüne nasıl bağlandığını ölçer. İstek ömrü kavramı önceki kursta kuruldu; burada yalnız ölçümün konusudur.

Ölçü ekseni yine ortak soru: bu davranış üretime çıkıyor mu? `yan.php` birebir aşağıdadır ve bu derste de yalnız diske yazılır.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Bellek sınırının davranışını anlamak için sınırın ne işe yaradığını hatırlayalım. PHP tek bir isteği kendi payı içinde tutar; pay, bir istekteki hatalı bir döngünün ya da beklenmedik büyük bir girdinin makinenin tamamını tüketmesini önleyen bir emniyettir. Sınır aşıldığında PHP işi yarım bırakır, çünkü devam etmenin güvenli bir yolu yoktur: ayrılacak bellek kalmamıştır. İstek durur, ama durmadan önce PHP kapanış fonksiyonlarını çağırır. Bu davranışı gözlemek için yoklama betiği kasıtlı olarak sonsuz bir döngüde bir megabaytlık dizeler biriktirir. Dizeler bir dizide tutulduğu için hiçbiri serbest kalmaz ve sınır kaçınılmaz olarak aşılır.

İlk ölçüm iki yoklama içerir. Birincisi belleği `ini_set('memory_limit', '8M')` ile sekiz megabayta çeker ve sınırı aşana kadar bellek ayırır. İstek durduğunda bir kapanış fonksiyonu (shutdown function) devreye girer ve `error_get_last()` ile son hatanın türüne bakar. İkincisi bir bağlantı nesnesi kurar ve yıkıcısının (destructor) betik bittikten sonra koşup koşmadığına bakar. Çıktıda hata iletisi metni ya da bayt sayısı yoktur; yoklamalar yalnız `evet` ya da `hayır` basar.

```php
<?php
// bellek.php — sınır aşılınca istek durur, yıkıcı istek sonunda koşar
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('sinir-asimi.php', <<<'PHP'
<?php
// sinir-asimi.php — memory_limit aşılınca kapanış fonksiyonu hatayı görür
ini_set('memory_limit', '8M');
register_shutdown_function(function (): void {
    $son = error_get_last();
    echo $son !== null && $son['type'] === E_ERROR ? "evet\n" : "hayır\n";
});
$parcalar = [];
while (true) {
    $parcalar[] = str_repeat('a', 1024 * 1024);
}
PHP);

file_put_contents('yikici.php', <<<'PHP'
<?php
// yikici.php — bağlantı nesnesinin yıkıcısı betikten sonra koşar
final class Baglanti {
    public function __destruct() {
        echo "evet\n";
    }
}
$baglanti = new Baglanti();
echo "betik bitti\n";
PHP);

yanRaporu('bellek sınırı aşılınca istek durur', 'sinir-asimi.php');
yanRaporu('yıkıcı betik bittikten sonra koşar', 'yikici.php');
```

```
bellek sınırı aşılınca istek durur → iki yanda da
    geliştirme evet · üretim evet
yıkıcı betik bittikten sonra koşar → iki yanda da
    geliştirme evet · üretim evet
```

Sınır aşıldığında istek durdu ve kapanış fonksiyonu son hatanın `E_ERROR` olduğunu gördü; yıkıcı da betik bittikten sonra koştu. İkisi de iki profilde aynı: yan **iki yanda da**. Geliştirmede ekrana düşen hata iletisi üretimde gizlenir, ama isteğin durması ve kapanış fonksiyonunun çağrılması ayarlara bağlı değildir. Bu yüzden bir isteğin ölümünü yakalayıp günlüğe yazmak istiyorsanız kapanış fonksiyonu iki profilde de çalışan yoldur.

Bağlantılar bu ömre iki biçimde bağlanır. Sıradan bağlantı istekle doğar ve yıkıcısı koşunca, yani istek bitince kapanır. Kalıcı bağlantı (persistent connection) ise istek bittiğinde kapanmaz; süreç onu bir sonraki istek için saklar. Bağlantı havuzu (connection pool) bir adım öteye gider: bağlantıları süreçlerden bağımsız bir katmanda tutar ve istekler arasında paylaştırır. Bu derste ikisi de kurulmadı, yalnız kavram olarak anılıyor; ölçülen şey sıradan bağlantının yıkıcısıdır.

#### Kapanış Fonksiyonu ve Yıkıcı Sırası

Önceki bölümde iki ayrı mekanizma gördük: sınır aşımında devreye giren kapanış fonksiyonu ve nesne ömrünün sonunda koşan yıkıcı. Gerçek bir uygulamada ikisi birlikte bulunur ve hangisinin önce koştuğu önemlidir. Kapanış fonksiyonunda veritabanına son bir kayıt yazıyorsanız, bağlantı nesnesinin o ana kadar yıkılmamış olması gerekir. Bu bölüm sırayı ölçer. Bağlantı nesnesinin yıkıcısı, kapanış fonksiyonunun koşup koşmadığına bakar ve ona göre `evet` ya da `hayır` basar. İlk yoklamada nesne betik sonuna kadar yaşar; ikincisinde betik, kapanış fonksiyonunu kaydettikten sonra nesneyi `unset()` ile kendisi bırakır.

```php
<?php
// sira.php — kapanış fonksiyonu ile yıkıcı hangi sırayla koşar?
declare(strict_types=1);
require __DIR__ . '/yan.php';

$sinif = <<<'PHP'
<?php
final class Baglanti {
    public static bool $kapanisKostu = false;
    public bool $kapanisOnceMi = true;
    public function __destruct() {
        echo self::$kapanisKostu === $this->kapanisOnceMi ? "evet\n" : "hayır\n";
    }
}
register_shutdown_function(function (): void {
    Baglanti::$kapanisKostu = true;
});
PHP;

file_put_contents('sonda.php', $sinif . <<<'PHP'

$baglanti = new Baglanti();
PHP);

file_put_contents('erken.php', $sinif . <<<'PHP'

$baglanti = new Baglanti();
$baglanti->kapanisOnceMi = false;
unset($baglanti);
PHP);

yanRaporu('betik sonunda kapanış fonksiyonu yıkıcıdan önce koşar', 'sonda.php');
yanRaporu('unset() ile bırakılan nesnenin yıkıcısı kapanıştan önce koşar', 'erken.php');
```

```
betik sonunda kapanış fonksiyonu yıkıcıdan önce koşar → iki yanda da
    geliştirme evet · üretim evet
unset() ile bırakılan nesnenin yıkıcısı kapanıştan önce koşar → iki yanda da
    geliştirme evet · üretim evet
```

Önce iki yoklamanın tasarımını açalım. Her ikisinde de kapanış fonksiyonu yalnız bir işi yapar: `Baglanti::$kapanisKostu` bayrağını `true` yapar. Yıkıcı ise bayrağın o anki değerine bakar ve nesnenin "kapanış önce mi koşmalı" beklentisiyle karşılaştırır; uyuşuyorsa `evet`, uyuşmuyorsa `hayır` basar. Böylece sıra bilgisi, yıkıcının çıktısına dönüşür ve ayrı bir günlük dosyası gerekmez. İlk yoklamada nesne betik sonuna kadar yaşar ve beklenti "kapanış önce"dir. İkincisinde betik nesneyi `unset()` ile bırakır, nesneyi tutan başka bir referans kalmadığı için yıkıcı hemen koşar; beklenti "kapanış sonra"dır ve bu beklenti `kapanisOnceMi` alanı `false` yapılarak iletilir.

İki satır da iki profilde `evet` verdi. Birinci satırın anlamı şudur: bir nesneyi betik sonuna kadar yaşatırsanız, önce kapanış fonksiyonları koşar, nesneler sonra yıkılır. Bu sıra, kapanış fonksiyonunda bağlantı nesnesini hâlâ kullanabileceğiniz anlamına gelir: günlüğe son kaydı yazmak, açık bir işlemi bitirmek, bir kuyruğa iş bırakmak gibi işleri güvenle yaparsınız. İkinci satırın anlamı da tersidir: nesneyi sizin elinizle bıraktıysanız yıkıcı kapanıştan önce koşar ve kapanış fonksiyonu o bağlantıyı artık bulamaz. İki davranış da dilin kuralıdır ve profil ayarına bağlı olmadığı için `iki yanda da` sınıfındadır.

Bunun üretimdeki karşılığı basit bir kuraldır. Kapanış fonksiyonunda kullanacağınız bir kaynağı, kaydettiğiniz anonim fonksiyona `use` ile bağlayarak ya da bir nesne özelliği olarak taşıyın; global bir değişkeni `unset()` ile bırakmayın. Bellek sınırı aşıldığında kapanış fonksiyonu yine koşar, ama bu noktada bellek tükenmiş olduğundan fonksiyonun yapabileceği iş küçüktür: yalnız hatayı günlüğe yazmak gibi. Büyük bir temizlik ya da yeni bir bağlantı açmak bu anda çoğu zaman başarısız olur. Sınır aşımı ölçümümüzdeki kapanış fonksiyonunun yalnız `error_get_last()` okuması bu yüzden bilinçli bir seçimdi.


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

Yıkıcının "istek sonunda koşar" olması, nesneyi bıraktığınız anda koşar anlamına gelmez. Bir nesne kendisine dönen bir referans taşıyorsa, yani iki nesne birbirini tutuyorsa, `unset()` ikisini de hemen yok etmez. Bu örnekte iki nesne birbirini tutuyor. Yoklamalar evet yönünde yazıldı: `unset()` sonrası yıkıcı hemen koşmaz mı, `gc_collect_cycles()` sonrası koşar mı?

```php
<?php
// dongu.php — döngüsel referansta yıkıcı ne zaman koşar?
declare(strict_types=1);
require __DIR__ . '/yan.php';

$govde = <<<'PHP'
<?php
class Dugum {
    public static int $yikilan = 0;
    public ?Dugum $komsu = null;
    public function __destruct() {
        self::$yikilan++;
    }
}
$a = new Dugum();
$b = new Dugum();
$a->komsu = $b;
$b->komsu = $a;
unset($a, $b);
$unsetSonrasi = Dugum::$yikilan;
gc_collect_cycles();
$toplayiciSonrasi = Dugum::$yikilan;
PHP;

file_put_contents('hemen.php', $govde . "\necho \$unsetSonrasi === 0 ? \"evet\\n\" : \"hayır\\n\";\n");
file_put_contents('toplayici.php', $govde . "\necho \$toplayiciSonrasi === 2 ? \"evet\\n\" : \"hayır\\n\";\n");

yanRaporu('unset() sonrası yıkıcı hemen koşmaz', 'hemen.php');
yanRaporu('gc_collect_cycles() sonrası yıkıcı koşar', 'toplayici.php');
```

```
unset() sonrası yıkıcı hemen koşmaz → iki yanda da
    geliştirme evet · üretim evet
gc_collect_cycles() sonrası yıkıcı koşar → iki yanda da
    geliştirme evet · üretim evet
```

İki yoklama da iki profilde `evet` verdi. `unset()` sonrasında yıkıcı koşmadı, çünkü iki nesne birbirini tuttuğu için referans sayısı sıfıra inmedi. `gc_collect_cycles()` çağrısı döngüyü bulup ikisini de yıktı ve yıkıcı iki kez koştu. Bunun pratik sonucu şudur: yıkıcıda bağlantıyı kapatan bir nesne döngüsel bir yapının parçasıysa, bağlantı `unset()` ile değil, ya açıkça yazdığınız bir `kapat()` çağrısıyla ya da çöp toplayıcı çalıştığında kapanır. Bağlantıyı yıkıcıya emanet etmek, nesne grafiğinizde döngü olmadığını varsaymaktır. (**BA3.** Çöp toplayıcıya ilişkin hiçbir yönerge değiştirilmedi; yoklamalar varsayılan davranışı ölçer.)

Bellek tarafında da aynı ders çıkar. Sınırı aşan istek, kapanış fonksiyonunu çağırarak durur; sınır her isteğin kendi payıdır ve istek bitince o pay serbest kalır. Uzun ömürlü bir süreçte ise, örneğin bir kuyruk işçi sürecinde, süreç biten isteklerle sıfırlanmadığı için döngüsel yapılar birikebilir ve `memory_limit` bu birikimi yakalayan son sigortadır.

Sınırlayıcı ölçümü bu kursun ortak yöntemiyle okuyalım: bir iddia kuruyoruz, iddiayı çürütebilecek bir ölçüm yapıyoruz. İddia "yıkıcı, nesneyi bıraktığınız anda koşar" idi. Çürütücü ölçüm, iki nesneyi birbirine bağlayıp `unset()` çağırmaktır. Yıkıcı koşmadı; yani iddia, bu koşulda doğru değildir. Düzeltilmiş iddia daha dardır: yıkıcı, nesneye giden son referans düştüğünde koşar. Döngüsel yapıda son referans düşmez, çünkü nesneler birbirini ayakta tutar. Bu yüzden bağlantıyı kapatma sorumluluğunu yıkıcıya yüklemek yerine, bağlantıyı açan kodun açıkça kapatması daha sağlam bir düzendir; yıkıcı yedek güvenlik ağı olarak kalır.

Ölçümün sınırı da açıktır: yoklamalar nesne sayar, bağlantı kurmaz. Gerçek bir veritabanı sunucusunda açık bağlantıların karşı taraftaki sayısını görmek ayrı bir araç ve ayrı bir ortam ister; bu dersin ölçümü ise yıkıcının koşup koşmadığını kanıtlar, koşmanın karşı tarafta ne yaptığını değil. İkisi birbirine bağlıdır ama aynı ölçüm değildir.

Bu bölümde `gc_collect_cycles()` çağrısını elle yaptık, çünkü ölçümün tekrar edilebilir olması gerekir. Gerçek bir uygulamada bu çağrıyı genellikle yapmazsınız; çöp toplayıcı kendi eşiğine ulaşınca çalışır. Uzun ömürlü işçi süreçlerde, büyük bir işin sonunda elle çağırmak bilinçli bir seçim olabilir, ama bunu bir kural olarak değil, ölçümle doğrulanmış bir istisna olarak uygulayın.

Bu iki kavramı birleştirelim. Bir kuyruk işçi süreci düşünün: uzun ömürlü bir süreç, her işte bir bağlantı nesnesi kuruyor ve işi bitince onu bırakıyor. Bağlantı nesnesi başka bir nesneyle birbirini tutuyorsa, örneğin bir "oturum" nesnesi bağlantıyı, bağlantı da oturumu tutuyorsa, `unset()` hiçbir bağlantıyı kapatmaz. Çöp toplayıcı çalışana kadar bağlantılar karşı tarafta açık kalır. Toplayıcı belirli bir birikim eşiğinde kendiliğinden çalışır, ama ne zaman çalışacağını kodunuz bilemez. Karşı tarafın bağlantı sınırı küçükse, bu belirsizlik bir sabah bağlantı hatası olarak görünür. Bu derste bağlantı sunucusu kurmadık; ama döngüsel referans yoklaması, aynı davranışı bir nesne sayacıyla ölçüyor.

Dersin üç ölçümünün sınadığı dört davranışı yan yana koyunca ortak bir yapı görülür. Sınır aşımı, yıkıcı zamanı, kapanış sırası ve döngüsel referans, hepsi dilin çalışma zamanı kurallarıdır ve `php.ini` ayarlarının profil farkına bağlı değildir. Bu, güven verici bir bulgudur, çünkü bellek ve bağlantı davranışını geliştirmede öğrenip üretimde aynen beklemenizi sağlar. Dikkat edilecek tek şey ayarın kendisidir: `memory_limit` değerini ayar dosyası belirler ve sınır aşımının hangi noktada gerçekleştiği o değere bağlıdır. İki hazır profilde değer 128M ile aynıdır, ama bir dağıtımın kendi ayar dosyası onu değiştirebilir. Bu derste sınırı betik içinde sekiz megabayta çektik ve iki profilin aynı noktaya gelmesini sağladık. Gerçek bir dağıtımda komut satırı süreci ile web süreci ayrı ayar dosyaları okuyabilir; o durumda kuyruk işçi sürecini web ayarıyla sınamak, işçi süreç için yanlış bir güven verir. Sınırı bilerek, ölçtüğünüz sürecin kendi değeriyle ölçün ve değeri dağıtım belgenize yazın; sınır aşımı bir gün geldiğinde ilk bakılacak yer orasıdır.

#### Özet

- `memory_limit` aşılınca istek durur; kapanış fonksiyonu `error_get_last()` ile son hatanın `E_ERROR` olduğunu görür.
- Bu dersin ölçü sınıfı **iki yanda da**dır: sınır aşımı, yıkıcı davranışı ve döngüsel referans ölçümlerinin hepsi geliştirme ve üretim profillerinde aynı sonucu verdi.
- Bağlantı nesnesinin yıkıcısı betik bittikten sonra koşar; kalıcı bağlantı ve bağlantı havuzu bu ömrü isteklerin ötesine uzatan kavramlardır.
- Döngüsel referansta `unset()` yıkıcıyı hemen koşturmaz; `gc_collect_cycles()` koşturur.
- Bağlantıyı kapatma işini yıkıcıya bırakmak, nesne grafiğinde döngü olmadığına güvenmektir.
- Betik sonuna kadar yaşayan nesnenin yıkıcısı kapanış fonksiyonundan sonra, `unset()` ile bırakılanın yıkıcısı ise kapanıştan önce koşar; iki sıra da iki profilde aynıdır (ölçü sınıfı: iki yanda da).

#### Sonraki Adım

Bellek ve bağlantı bir isteğin içinde yönetildi. Bu derste ölçülen her davranış çalışma zamanının kuralıydı; sıradaki derste ölçülen davranış ise ayarlara ve sürecin nasıl başlatıldığına bağlıdır. İsteklerin kendisini kimin karşıladığı ayrı bir sorudur: web sunucusu ve süreç yöneticisi, istekleri işçi süreçlere dağıtır. Sonraki ders yerleşik web sunucusunu iki işçiyle başlatır ve istekler arasında neyin taşınıp neyin taşınmadığını ölçer.

### Süreç Yöneticisi ve Web Sunucusu

Source: https://academia.sh/tr/kurslar/php-uretim/surec-yoneticisi-ve-web-sunucusu

İstekleri kimin karşıladığını ve işçi süreçlerin istekler arasında neyi paylaşmadığını yerleşik web sunucusuyla ölçmek: durumu süreç dışında tutmak neden gerekir.

Bir tarayıcı PHP uygulamasına istek gönderdiğinde araya iki katman girer. Web sunucusu (web server) bağlantıyı kabul eder ve isteği okur. Süreç yöneticisi (process manager) PHP kodunu çalıştıran işçi süreçleri (worker process) başlatır, izler ve istekleri aralarında dağıtır. Yönetici işçi sayısını artırıp azaltabilir, çöken bir işçinin yerine yenisini koyabilir. Ayrıntıların hepsi üretim kurulumuna bağlıdır; bu derste kurulum yerine bu işin küçük bir modeli olan yerleşik web sunucusu (built-in web server) ölçülür.

Yerleşik sunucu yalnız `php -S` ile koşar ve üretim için tasarlanmamıştır; birden çok işçi süreç desteği PHP 7.4.0 ile gelen deneysel bir özelliktir ve Windows'ta desteklenmez. Üretimde web sunucusu ve süreç yöneticisi ayrı bir yapılandırmayla kurulur. Yerleşik sunucu bu yapıyı ölçülebilir bir kapsama indirir: `PHP_CLI_SERVER_WORKERS` ortam değişkeni işçi süreç sayısını belirler ve her işçi ayrı bir süreçtir. Ölçü ekseni bu kursun ortak sorusudur: bu davranış üretime çıkıyor mu? `yan.php` birebir aşağıdadır, yalnız diske yazılır.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Önce sunucuyu iki profilde başlatan betiği yazalım. Sunucu her zaman `-d display_errors=0` ile ve yalnız `127.0.0.1` adresinde açılır. Profil yönergeleri `yan.php` içindeki `PROFILLER` tablosundan alınmıştır; tek bilinçli ayrım `display_errors` yönergesidir: bu derste yanıt gövdesi değil günlük ölçüldüğü için iki profilde de `0` verilir, tablodaki geliştirme değeri `1` burada kullanılmaz. İki profil de aynı işçi sayısıyla başlar. Betik iki sayfa yazar: `sayac.php` her istekte bellekteki bir sayacı artırıp yazdırır, `uyari.php` tanımsız bir değişken okuyarak uyarı üretir. Sunucu hazır olana kadar `curl` ile beklenir, sonunda `kill` ile durdurulur. Betik sonuçları `sunucu-sonuc.txt` dosyasına da yazar.

```bash
# sunucu.sh — yerleşik sunucuyu iki işçiyle iki profilde başlatır
cat > sayac.php <<'EOF'
<?php
$GLOBALS['sayac'] = ($GLOBALS['sayac'] ?? 0) + 1;
echo $GLOBALS['sayac'];
EOF
cat > uyari.php <<'EOF'
<?php
echo $tanimsiz;
echo 'tamam';
EOF

GELISTIRME="-d display_startup_errors=1 -d error_reporting=32767 -d mysqlnd.collect_memory_statistics=1 -d zend.assertions=1 -d zend.exception_ignore_args=0 -d zend.exception_string_param_max_len=15"
URETIM="-d display_startup_errors=0 -d error_reporting=24575 -d mysqlnd.collect_memory_statistics=0 -d zend.assertions=-1 -d zend.exception_ignore_args=1 -d zend.exception_string_param_max_len=0 -d opcache.enable_cli=1 -d opcache.validate_timestamps=0"
: > sunucu-sonuc.txt

for profil in gelistirme uretim; do
  if [ "$profil" = gelistirme ]; then yonergeler=$GELISTIRME; else yonergeler=$URETIM; fi
  rm -f gunluk-$profil.txt
  PHP_CLI_SERVER_WORKERS=2 php -n -d display_errors=0 -d log_errors=1 -d error_log=gunluk-$profil.txt \
    $yonergeler -S 127.0.0.1:8471 >/dev/null 2>&1 &
  pid=$!
  for i in $(seq 1 50); do curl -s -o /dev/null http://127.0.0.1:8471/sayac.php && break; sleep 0.1; done
  ilk=$(curl -s http://127.0.0.1:8471/sayac.php)
  ikinci=$(curl -s http://127.0.0.1:8471/sayac.php)
  ucuncu=$(curl -s http://127.0.0.1:8471/sayac.php)
  durum=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8471/uyari.php)
  kill $pid; wait $pid 2>/dev/null
  uyarilar=$(grep -c Warning gunluk-$profil.txt)
  echo "$profil $ilk $ikinci $ucuncu $durum $uyarilar" >> sunucu-sonuc.txt
  echo "$profil: sayaç $ilk $ikinci $ucuncu · uyarılı sayfa $durum · günlükte uyarı $uyarilar"
done
```

```
gelistirme: sayaç 1 1 1 · uyarılı sayfa 200 · günlükte uyarı 1
uretim: sayaç 1 1 1 · uyarılı sayfa 200 · günlükte uyarı 1
```

Şimdi iki profilin sonucunu okuyup yanını çıkaralım. Üç ölçüm sorusu var: sayaç her istekte `1` mi, uyarılı sayfa `200` mü, uyarı günlüğe düştü mü? Betik her birinin iki profilde aynı olup olmadığına bakar ve `yan.php` içindeki `yan()` fonksiyonuyla adlandırır.

```php
<?php
// sunucu-yan.php — sunucu ölçümlerinin yanını çıkarır
declare(strict_types=1);
require __DIR__ . '/yan.php';

$satirlar = array_map(
    fn(string $satir): array => explode(' ', trim($satir)),
    file('sunucu-sonuc.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES),
);
[$gelistirme, $uretim] = $satirlar;

$olcumler = [
    'sayaç her istekte 1' => fn(array $s): bool => array_slice($s, 1, 3) === ['1', '1', '1'],
    'uyarılı sayfa 200 verir' => fn(array $s): bool => $s[4] === '200',
    'uyarı günlüğe düşer' => fn(array $s): bool => (int) $s[5] === 1,
];

foreach ($olcumler as $ad => $olcum) {
    echo $ad, ' → ', yan($olcum($gelistirme), $olcum($uretim)), "\n";
}
```

```
sayaç her istekte 1 → iki yanda da
uyarılı sayfa 200 verir → iki yanda da
uyarı günlüğe düşer → iki yanda da
```

Sayaç iki profilde de her istekte `1` çıktı; iki işçi süreç ayrı bellek taşıdığı için bellekteki sayaç istekler arasında birikmedi. Uyarılı sayfa iki profilde `200` döndü ve uyarı ekrana değil günlüğe yazıldı. Bu yüzden üretimde bir sayfa uyarı verdiği hâlde yanıt sağlıklı görünür: durum kodu uyarıyı haber vermez, onu görmenin yolu günlüktür. (**BA4.** Her profil sunucuyu kendi ayarlarıyla baştan başlattı; çalışan bir sunucunun ayarını değiştirmek ölçülmedi.)

#### İstek Başına Durum

Bir önceki ölçüm bellekteki sayacın istekler arasında birikmediğini gösterdi. Bu bölüm aynı soruyu dört farklı durum türü için sorar. PHP'de durumun yaşayabileceği yerler bellidir: bir sınıfın statik özelliği, global değişken, fonksiyon içindeki `static` değişken ve bir dosyanın `include_once` ile yüklenmesinin yan sonucu. Dört yerin her biri bir sayaç tutar ve sayfa dördünü birden yazdırır. Beklentimiz hepsinin `1` çıkmasıdır, çünkü PHP her istekte yürütme ortamını baştan kurar. Üç istek gönderilir ve yalnız üçüncünün yanıtı yazdırılır: birikme olsaydı sayılar orada `3` görünürdü.

```bash
# durum.sh — istek başına hangi durum taşınır?
cat > yuklu.php <<'EOF'
<?php
$GLOBALS['yuklu'] = ($GLOBALS['yuklu'] ?? 0) + 1;
EOF
cat > durum.php <<'EOF'
<?php
final class Tutucu {
    public static int $n = 0;
}
function say(): int {
    static $c = 0;
    return ++$c;
}
Tutucu::$n++;
$GLOBALS['g'] = ($GLOBALS['g'] ?? 0) + 1;
include_once __DIR__ . '/yuklu.php';
echo 'statik özellik ', Tutucu::$n, ' · global ', $GLOBALS['g'],
    ' · fonksiyon içi static ', say(), ' · include_once ', $GLOBALS['yuklu'];
EOF

GELISTIRME="-d display_startup_errors=1 -d error_reporting=32767 -d mysqlnd.collect_memory_statistics=1 -d zend.assertions=1 -d zend.exception_ignore_args=0 -d zend.exception_string_param_max_len=15"
URETIM="-d display_startup_errors=0 -d error_reporting=24575 -d mysqlnd.collect_memory_statistics=0 -d zend.assertions=-1 -d zend.exception_ignore_args=1 -d zend.exception_string_param_max_len=0 -d opcache.enable_cli=1 -d opcache.validate_timestamps=0"

for profil in gelistirme uretim; do
  if [ "$profil" = gelistirme ]; then yonergeler=$GELISTIRME; else yonergeler=$URETIM; fi
  PHP_CLI_SERVER_WORKERS=2 php -n -d display_errors=0 $yonergeler -S 127.0.0.1:8471 >/dev/null 2>&1 &
  pid=$!
  for i in $(seq 1 50); do curl -s -o /dev/null http://127.0.0.1:8471/durum.php && break; sleep 0.1; done
  curl -s -o /dev/null http://127.0.0.1:8471/durum.php
  curl -s -o /dev/null http://127.0.0.1:8471/durum.php
  son=$(curl -s http://127.0.0.1:8471/durum.php)
  kill $pid; wait $pid 2>/dev/null
  echo "$profil: $son"
done
```

```
gelistirme: statik özellik 1 · global 1 · fonksiyon içi static 1 · include_once 1
uretim: statik özellik 1 · global 1 · fonksiyon içi static 1 · include_once 1
```

Betiğin kurduğu düzeni okuyalım. `durum.php` dört ayrı yerde bir sayaç artırır. Birincisi `Tutucu` sınıfının statik özelliğidir; statik özellik sınıfa aittir, nesneye değil, ve bu yüzden "süreç boyunca yaşar" diye düşünülebilir. İkincisi global dizideki `g` anahtarıdır. Üçüncüsü `say()` fonksiyonunun içindeki `static $c` değişkenidir; bu değişken fonksiyon çağrıları arasında değerini korur. Dördüncüsü `yuklu.php` dosyasıdır: `include_once` dosyayı bir kez yükler ve dosyanın içindeki kod bir global sayaç artırır. `include_once` adındaki "bir kez", bir istek içinde bir kez demektir; sorumuz yeni bir istekte dosyanın yine yüklenip yüklenmediğidir.

Çıktının iki satırı da dört sayının hepsini `1` gösterir. Bu, PHP'nin istek modelinin ölçülmüş hâlidir: her istek, işçi süreç aynı kalsa bile, kullanıcı koduna göre sıfırdan kurulur. Statik özellik sıfırlanır, çünkü sınıf tanımı da değerleriyle birlikte her istekte yeniden oluşturulur. Global dizi istek bitince boşalır. Fonksiyon içi `static` değişken, fonksiyon her istekte yeniden tanımlandığı için sıfırdan başlar. `include_once` dosyayı her istekte yeniden yükler ve içindeki sayacı sıfırdan artırır. Dördü de aynı kuralın sonucudur ve iki profilde aynı olması, kuralın ayarlara bağlı olmadığını gösterir; bu yüzden bölümün sınıfı `iki yanda da`dır.

Bu sonucun gerçek üretimde iki yönü vardır. Olumlu yönü, bir isteğin bıraktığı kirin sonrakini bozmamasıdır: sızıntı yapan bir istek, bir sonraki isteğin değişkenlerini etkilemez. Bu özelliği ucuza veren model, bedelini başka yerden alır: her istek uygulamayı baştan kurar. Web uygulama çerçevesinin yapılandırmayı okuması, sınıfların yüklenmesi, bağlantıların açılması her istekte yinelenir. Kod önbelleği bu bedelin yalnız bir parçasını, dosyaları çevirme işini kaldırır. Kalan parça, yani nesnelerin kurulması, istek başına durumun doğal sonucudur. Uzun ömürlü çalışma modelleri bu bedeli azaltmak için durumu istekler arasında tutar ve bunun karşılığında sızıntı riskini kendileri yönetmek zorunda kalır. Bu derste o modeller kurulmadı; ölçülen yalnız işçi süreç başına bir isteğin klasik modelidir.


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

Bellekteki durum istekler arasında taşınmıyorsa durumu dosyaya yazmak çözüm olur mu? Aşağıdaki betik sayacı bir dosyada tutan sayfayı iki profilde başlatır ve üç isteğin döndürdüğü değerleri yazdırır.

```bash
# sinir.sh — durum dosyaya yazılırsa istekler arasında taşınır mı?
cat > dosyali.php <<'EOF'
<?php
$yol = __DIR__ . '/sayac.txt';
$n = (int) @file_get_contents($yol) + 1;
file_put_contents($yol, (string) $n);
echo $n;
EOF

GELISTIRME="-d display_startup_errors=1 -d error_reporting=32767 -d mysqlnd.collect_memory_statistics=1 -d zend.assertions=1 -d zend.exception_ignore_args=0 -d zend.exception_string_param_max_len=15"
URETIM="-d display_startup_errors=0 -d error_reporting=24575 -d mysqlnd.collect_memory_statistics=0 -d zend.assertions=-1 -d zend.exception_ignore_args=1 -d zend.exception_string_param_max_len=0 -d opcache.enable_cli=1 -d opcache.validate_timestamps=0"

for profil in gelistirme uretim; do
  if [ "$profil" = gelistirme ]; then yonergeler=$GELISTIRME; else yonergeler=$URETIM; fi
  rm -f sayac.txt
  PHP_CLI_SERVER_WORKERS=2 php -n -d display_errors=0 $yonergeler -S 127.0.0.1:8472 >/dev/null 2>&1 &
  pid=$!
  for i in $(seq 1 50); do curl -s -o /dev/null http://127.0.0.1:8472/dosyali.php && break; sleep 0.1; done
  rm -f sayac.txt
  ilk=$(curl -s http://127.0.0.1:8472/dosyali.php)
  ikinci=$(curl -s http://127.0.0.1:8472/dosyali.php)
  ucuncu=$(curl -s http://127.0.0.1:8472/dosyali.php)
  kill $pid; wait $pid 2>/dev/null
  echo "$profil: dosyadaki sayaç $ilk $ikinci $ucuncu"
done
```

```
gelistirme: dosyadaki sayaç 1 2 3
uretim: dosyadaki sayaç 1 2 3
```

Dosyadaki sayaç iki profilde de `1 2 3` çıktı: iki işçi aynı dosyayı gördü ve istekler arasında durum taşındı. Bellekteki sayaç ise `1 1 1` kalmıştı. Fark işçinin belleğinde değil, durumun nerede tutulduğundadır. İşçi süreçlere bağlı değişkenler, statik özellikler ve önbelleğe alınmış nesneler istekler arasında güvenilir biçimde paylaşılmaz; paylaşılması gereken durum süreç dışında, bir dosyada, veritabanında ya da bir önbellek sunucusunda tutulur. Burada dosyayı kullandık, çünkü ölçüm için en küçük dış depodur; sıralı üç istek çakışmaz. Aynı anda yazan iki işçi için dosya kilidi ya da gerçek bir depo gerekir, bu da ölçülmedi.

Bu ölçümler birlikte işçi sayısı kararına ışık tutar. Her işçi süreç, kendi istek belleğiyle çalışır; iki işçi, aynı anda iki isteği karşılayabilir ama birbirlerinin belleğini görmez. Bu yüzden işçi süreç sayısını artırmak, aynı anda karşılanabilecek istek sayısını artırır ve toplam bellek kullanımını da artırır. Bir işçi sürecin tipik bellek tutarını bilmeden sayıyı artırmak, makinenin belleğini aşıp sistemi yavaşlatmaya götürebilir. Eşzamanlılık sayıyla gelir, ama sayının üst sınırını bellek koyar. Dağıtım belgenize "iki işçi" yerine "işçi başına şu kadar bellek, toplamda şu kadar işçi" yazmak, kararı sonradan açıklanabilir kılar.

(**BA6.** Sunucunun profil yönergeleri `yan.php` içindeki `PROFILLER` tablosuyla aynıdır, yalnız `display_errors` iki profilde de `0` verilir; tablodaki geliştirme değeri `1`dir. Bu derste yanıt gövdesi değil yalnız günlük ve durum kodu ölçüldü; `display_errors` ayrımının yanıta etkisi ölçülmedi, sonraki yapılandırma ayarı dersinde ele alınır.) Yerleşik sunucunun işçi süreçleri ölçüm için seçildi. Üretimde süreç yöneticisinin işçi sayısını nasıl belirlediği, işçilerin kaç istekten sonra yeniden başlatıldığı ve bir işçi çöktüğünde ne olduğu yapılandırma konularıdır ve bu ölçümün kapsamında değildir. Ölçüm, işçi sayısı iki iken bile iki profilin aynı durumu verdiğini ve durumun istekler arasında taşınmadığını gösterdi. Bu iki bilgi, sonraki derste yapılandırma farklarını konuşurken zemin olacak. Özetle: durum işçi süreçte değil, onun dışında yaşar; işçi süreç sayısı da bu yüzden eşzamanlılığı belleğe karşı tartan bir dağıtım kararıdır.

#### Özet

- Web sunucusu bağlantıyı alır, süreç yöneticisi PHP kodunu çalıştıran işçi süreçleri başlatır, izler ve istekleri aralarında dağıtır.
- Yerleşik web sunucusu yalnız `php -S` ile koşar, üretim için değildir; bu derste iki işçili küçük bir model olarak kullanıldı.
- Bu dersin ölçü sınıfı **iki yanda da**dır: bellekteki sayaç iki profilde de her istekte `1` verdi, uyarılı sayfa iki profilde `200` döndü ve uyarı günlüğe düştü.
- Durum kodu uyarıyı haber vermez; üretimde uyarıyı görmenin yolu günlüktür.
- Bellekteki durum istekler arasında taşınmaz, dosyadaki taşınır: paylaşılacak durum süreç dışında tutulur.
- Statik özellik, global değişken, fonksiyon içi `static` değişken ve `include_once` sayaçlarının dördü de üçüncü istekte `1` verdi; istek başına durum iki profilde aynıdır (ölçü sınıfı: iki yanda da).

#### Sonraki Adım

İşçi süreç ayarlarıyla başlar: bellek sınırı, hata raporlama ve kod önbelleği yönergeleri süreç başlarken verilir. Son ders iki profilin yedi yönergede nasıl ayrıştığını yazar ve çalışırken bir yönergeyi `ini_set()` ile değiştirmenin hangi profilde kabul edildiğini ölçer.

### Yapılandırma Ayarı

Source: https://academia.sh/tr/kurslar/php-uretim/yapilandirma-ayari

PHP'nin geliştirme ve üretim ayar dosyalarının ayrıştığı yedi yönergeyi ölçmek: sertleştirme neyi kapatır ve kod içinden ini_set() ile geri açılabilir mi.

PHP iki hazır ayar dosyasıyla dağıtılır: biri geliştirme, biri üretim içindir. Bu dersin konusu, o iki dosyanın birbirinden hangi noktada ayrıldığıdır. Ayrım bir tercih listesi değil, sertleştirmedir (hardening): üretimde hata ayrıntısını, çağrı argümanlarını ve geliştirme denetimlerini kapatarak, bir hatanın dışarıya bilgi sızdırmasını engellemek. Ortam değişkenleriyle yapılandırmayı daha önce web uygulaması bağlamında gördünüz ("Yapılandırma ve Ortam Değişkenleri"); burada ayarın kendisi, yani PHP'nin yönergeleri ölçülür.

Ölçü ekseni bu kursun ortak sorusudur: bu davranış üretime çıkıyor mu? Aşağıdaki `yan.php` tam olarak bu iki dosyayı `PROFILLER` sabitinde taşır. Dosya bu derste de yalnız diske yazılır.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Önce yedi farkı `PROFILLER` sabitinden çıkaralım. Betik iki profildeki değerleri karşılaştırır ve yalnız ayrışan yönergeleri yazar. Profil dizisinde yalnız üretime özgü olan `opcache.*` yönergeleri bu karşılaştırmanın dışında kalır, çünkü iki dosyanın farkı değil, komut satırında kod önbelleğini açan eklemelerdir.

```php
<?php
// fark.php — iki profilin ayrıştığı yönergeleri yazar
declare(strict_types=1);
require __DIR__ . '/yan.php';

$ayrisan = 0;
foreach (PROFILLER['geliştirme'] as $ad => $deger) {
    $uretim = PROFILLER['üretim'][$ad];
    if ($deger !== $uretim) {
        $ayrisan++;
        echo "$ad: geliştirme $deger · üretim $uretim\n";
    }
}
echo "ayrışan yönerge sayısı: $ayrisan\n";
```

```
display_errors: geliştirme 1 · üretim 0
display_startup_errors: geliştirme 1 · üretim 0
error_reporting: geliştirme 32767 · üretim 24575
mysqlnd.collect_memory_statistics: geliştirme 1 · üretim 0
zend.assertions: geliştirme 1 · üretim -1
zend.exception_ignore_args: geliştirme 0 · üretim 1
zend.exception_string_param_max_len: geliştirme 15 · üretim 0
ayrışan yönerge sayısı: 7
```

Yedi satırın okunuşu şöyledir. İlk üç satır hata görünürlüğünü belirler: `display_errors` ve `display_startup_errors` hatayı yanıta yazar, `error_reporting` hangi türlerin bildirileceğini söyler. `mysqlnd.collect_memory_statistics` veritabanı sürücüsünün bellek istatistiğini toplar, üretimde kapalıdır. `zend.assertions` `assert()` çağrılarının derlenip derlenmeyeceğini belirler. Son iki satır istisna izine aittir: `zend.exception_ignore_args` izden çağrı argümanlarını atar, `zend.exception_string_param_max_len` kalan dize argümanlarının uzunluğunu sınırlar.

| Yönerge | Geliştirme | Üretim |
|---|---|---|
| `display_errors` | `1` | `0` |
| `display_startup_errors` | `1` | `0` |
| `error_reporting` | `32767` | `24575` |
| `mysqlnd.collect_memory_statistics` | `1` | `0` |
| `zend.assertions` | `1` | `-1` |
| `zend.exception_ignore_args` | `0` | `1` |
| `zend.exception_string_param_max_len` | `15` | `0` |

Şimdi ayrımın davranışa nasıl yansıdığına bakalım. Birinci yoklama çalışırken `ini_set('zend.assertions', '1')` dener ve çağrının reddedildiğini, değerin değişmediğini sorar. İkinci yoklama içinde bir fonksiyon çağrısı olan bir istisna fırlatır ve istisna izinde argümanın saklanıp saklanmadığına bakar. Her iki yoklama da `evet` yönünde yazıldı: yalnız üretimde görünen davranış `evet` verir.

```php
<?php
// ayar.php — sertleştirme davranışta nasıl görünür?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('assertion.php', <<<'PHP'
<?php
// assertion.php — çalışırken zend.assertions açılmaya çalışılır
$donen = @ini_set('zend.assertions', '1');
echo $donen === false && ini_get('zend.assertions') === '-1' ? "evet\n" : "hayır\n";
PHP);

file_put_contents('iz.php', <<<'PHP'
<?php
// iz.php — istisna izinde çağrı argümanı saklanır mı?
function at(string $gizli): void {
    throw new RuntimeException();
}
try {
    at('gizli deger');
} catch (RuntimeException $istisna) {
    echo !isset($istisna->getTrace()[0]['args']) ? "evet\n" : "hayır\n";
}
PHP);

yanRaporu('zend.assertions çalışırken açılamaz', 'assertion.php');
yanRaporu('istisna izinde argüman saklanmaz', 'iz.php');
```

```
zend.assertions çalışırken açılamaz → yalnız üretimde
    geliştirme hayır · üretim evet
istisna izinde argüman saklanmaz → yalnız üretimde
    geliştirme hayır · üretim evet
```

Her iki yoklama yalnız üretimde `evet` verdi: yan **yalnız üretimde**. Üretimde `zend.assertions` değeri `-1` iken çalışırken açılmaya çalışılan değer reddedildi ve `-1` kaldı; bu, üretimde derlenmemiş `assert()` çağrılarının sonradan etkinleştirilemeyeceği anlamına gelir. İstisna izi ise argümanı taşımadı; geliştirmede aynı iz argümanı taşıdığı için bir hata mesajının yığıt kaydı bir parolayı ya da kullanıcı verisini dışarı çıkarabilirdi. Üretimde bu iki sızıntı yolu kapalıdır. (**BA5.** Bu yoklama yalnız profil değerinin etkisine bakar; `zend.exception_ignore_args` yönergesinin çalışırken değiştirilebilirliği aşağıdaki erişim düzeyi bölümünde ölçülür.)

#### Yönergenin Erişim Düzeyi

Ayar dosyası bir yönergeyi belirler, ama yönergeye sonradan kim dokunabilir sorusu ayrıdır. PHP her yönergeye bir erişim düzeyi (access level) verir: `PHP_INI_USER` kullanıcı betiğinin `ini_set()` ile değiştirebileceği, `PHP_INI_PERDIR` ayar dosyasında ya da dizin düzeyinde değiştirilebileni, `PHP_INI_SYSTEM` ise yalnız ana ayar dosyasında belirlenebileni gösterir. Üçünü birden taşıyan yönerge `PHP_INI_ALL` olarak anılır. `ini_get_all(null, true)` her yönergenin bu düzeyini döndürür. Aşağıdaki yoklama sekiz yönerge için iki şey yazar: erişim düzeyi ve `ini_set()` çağrısının kabul edilip edilmediği. Kabul, çağrının `false` dönmemesi demektir. Sekiz yönergeden yedisi profil farkı olan yönergelerdir; sekizincisi `opcache.enable_cli` üretim profilinin eklediği ve değeri komut satırında kod önbelleğini açan yönergedir. `mysqlnd.collect_memory_statistics` ölçüm dışında bırakıldı: yönerge veritabanı sürücüsü eklentisine bağlıdır ve bu ders yalnız çekirdek ve kod önbelleği yönergelerinin erişim düzeyini ölçer. Yoklama çocuk süreçte koşar, sonucu profile özgü dosyaya yazar; sürücü ikisini karşılaştırır.

Ek olarak ikinci bir yoklama, `zend.exception_ignore_args` için `ini_set()` davranışını doğrudan sınar: üretim profilinde yönerge `1` iken kod içinden `0` yapılırsa, sonradan fırlatılan istisnanın izinde argüman görünür mü?

```php
<?php
// erisim.php — yönergeyi kim değiştirebilir, ini_set() kabul eder mi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('erisim-olc.php', <<<'PHP'
<?php
// erisim-olc.php — erişim düzeyini ve ini_set() kabulünü profile özgü dosyaya yazar
$profil = ini_get('zend.assertions') === '1' ? 'gelistirme' : 'uretim';
$hedefler = [
    'display_errors' => '1',
    'display_startup_errors' => '1',
    'error_reporting' => '1',
    'zend.assertions' => '0',
    'zend.exception_ignore_args' => '0',
    'zend.exception_string_param_max_len' => '5',
    'opcache.enable_cli' => '1',
    'opcache.validate_timestamps' => '1',
];
$tum = ini_get_all(null, true);
$seviyeler = [1 => 'USER', 2 => 'PERDIR', 4 => 'SYSTEM'];
$satirlar = [];
foreach ($hedefler as $ad => $hedef) {
    $erisim = $tum[$ad]['access'];
    $adlar = [];
    foreach ($seviyeler as $bit => $seviye) {
        if (($erisim & $bit) !== 0) {
            $adlar[] = $seviye;
        }
    }
    $kabul = @ini_set($ad, $hedef) !== false ? 'kabul' : 'ret';
    $satirlar[] = $ad . ': ' . ($erisim === 7 ? 'ALL' : implode('|', $adlar)) . ' · ini_set ' . $kabul;
}
file_put_contents("erisim-$profil.txt", implode("\n", $satirlar) . "\n");
echo "evet\n";
PHP);

file_put_contents('iz-acik.php', <<<'PHP'
<?php
// iz-acik.php — zend.exception_ignore_args kod içinden 0 yapılırsa izde argüman görünür mü?
ini_set('zend.exception_ignore_args', '0');
function at(string $gizli): void {
    throw new RuntimeException();
}
try {
    at('gizli deger');
} catch (RuntimeException $istisna) {
    echo isset($istisna->getTrace()[0]['args']) ? "evet\n" : "hayır\n";
}
PHP);

yanRaporu('erişim ölçümü tamamlanır', 'erisim-olc.php');
echo file_get_contents('erisim-uretim.txt');
$uretimSatirlari = file('erisim-uretim.txt', FILE_IGNORE_NEW_LINES);
foreach (file('erisim-gelistirme.txt', FILE_IGNORE_NEW_LINES) as $satir) {
    if (!in_array($satir, $uretimSatirlari, true)) {
        echo "geliştirmede farklı: $satir\n";
    }
}
yanRaporu('kod içinden 0 yapılan zend.exception_ignore_args izde argümanı geri getirir', 'iz-acik.php');
```

```
erişim ölçümü tamamlanır → iki yanda da
    geliştirme evet · üretim evet
display_errors: ALL · ini_set kabul
display_startup_errors: ALL · ini_set kabul
error_reporting: ALL · ini_set kabul
zend.assertions: ALL · ini_set ret
zend.exception_ignore_args: ALL · ini_set kabul
zend.exception_string_param_max_len: ALL · ini_set kabul
opcache.enable_cli: SYSTEM · ini_set ret
opcache.validate_timestamps: ALL · ini_set kabul
geliştirmede farklı: zend.assertions: ALL · ini_set kabul
kod içinden 0 yapılan zend.exception_ignore_args izde argümanı geri getirir → iki yanda da
    geliştirme evet · üretim evet
```

Tabloyu satır satır okuyalım. Sekiz yönergenin yedisi `ALL` düzeyindedir; yani `PHP_INI_USER`, `PHP_INI_PERDIR` ve `PHP_INI_SYSTEM` bitlerinin üçü de açıktır ve kod içinden `ini_set()` ile değiştirilebilir. `opcache.enable_cli` ise yalnız `SYSTEM` düzeyindedir: yalnız ana ayar dosyasında ya da komut satırında verilebilir ve çalışırken `ini_set()` onu reddeder. Tablo `ret` ile `kabul` arasındaki farkı gösterir. `ret`, çağrının `false` döndüğü, değerin değişmediği anlamına gelir. `kabul` ise çağrının değeri değiştirdiğidir. Tabloda üretim yanında iki `ret` var: `zend.assertions` ve `opcache.enable_cli`. Geliştirme yanında yalnız biri var: `opcache.enable_cli`.

Üretim ile geliştirme arasındaki tek fark `zend.assertions` satırıdır ve sürücünün son satırı bunu açıkça yazar: geliştirmede aynı yönerge `kabul` verdi. Erişim düzeyi iki profilde de `ALL` görünüyor, yani yönerge kâğıt üzerinde kod içinden değiştirilebilir; yine de üretimde `-1` iken reddediliyor. Bunun sebebi PHP'nin bu yönergeye koyduğu ek kuraldır: `-1` değeri derleme zamanında `assert()` çağrılarının koddan çıkarılması demektir ve bir kez çıkarılan çağrı çalışırken geri getirilemez. Erişim düzeyi tek başına yeterli bilgi vermez: `ALL` yazan bir yönerge yine de belirli bir değerden belirli değerlere geçişi reddedebilir. Bu yüzden ölçüm yalnız tabloya değil, `ini_set()` sonucuna da bakar.

Üretimde altı yönergenin `kabul` çıkması önemli bir uyarıdır: `display_errors`, `error_reporting`, `zend.exception_ignore_args` ve `zend.exception_string_param_max_len` gibi yönergeler ayar dosyasında sertleştirildiği hâlde, kodun herhangi bir satırı bunları gevşetebilir. Son yoklama bunu `zend.exception_ignore_args` için doğrudan gösterdi. Üretim profilinde yönerge `1` iken kod içinden `0` yapıldığında, sonradan fırlatılan istisnanın izinde argüman yeniden göründü. (**BA7.** Yoklama yönergeyi istisna fırlatmadan önce değiştirdi; önceden oluşturulmuş bir istisnanın izi sonradan değişmez, bu ölçülmedi.) Bu yoklama iki profilde de `evet` verdiği için sınıfı `iki yanda da`dır; ama çıkardığı sonuç yalnız üretim için önemlidir, çünkü üretimde bu çağrı sertleştirmeyi geri alır.

Gerçek bir uygulamada bunun karşılığı iki somut alışkanlıktır. Birincisi, kodda `ini_set()` çağrılarını düzenli aramaktır: özellikle kütüphanelerin içinde hata görünürlüğünü ya da istisna izini gevşeten çağrılar, ayar dosyasındaki sertleştirmeyi sessizce geçersiz kılar. İkincisi, bir yönergeyi gerçekten kilitlemek istiyorsanız erişim düzeyine bakmaktır: `PHP_INI_SYSTEM` düzeyindeki yönergeler kod içinden değişmez, `ALL` düzeyindekiler değişir. Bazı barındırma kurulumları `disable_functions` ya da özel ayar dosyalarıyla `ini_set()` çağrısının kendisini kısıtlar; bu kısıtlamalar bu derste kurulmadı ve ölçülmedi.


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

Sertleştirme ayar dosyasında yapılır; kod içinden geri alınamaz mı? `display_errors` bir kod satırıyla yeniden açılabilen yönergelerden biridir. Üretim profiliyle başlayıp betik içinde `ini_set('display_errors', '1')` çağırırsak hata yanıta yazılır mı? Yoklama hata iletisini yakalar ve metnini basmaz; yalnız bir uyarı iletisinin yakalanıp yakalanmadığını söyler.

```php
<?php
// sinir.php — kod içi ini_set() display_errors'u geri açar mı?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('gorunur.php', <<<'PHP'
<?php
// gorunur.php — display_errors kod içinden açılır, uyarı yakalanır
$donen = ini_set('display_errors', '1');
ob_start();
echo $tanimsiz;
$yakalanan = ob_get_clean();
echo $donen !== false && ini_get('display_errors') === '1' && str_contains($yakalanan, 'Warning') ? "evet\n" : "hayır\n";
PHP);

yanRaporu('ini_set() ile açılan display_errors uyarıyı yanıta yazar', 'gorunur.php');
```

```
ini_set() ile açılan display_errors uyarıyı yanıta yazar → iki yanda da
    geliştirme evet · üretim evet
```

Yoklama iki profilde de `evet` verdi: üretim profilinde başlayan süreç, kod içinden `display_errors` açıldığında uyarıyı yanıta yazdı. Yani ayar dosyasındaki `display_errors=0` bir kilit değildir; bir kütüphane ya da bir betik satırı onu geri açabilir. Karşıt örnek `zend.assertions` idi: o yönerge `-1` iken çalışırken açılamaz. Sertleştirmenin gücü yönergeden yönergeye değişir, bu yüzden üretimde yalnız ayar dosyasına güvenmek yetmez. Kodda `ini_set()` çağrılarını aramak ve hata görünürlüğünü bu kayıtların dışında da denetlemek gerekir.

Dersin dürüst sınırı şudur: ölçülen şey yedi yönergenin değerleri ve bu değerlerin yukarıdaki yoklamalarda görülen davranışlarıdır. Bir üretim kurulumunda başka yönergeler de açılır ya da kapanır; liste burada hazır dosyaların farkıdır, bir kurulum kontrol listesi değil.

Dersin ölçümlerini birbirine bağlayalım. Yedi yönergelik fark tablosu, hazır iki dosyanın profile göre nerede ayrıştığını söyler. Davranış yoklamaları, o ayrımın üretimde somut olarak neyi kapattığını gösterir: çalışırken açılamayan `assert()` denetimi ve argümansız istisna izi. Erişim düzeyi ölçümü ise bu kapanışın ne kadar sağlam olduğunu sınar: bazıları kilitlidir, bazıları değildir. Sınırlayıcı ölçüm de aynı soruyu `display_errors` için sorar ve yanıtın olumsuz olduğunu gösterir. Birlikte okununca ortaya şu çıkar: üretim ayarı bir sertleştirme listesidir, ama her maddesinin aynı sağlamlıkta olduğunu varsaymak yanlıştır.

#### Özet

- Geliştirme ve üretim ayar dosyaları yedi yönergede ayrışır: `display_errors`, `display_startup_errors`, `error_reporting`, `mysqlnd.collect_memory_statistics`, `zend.assertions`, `zend.exception_ignore_args` ve `zend.exception_string_param_max_len`.
- Bu dersin ölçü sınıfı **yalnız üretimde**dir: üretimde `zend.assertions` çalışırken açılamaz ve istisna izinde argüman saklanmaz; geliştirmede ikisi de görülmez.
- Ayrımın amacı sertleştirmedir: hata ayrıntısı, çağrı argümanları ve geliştirme denetimleri üretimde dışarı sızmaz.
- `display_errors` kod içinden `ini_set()` ile geri açılabildi (iki yanda da); ayar dosyasındaki değer her yönerge için kilit değildir.
- Erişim düzeyi tek başına yetmez: `zend.assertions` `ALL` görünür ama üretimde `-1` iken `ini_set()` reddedilir, geliştirmede kabul edilir; `opcache.enable_cli` her iki profilde de `SYSTEM` düzeyindedir ve reddedilir (ölçü sınıfı: yalnız üretimde).
- Kod içinden `0` yapılan `zend.exception_ignore_args`, üretimde istisna izine argümanı geri getirir (ölçü sınıfı: iki yanda da).

#### Sonraki Adım

Bu kursun son dersi dağıtım iş akışıdır: kodu üretime alırken ayar, kod önbelleği ve süreç yöneticisinin tek bir akışta nasıl bir araya geldiği. Önceki beş derste ölçülen her davranış orada bir adıma karşılık gelir.

### Dağıtım Akışı

Source: https://academia.sh/tr/kurslar/php-uretim/dagitim-akisi

Kodu üretime alırken sürüm dizinleri ve sembolik bağ geçişi: atomik geçişten sonra neden eski sürüm koşar, hangi çağrı yeni sürümü getirir.

Önceki beş ders tek tek ayar ve ölçüm yaptı: kod önbelleği, süreç yöneticisi, yapılandırma yönergeleri. Bir dağıtım hepsini aynı anda sınar, çünkü yeni kod yalnız diske yazılmakla üretime çıkmaz. Kesintisiz dağıtım (zero-downtime deployment) denen düzende her sürüm kendi dizinine kurulur ve istekleri karşılayan yol, tek bir adımda yeni dizine çevrilir. Bu ders o adımı kurar ve üretim ayarlarının onu nasıl etkilediğini ölçer.

Ölçü ekseni yine şu soru: bu davranış üretime çıkıyor mu? DevOps ve Platform Mühendisliği müfredatında geliştirme ile üretim arasındaki her sapma gizli kusur sayıldı ve kapatılması hedeflendi. Burada farkın çoğu kasıtlıdır: PHP iki ayar dosyasıyla dağıtılır ve fark o dosyalardan gelir. Bu ders farkı kapatmaz, adlandırır. Aşağıdaki `yan.php` kursun ortak çekirdeğidir; bu derste de yalnız diske yazılır, çıktısı yoktur.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

#### Sürüm Dizinleri ve Atomik Geçiş

Düzen şöyledir: her sürüm `surum-1`, `surum-2` gibi ayrı bir dizindedir ve isteklerin kullandığı `guncel` adı bir sembolik bağdır (symbolic link); bağ, o anda yayında olan sürüm dizinini gösterir. Yeni sürüm yanda hazırlanır, çalışan sürüme hiç dokunulmaz. Yayına alma, bağın başka bir dizini göstermesinden ibarettir.

Geçişin atomik (atomic) olması gerekir: bir istek bağı okurken ne eski ne yeni dizini gösteren bir ara durum görmemelidir. `symlink()` var olan bir adın üzerine yazmaz; bu yüzden önce `guncel-yeni` adıyla yeni bir bağ kurulur, sonra `rename()` onu `guncel` adının üstüne taşır. Aynı dosya sistemi içinde `rename()` tek adımda biter: `guncel` adı her an ya eski ya yeni dizini gösterir. İki yardımcı fonksiyon bu düzeni kurar. Dosya bu derste yalnız diske yazılır.

```php
<?php
// kur.php — iki sürüm dizini, ilkini gösteren bağ ve atomik geçiş
declare(strict_types=1);

function surumleriKur(): void {
    foreach ([1, 2] as $n) {
        @mkdir(__DIR__ . "/surum-$n");
        $yol = __DIR__ . "/surum-$n/selam.php";
        file_put_contents($yol, "<?php return $n;");
        touch($yol, time() - 60);
    }
    @unlink(__DIR__ . '/guncel');
    symlink('surum-1', __DIR__ . '/guncel');
}

function gecir(): void {
    @unlink(__DIR__ . '/guncel-yeni');
    symlink('surum-2', __DIR__ . '/guncel-yeni');
    rename(__DIR__ . '/guncel-yeni', __DIR__ . '/guncel');
}
```

Her sürümün `selam.php` dosyası yalnız sürüm numarasını döndürür. `touch()` dosyaların zamanını geçmişe alır; önbellek son saniyelerde değişmiş dosyayı almadığı için yoklamalar bu adımı kodun başında yapar. İlk yoklama geçişin diskte işe yaradığını, ikincisi önbellek açıkken ne olduğunu sorar. Her yoklama kendi sürecinde ve `surumleriKur()` ile sıfırdan kurulan dizinlerde koşar.

```php
<?php
// dagitim.php — geçişten sonra hangi sürüm koşuyor?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('gecis.php', <<<'PHP'
<?php
// gecis.php — bağ geçişten sonra yeni dizini gösterir mi?
require __DIR__ . '/kur.php';
surumleriKur();
gecir();
echo is_link(__DIR__ . '/guncel') && readlink(__DIR__ . '/guncel') === 'surum-2' ? "evet\n" : "hayır\n";
PHP);

file_put_contents('eski.php', <<<'PHP'
<?php
// eski.php — geçişten sonra aynı yoldan eski sürüm mü koşar?
require __DIR__ . '/kur.php';
surumleriKur();
$once = include __DIR__ . '/guncel/selam.php';
gecir();
$sonra = include __DIR__ . '/guncel/selam.php';
echo $sonra === $once ? "evet\n" : "hayır\n";
PHP);

yanRaporu('bağ geçişten sonra yeni dizini gösterir', 'gecis.php');
yanRaporu('geçişten sonra aynı yoldan eski sürüm koşar', 'eski.php');
```

```
bağ geçişten sonra yeni dizini gösterir → iki yanda da
    geliştirme evet · üretim evet
geçişten sonra aynı yoldan eski sürüm koşar → yalnız üretimde
    geliştirme hayır · üretim evet
```

Birinci yoklama iki profilde de `evet` verdi: geçiş diskte her yerde aynı biçimde tamamlanır. İkinci yoklama yalnız üretimde `evet` verdi (**BA8.** Ölçüm komut satırı sürecinde, tek süreçle yapıldı; gerçek bir dağıtımda geçiş sürüyor olan işçi süreçlerin yanında olur ve her süreç kendi isteğini yeni yoldan görür, bu derste o düzen kurulmadı). Sembolik bağ yeni dizini gösterdiği hâlde `guncel/selam.php` yolundan gelen ikinci yükleme, birincisiyle aynı değeri döndürdü: önbellek bu yolu zaten tanıyordu ve diske bakmadı. Geliştirmede kod önbelleği kapalı olduğundan her yükleme diski okur ve yeni sürüm hemen gelir.

#### Yeni Sürümü Getiren Çağrılar

Sorun, önbelleğin bir yolu anahtar olarak tutması ve bu yolun bağdan geçmesidir. Dağıtım betiğinin elinde üç aday çağrı vardır: dosya sistemi denetim önbelleğini temizleyen `clearstatcache(true)`, tüm kod önbelleğini boşaltan `opcache_reset()` ve tek dosyayı geçersiz kılan `opcache_invalidate()`. Dördüncüsü çağrı değil, yoldur: bağı kodda `realpath()` ile çözmek. Dört yoklama aynı kurulumla koşar; her birinde `evet`, adının söylediği şeyin gerçekleştiği anlamına gelir.

```php
<?php
// cozumler.php — hangi çağrı geçişten sonra yeni sürümü getirir?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('stat.php', <<<'PHP'
<?php
// stat.php — clearstatcache(true) sonrası eski sürüm kalır mı?
require __DIR__ . '/kur.php';
surumleriKur();
include __DIR__ . '/guncel/selam.php';
gecir();
clearstatcache(true);
$sonra = include __DIR__ . '/guncel/selam.php';
echo $sonra === 1 ? "evet\n" : "hayır\n";
PHP);

file_put_contents('sifirla.php', <<<'PHP'
<?php
// sifirla.php — opcache_reset() true döner ama aynı istekte eski sürüm kalır mı?
require __DIR__ . '/kur.php';
surumleriKur();
include __DIR__ . '/guncel/selam.php';
gecir();
$dondu = opcache_reset();
$sonra = include __DIR__ . '/guncel/selam.php';
echo $dondu === true && $sonra === 1 ? "evet\n" : "hayır\n";
PHP);

file_put_contents('baglayol.php', <<<'PHP'
<?php
// baglayol.php — bağ yoluyla geçersiz kılmadan sonra yeni sürüm gelir mi?
require __DIR__ . '/kur.php';
surumleriKur();
$yol = __DIR__ . '/guncel/selam.php';
include $yol;
gecir();
opcache_invalidate($yol, true);
$sonra = include $yol;
echo $sonra === 2 ? "evet\n" : "hayır\n";
PHP);

file_put_contents('eskiyol.php', <<<'PHP'
<?php
// eskiyol.php — eski sürümün çözülmüş yoluyla geçersiz kılma yeni sürümü getirir mi?
require __DIR__ . '/kur.php';
surumleriKur();
$yol = __DIR__ . '/guncel/selam.php';
include $yol;
$eskiYol = realpath($yol);
gecir();
$dondu = opcache_invalidate($eskiYol, true);
$sonra = include $yol;
echo $dondu === true && $sonra === 2 ? "evet\n" : "hayır\n";
PHP);

file_put_contents('cozulmus.php', <<<'PHP'
<?php
// cozulmus.php — bağ include'dan önce realpath() ile çözülünce yeni sürüm gelir mi?
require __DIR__ . '/kur.php';
surumleriKur();
include realpath(__DIR__ . '/guncel') . '/selam.php';
gecir();
$sonra = include realpath(__DIR__ . '/guncel') . '/selam.php';
echo $sonra === 2 ? "evet\n" : "hayır\n";
PHP);

yanRaporu('clearstatcache(true) sonrası eski sürüm kalır', 'stat.php');
yanRaporu('bağ yoluyla geçersiz kılmadan sonra yeni sürüm gelir', 'baglayol.php');
yanRaporu('eski sürümün çözülmüş yoluyla geçersiz kılma yeni sürümü getirir', 'eskiyol.php');
yanRaporu('realpath() ile çözülen yoldan yeni sürüm gelir', 'cozulmus.php');
yanRaporu('opcache_reset() true döner ama aynı istekte eski sürüm kalır', 'sifirla.php');
```

```
clearstatcache(true) sonrası eski sürüm kalır → yalnız üretimde
    geliştirme hayır · üretim evet
bağ yoluyla geçersiz kılmadan sonra yeni sürüm gelir → yalnız geliştirmede
    geliştirme evet · üretim hayır
eski sürümün çözülmüş yoluyla geçersiz kılma yeni sürümü getirir → yalnız üretimde
    geliştirme hayır · üretim evet
realpath() ile çözülen yoldan yeni sürüm gelir → iki yanda da
    geliştirme evet · üretim evet
opcache_reset() true döner ama aynı istekte eski sürüm kalır → yalnız üretimde
    geliştirme hayır · üretim evet
```

Üç sonuç ayrılıyor. Birincisi, `clearstatcache(true)` işe yaramadı: üretimde ikinci yükleme yine eski sürümü verdi. Bu çağrı dosya sistemi sorgularının önbelleğini temizler, kod önbelleğine dokunmaz. İkincisi, `opcache_reset()` üretimde `true` döndü ama aynı istekte eski sürüm kaldı; çağrının başarılı görünmesi, o istekte yeni sürümün geleceği anlamına gelmiyor. Bir sonraki isteği bu ölçüm göstermedi. Üçüncüsü, geçersiz kılma çağrısının hangi yolu aldığı önemlidir: bağ yoluyla (`guncel/selam.php`) yapılan çağrıdan sonra üretimde eski sürüm kaldı; yeni sürümü yalnız önbelleği kapalı olan geliştirme gördü. Eski sürümün çözülmüş yoluyla (`realpath()` sonucu) yapılan çağrı ise üretimde yeni sürümü getirdi. Aynı biçimde, include etmeden önce `realpath()` ile çözülen yoldan yeni sürüm iki profilde de geldi. Bu üç sonuç, önbelleğin dosyayı bağın adıyla değil çözülmüş yolla tuttuğu varsayımıyla tutarlıdır. Geliştirmede `opcache_reset()` ve `opcache_invalidate()` önbellek kapalı olduğundan `false` döner, bu yüzden dağıtım betiğinin çağrıları geliştirmede sınanamaz.

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

`realpath()` çözümü, yolun ne zaman çözüldüğüne bağlıdır. Yoklama yolu geçişten önce bir kez çözer ve saklar; geçişten sonra saklanan yoldan yükler. Uzun ömürlü bir süreç yolu başlangıçta çözüp bellekte tutsaydı bu olurdu.

```php
<?php
// sinir.php — geçişten önce çözülen yol geçişten sonra da eski sürümü verir mi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('saklanan.php', <<<'PHP'
<?php
// saklanan.php — çözülmüş yol geçişten önce saklanır, geçişten sonra kullanılır
require __DIR__ . '/kur.php';
surumleriKur();
$saklanan = realpath(__DIR__ . '/guncel');
$once = include $saklanan . '/selam.php';
gecir();
$sonra = include $saklanan . '/selam.php';
echo $sonra === $once ? "evet\n" : "hayır\n";
PHP);

yanRaporu('geçişten önce çözülen yol geçişten sonra da eski sürümü verir', 'saklanan.php');
```

```
geçişten önce çözülen yol geçişten sonra da eski sürümü verir → iki yanda da
    geliştirme evet · üretim evet
```

Yoklama iki profilde de `evet` verdi: saklanan yol eski dizini gösterdiği için geçişten sonra da eski sürüm geldi, kod önbelleği açık olsun olmasın. Demek ki `realpath()` tek başına bir çözüm değil, bir zamanlama kuralıdır: çözümleme her istekte, include'dan hemen önce yapılmalıdır. Ölçümün sınırı da burada: yoklama bir süreç ve iki sürümle sınırlıdır, saklanan yolun gerçek bir işçi süreçte ne kadar yaşadığı ölçülmedi.

#### Özet

- Kesintisiz dağıtım, her sürümü ayrı dizine koyar ve `symlink()` ile kurulan yeni bağı `rename()` ile `guncel` adının üstüne taşır; geçiş (atomik geçiş) diskte iki profilde de tamamlanır.
- Bu dersin ölçü sınıfı **yalnız üretimde**dir: `opcache.validate_timestamps=0` iken geçişten sonra aynı bağ yolundan eski sürüm koşar; geliştirmede kod önbelleği kapalıdır ve yeni sürüm gelir.
- `clearstatcache(true)` bunu çözmez; `opcache_reset()` `true` döner ama aynı istekte eski sürüm kalır; geliştirmede iki `opcache_*` çağrısı `false` döner.
- Yeni sürümü getiren iki yol ölçüldü: include'dan önce `realpath()` ile çözülmüş yol ve eski sürümün çözülmüş yoluyla `opcache_invalidate($yol, true)`. Bağ yoluyla geçersiz kılmadan sonra üretimde eski sürüm kaldı.
- Sınır: `realpath()` sonucu geçişten önce saklanırsa eski sürüm iki yanda da gelir; çözümleme her istekte yapılmalıdır.

#### Kurs Kapanışı

Kurs, kodu üretime taşıyan yedi yönergenin ve dört davranış sınıfının ölçülmesiyle kapanıyor. Her dersin ölçülen davranışı, yanı ve sınırlayıcı ölçümü aşağıdadır.

| Ders | Ölçülen davranış | Yan | Sınırlayıcı ölçüm |
|---|---|---|---|
| Statik Çözümleme | çağrılmayan koldaki tanımsız fonksiyon koşmaz, söz dizimi denetimi yalnız söz dizimine bakar, belirteç denetleyicisi `ozetle` çağrısını bulur | koşuma girmiyor | adı değişkenden çağrılırsa denetleyici 0 buldu, doğrudan koşum `Error` attı |
| Kod Stili Denetimi | biçimsiz kod iki profilde aynı çalışır, belirteç tabanlı denetleyici satır sonu, çift boşluk ve girintiyi sayar | koşuma girmiyor | nowdoc içindeki satır sonu boşluğunu belirteçsiz düzeltme dizeyi kısaltır, yalnız `T_WHITESPACE` yeniden yazan biçimlendirici uzunluğu 13 bırakır |
| Test Çerçeveleri | `assert()` tabanlı sınama geliştirmede yakalar, üretimde (`zend.assertions=-1`) derlenmez | yalnız geliştirmede | `zend.assertions=0` da yakalamaz ve çalışırken `1`'e çevrilebilir, `-1`'de yol kapalıdır |
| Hata Ayıklayıcı | `kart-0042` istisna izinde geliştirmede görünür, üretimde gizlidir | yalnız geliştirmede | değer iletiye yazılırsa `getMessage()` iki profilde de `kart-0042` taşır |
| Kod Önbellekleme | `validate_timestamps=0` ile yeniden yazılan dosya eski sürümle koşar, `opcache_invalidate($yol, true)` yeni sürümü getirir | yalnız üretimde | `validate_timestamps=1` ve `revalidate_freq=0` eski sürüm sorununu giderir |
| Profil Çıkarma | 500 çağrı, toplam 249500 ve eşit bellek farkı; üç fonksiyonlu zincirde `oku` 400, `donustur` 200, `ozetle` 5 | iki yanda da | `assert()` içindeki sayaç geliştirmede 500, üretimde 0 |
| Bellek ve Bağlantı Yönetimi | `memory_limit` aşılınca istek durur, yıkıcı istek sonunda koşar | iki yanda da | döngüsel referansta `unset()` yıkıcıyı koşturmaz, `gc_collect_cycles()` koşturur |
| Süreç Yöneticisi ve Web Sunucusu | iki işçi süreçte bellekteki sayaç her istekte `1` kalır, uyarılı sayfa iki profilde `200` döner | iki yanda da | sayaç dosyada tutulursa `1 2 3`, bellekte `1 1 1` |
| Yapılandırma Ayarı | yedi yönergelik fark; `ini_set('zend.assertions','1')` geliştirmede kabul, üretimde `false` | yalnız üretimde | `display_errors` üretimde `ini_set()` ile geri açılabildi |
| Dağıtım Akışı | sembolik bağ geçişinden sonra aynı yoldan eski sürüm koşar, `realpath()` ile çözülen yoldan yeni sürüm gelir | yalnız üretimde | geçişten önce çözülüp saklanan yoldan iki profilde de eski sürüm gelir |

Derslerin dağılımı: **3 iki yanda da · 2 yalnız geliştirmede · 3 yalnız üretimde · 2 koşuma girmiyor**. Çekirdek (`davranislar.php`, Statik Çözümleme dersinde koşturuldu): **4 · 4 · 2 · 2**. İki sayı ayrı şeyleri sayar: ilki on dersin ana ölçümünün yanını, ikincisi on iki ayrı davranışın yanını sayar; bir ders birden çok davranış ölçtüğü ve bir davranış birden çok dersi ilgilendirdiği için iki dağılımın aynı çıkması beklenmez.

M12 burada kapanır; beş kurs PHP'yi bir dilden bir üretim sistemine kadar yürüttü. PHP Temelleri kursu, bir sorunun hangi yoldan duyulduğunu ölçtü (*bu sorun hangi kanaldan duyuluyor*: sessiz · dönüş değeri · uyarı · durdurur, 3/4/4/3). Nesneye Dayalı PHP ve Standartlar kursu, bir değişikliğin nereye ulaştığını ölçtü (*bu değişiklik nereye ulaşıyor*: yerinde · bağlantıda · kıran · örtük, 3/3/3/3). Web ile PHP: Oturum, Veri ve Güvenlik kursu, sonucu kimin değiştirebildiğini ölçtü (*bu sonucu kim değiştirebiliyor*: sunucu · ortam · istemci · üçüncü taraf, 5/4/4/2). Web Uygulama Çerçevesi kursu, bir parçanın o istekte koşup koşmadığını ölçtü (*bu parça bu istekte koştu mu*: koştu · ertelendi · atlandı · bağlanmadı, 6/5/5/5). PHP'de Kalite, Başarım ve Dağıtım kursu, bir davranışın üretime çıkıp çıkmadığını ölçtü (*bu davranış üretime çıkıyor mu*: iki yanda da · yalnız geliştirmede · yalnız üretimde · koşuma girmiyor, 3/2/3/2). Sıradaki müfredat Ruby'dir.

