İçeriğe geç
academia.sh

Ders 09 / 12

Otomatik Yükleme

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

Yayın Güncelleme

Bu derste

  • spl_autoload_register ile bir ad alanı önekini dizine eşleyen otomatik yükleyici yazar
  • Sınıf dosyası taşındığında yükleyicinin tek bağlantı noktası olarak değişikliği karşıladığını ölçer
  • Dosya sayısının değil kullananın düzenlenip düzenlenmediğinin sınıfı belirlediğini gösterir
İçindekiler

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

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

Elle Yükleme ve Bedeli

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Birden Çok Yükleyici

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

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

echo "-- Hedef\n";
class_exists('Hedef');
echo "-- Yok\n";
class_exists('Yok');
-- Hedef
basa: Hedef
birinci: Hedef
ikinci: Hedef
-- Yok
basa: Yok
birinci: Yok
ikinci: Yok
ucuncu: Yok

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

Önekten Dizine Eşleme

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

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

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

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

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

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

Ölçüm Düzeneği

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

<?php
// yayilim.php — bir degisikligin nereye ulastigini olcen yardimcilar

function yaz(string $dizin, array $dosyalar): void
{
    foreach ($dosyalar as $ad => $icerik) {
        if ($icerik === null) {
            continue; // null: dosya silindi
        }
        $yol = "$dizin/$ad";
        if (!is_dir(dirname($yol))) {
            mkdir(dirname($yol), 0777, true);
        }
        file_put_contents($yol, $icerik);
    }
}

function oku(string $dizin): array
{
    $dosyalar = [];
    $gezgin = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dizin, FilesystemIterator::SKIP_DOTS)
    );
    foreach ($gezgin as $dosya) {
        $ad = substr($dosya->getPathname(), strlen($dizin) + 1);
        $dosyalar[$ad] = file_get_contents($dosya->getPathname());
    }
    return $dosyalar;
}

function kostur(string $dizin): array
{
    $komut = 'cd ' . escapeshellarg($dizin) . ' && php -d display_errors=0 main.php 2>/dev/null';
    exec($komut, $satirlar, $kod);
    return [$kod, implode("\n", $satirlar)];
}

function olc(string $ad, array $taban, array $degisiklik, array $baglanti = []): string
{
    if (array_key_exists('main.php', $degisiklik)) {
        throw new LogicException("$ad: kullanan duzenlenmez");
    }
    yaz("olcum/$ad/once", $taban);
    yaz("olcum/$ad/sonra", array_merge($taban, $degisiklik));
    [$kodOnce, $ciktiOnce] = kostur("olcum/$ad/once");
    [$kodSonra, $ciktiSonra] = kostur("olcum/$ad/sonra");
    if ($kodOnce !== 0) {
        throw new LogicException("$ad: taban calismadi");
    }
    $sinif = match (true) {
        $kodSonra !== 0 => 'kiran',
        $ciktiOnce !== $ciktiSonra => 'ortuk',
        array_intersect(array_keys($degisiklik), $baglanti) !== [] => 'baglantida',
        default => 'yerinde',
    };
    printf(
        "%-18s %d dosya  cikis=%-3d cikti %-6s -> %s\n",
        $ad,
        count($degisiklik),
        $kodSonra,
        $ciktiOnce === $ciktiSonra ? 'ayni' : 'farkli',
        $sinif
    );
    return $sinif;
}

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

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

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

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

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

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

$once = oku('olcum/otomatik-yukleme/once');
olc('yukleyicisiz', $once, [
    'src/Model/Urun.php' => null,
    'kaynak/Model/Urun.php' => $once['src/Model/Urun.php'],
]);
otomatik-yukleme   3 dosya  cikis=0   cikti ayni   -> baglantida
kalem -> kalem
yukleyicisiz       2 dosya  cikis=255 cikti farkli -> kiran

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

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

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

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

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

$once = oku('olcum/otomatik-yukleme/once');
olc('kucuk-harf', $once, [
    'src/Model/Urun.php' => null,
    'src/Model/urun.php' => $once['src/Model/Urun.php'],
]);
kucuk-harf         2 dosya  cikis=255 cikti farkli -> kiran

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

Özet

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

Sonraki Adım

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

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat