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-yuklemegözleminde sınıf dosyası başka kök dizine taşındı, üç dosya değişti, çıktıkalemkaldı ve değişiklikyukleyici.phpnoktasında karşılandı. - Karşıtı
yukleyicisizkı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,::classve 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.phpdiye 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.