Ders 08 / 12
Ad Alanları
namespace, use ve takma ad ile tam nitelikli ad; aynı adlı iki sınıfın ad alanıyla ayrılması ve ad alanı olmayan kodda aynı çakışmanın programı durdurması; nitelenmemiş fonksiyon çağrısının küresel fonksiyona düşmesi.
Yayın Güncelleme
Bu derste
- namespace, use ve as ile bir sınıfa tam nitelikli adla ya da takma adla erişir
- Aynı adlı iki sınıfın küresel ad alanında kıran, ayrı ad alanlarında yerinde kaldığını ölçer
- Nitelenmemiş fonksiyon çağrısının küresel fonksiyona düştüğünü ve ters eğik çizgi önekinin bunu önlediğini gösterir
İçindekiler
Tip Bildirimleri dersinde değişiklik tanımın kendi içinden geldi: bir parametreye tip eklendi ve eski çağrı bunun bedelini ödedi. Bu derste değişiklik tanımın dışından geliyor. Başka biri, bizim sınıfımızla aynı adı taşıyan bir sınıf ekliyor. Kurs boyunca sorulan soru değişmiyor: bu değişiklik nereye ulaşıyor? Yanıt dört sınıftan biri: yerinde, bağlantıda, kıran, örtük.
M09/K02 bir adın bağlandığı yeri arıyordu: bildirimde mi, kullanım yerinde mi, değerde mi. Ad alanı o bağın kurulduğu yere bir önek ekler. Bu derste ise bağ değişince kaç dosyanın düzenlendiği ve düzenlenmemiş kodun ne yaşadığı ölçülür.
Ad Alanı, use ve Tam Nitelikli Ad
Ad alanı (namespace), adlara önek veren bir kapsamdır. namespace Magaza; ile açılan dosyadaki sınıf Kayit değil, Magaza\Kayit adını taşır. Önekli bu uzun biçime tam nitelikli ad denir. use bir tam nitelikli adı dosya içinde kısa adla anılabilir yapar; as ise aynı kısa ada sahip iki sınıf için takma ad verir.
<?php // adlar.php — ad alani, tam nitelikli ad ve takma ad namespace Magaza { class Kayit {} } namespace Dis { class Kayit {} } namespace { use Magaza\Kayit; use Dis\Kayit as DisKayit; echo Kayit::class, "\n"; echo DisKayit::class, "\n"; echo \Dis\Kayit::class, "\n"; echo get_class(new DisKayit()), "\n"; }
Magaza\Kayit Dis\Kayit Dis\Kayit Dis\Kayit
İki sınıfın kısa adı aynı, tam nitelikli adları farklı. Kayit ismi Magaza\Kayit’e, DisKayit takma adı Dis\Kayit’e bağlandı; ters eğik çizgiyle başlayan üçüncü satır use olmadan tam adı yazıyor. get_class ise nesnenin gerçek adını tam nitelikli biçimde döndürüyor. Ad alanı bir klasör değil, bir ad önekidir: sınıfın hangi dosyada durduğunu söylemez.
Ad alanının içinde yazılan nitelenmemiş bir sınıf adı, o ad alanının sınıfı sayılır. PHP’nin kendi sınıfları küresel ad alanında durur; bu yüzden ad alanı açıkken onlara da tam adla ya da use ile ulaşılır.
<?php // kok.php — ad alaninda nitelenmemis sinif adi kuresele dusmez namespace Magaza; echo Exception::class, "\n"; echo \Exception::class, "\n"; try { new Exception('x'); } catch (\Error $hata) { echo get_class($hata), "\n"; } echo get_class(new \Exception('x')), "\n";
Magaza\Exception Exception Error Exception
Exception yazıldığında PHP onu Magaza\Exception diye okudu; böyle bir sınıf olmadığı için new bir Error fırlattı ve catch onu yakaladı. Başına ters eğik çizgi konan ad küresel sınıfı seçti. Sınıflar için kural böyle: ad alanında bulunamayan sınıf adı küresele düşmez. Fonksiyonlar için kural farklı; bu dersin sınırlayıcı ölçümü tam olarak o fark.
Ad Çözümleme: Nitelenmemiş, Nitelenmiş, Tam Nitelikli
PHP bir adı üç biçimde görür ve her biri farklı çözülür. Nitelenmemiş ad ters eğik çizgi içermez (Para). Nitelenmiş ad içerir ama başında çizgi yoktur (Alt\Para). Tam nitelikli ad küresel ad alanından başlar ve başında çizgi taşır (\Para). Çözümleme sırası kısadır. Tam nitelikli ad olduğu gibi kalır. Nitelenmemiş ad önce use ile içe alınmış mı diye bakılır, alınmamışsa bulunulan ad alanına eklenir. Nitelenmiş adın ilk parçası use takma adıysa o parça takma adın gösterdiği adla değiştirilir, değilse yine bulunulan ad alanının altına yazılır. namespace\Para biçimi ise açıkça bulunulan ad alanını seçer.
Bu çözümleme derleme sırasında yapılır ve sınıfın var olmasını gerektirmez. ::class yalnız çözülmüş adı verir, bu yüzden aşağıdaki sürücüde hiçbir sınıf tanımlanmadan beş biçimin sonucu yan yana görülebilir.
<?php // cozum.php — bes yazilis bicimi ve cozulmus adlari namespace Magaza\Siparis; use Magaza\Arac as A; echo 'nitelenmemis : ', Para::class, "\n"; echo 'nitelenmis : ', Alt\Para::class, "\n"; echo 'takma adli : ', A\Para::class, "\n"; echo 'tam nitelikli : ', \Para::class, "\n"; echo 'namespace\ oneki: ', namespace\Para::class, "\n";
nitelenmemis : Magaza\Siparis\Para nitelenmis : Magaza\Siparis\Alt\Para takma adli : Magaza\Arac\Para tam nitelikli : Para namespace\ oneki: Magaza\Siparis\Para
Beş satırın dördü bulunulan ad alanına ya da takma adın gösterdiği yere gitti, yalnız ters eğik çizgiyle başlayan ad küresele çıktı. Bu, kodun başka bir ad alanına taşındığında ne olacağını da söyler: nitelenmemiş ve nitelenmiş adlar taşıyan dosyanın ad alanıyla birlikte kayar, tam nitelikli adlar yerinde kalır. Sınıflar için çözümleme burada biter. Fonksiyon ve sabitlerde bir adım daha vardır: bulunulan ad alanında bulunamayan nitelenmemiş ad küresele düşer. Sınırlayıcı ölçüm bu farkın sonucunu ölçüyor.
Gruplu use, use function ve use const
Bir dosyada birden çok use olabilir ve aynı ad alanından gelen adlar süslü parantezle tek satırda toplanabilir. Sınıflar için düz use yeterlidir; fonksiyon ve sabit için ayrı anahtar sözcük gerekir: use function ve use const. Düz use bir fonksiyonu ya da sabiti içe almaz.
<?php // kullan.php — gruplu use, use function ve use const namespace Magaza\Arac { const KDV = 20; function vergili(int $fiyat): int { return $fiyat + intdiv($fiyat * KDV, 100); } class Yuvarla {} class Para {} } namespace { define('GENEL', 'kuresel'); } namespace Uygulama { use Magaza\Arac\{Yuvarla, Para as Tutar}; use function Magaza\Arac\vergili; use const Magaza\Arac\KDV; echo vergili(100), ' ', KDV, "\n"; echo Yuvarla::class, ' ', Tutar::class, "\n"; echo GENEL, "\n"; }
120 20 Magaza\Arac\Yuvarla Magaza\Arac\Para kuresel
İlk satır iki içe almayı gösteriyor: vergili fonksiyonu ve KDV sabiti ad alanı yazılmadan çağrıldı. İkinci satırda gruplu use iki sınıfı tek ifadeyle aldı ve Para sınıfına grup içinde Tutar takma adı verildi. Üçüncü satır küresel bir sabitin, hiçbir use olmadan, ad alanı içinden okunabildiğini gösteriyor: sabitler de fonksiyonlar gibi küresele düşer. Sınıflar düşmez.
namespace Bildiriminin Yeri ve Dizinden Bağımsızlık
namespace bildirimi dosyanın ilk deyimi olmalıdır; yalnız declare ondan önce gelebilir. Önüne yazılan bir echo bile dosyayı derleme aşamasında durdurur. Aşağıdaki sürücü bunu ayrı bir PHP sürecinde dener ve yalnız çıkış kodunu basar. Aynı sürücü ikinci bir şeyi de gösterir: Magaza\Model ad alanındaki sınıf, adıyla hiçbir ilgisi olmayan derin/ic dizinindeki dosyada tanımlanıyor ve yine de çalışıyor.
<?php // yer.php — namespace ilk deyim olmali; dizin ad alanini belirlemez $dizin = 'olcum/yer/derin/ic'; mkdir($dizin, 0777, true); file_put_contents("$dizin/sinif.php", "<?php\nnamespace Magaza\\Model;\nclass Urun {}\n"); file_put_contents('olcum/yer/gec.php', "<?php\necho 'once';\nnamespace Magaza;\n"); require "$dizin/sinif.php"; $dosya = (new ReflectionClass('Magaza\Model\Urun'))->getFileName(); echo \Magaza\Model\Urun::class, ' ', basename($dosya), "\n"; exec('php -d display_errors=0 olcum/yer/gec.php 2>/dev/null', $satirlar, $kod); echo "namespace ilk degil: cikis=$kod\n";
Magaza\Model\Urun sinif.php namespace ilk degil: cikis=255
İlk satır dizinden bağımsızlığı kanıtlıyor: sınıfın adı Magaza\Model\Urun, dosyası ise olcum/yer/derin/ic/sinif.php. PHP ikisini eşleştirmez; ad alanı yalnız dosyanın içinde bildirilen bir önektir. İkinci satır namespace bildirimi echo’dan sonra geldiği için sürecin 255 ile bittiğini gösteriyor. Ad alanı ile dizin arasındaki eşleme dilin değil, bir sözleşmenin işidir. Bu sözleşmeyi bir sonraki ders kuruyor: otomatik yükleyici, ad alanı önekini dizinle eşleştirmeyi kendi üstüne alıyor.
Ölçüm Düzeneği
Ölçüm yayilim.php ile yapılır. Dosya olc() fonksiyonunu taşır; olc() aynı programı değişiklikten önce ve sonra kurar, ikisini koşturur ve sınıfı sırayla sorar: sonraki koşum 0 dışı çıkış verdiyse kıran, çıktı farklıysa örtük, düzenlenen dosya bağlantı listesindeyse bağlantıda, hiçbiri değilse yerinde. İki koşul vardır: önceki sürüm 0 ile çıkmalıdır ve kullanan main.php düzenlenmez. Kod blokları aşağıdaki derslerde yalnız anılır; bu dersin sürücüsü require_once ile onu çeker. Dosya çıktı bloğu olmadan, yalnız diske yazılır.
<?php // yayilim.php — bir degisikligin nereye ulastigini olcen yardimcilar function yaz(string $dizin, array $dosyalar): void { foreach ($dosyalar as $ad => $icerik) { if ($icerik === null) { continue; // null: dosya silindi } $yol = "$dizin/$ad"; if (!is_dir(dirname($yol))) { mkdir(dirname($yol), 0777, true); } file_put_contents($yol, $icerik); } } function oku(string $dizin): array { $dosyalar = []; $gezgin = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dizin, FilesystemIterator::SKIP_DOTS) ); foreach ($gezgin as $dosya) { $ad = substr($dosya->getPathname(), strlen($dizin) + 1); $dosyalar[$ad] = file_get_contents($dosya->getPathname()); } return $dosyalar; } function kostur(string $dizin): array { $komut = 'cd ' . escapeshellarg($dizin) . ' && php -d display_errors=0 main.php 2>/dev/null'; exec($komut, $satirlar, $kod); return [$kod, implode("\n", $satirlar)]; } function olc(string $ad, array $taban, array $degisiklik, array $baglanti = []): string { if (array_key_exists('main.php', $degisiklik)) { throw new LogicException("$ad: kullanan duzenlenmez"); } yaz("olcum/$ad/once", $taban); yaz("olcum/$ad/sonra", array_merge($taban, $degisiklik)); [$kodOnce, $ciktiOnce] = kostur("olcum/$ad/once"); [$kodSonra, $ciktiSonra] = kostur("olcum/$ad/sonra"); if ($kodOnce !== 0) { throw new LogicException("$ad: taban calismadi"); } $sinif = match (true) { $kodSonra !== 0 => 'kiran', $ciktiOnce !== $ciktiSonra => 'ortuk', array_intersect(array_keys($degisiklik), $baglanti) !== [] => 'baglantida', default => 'yerinde', }; printf( "%-18s %d dosya cikis=%-3d cikti %-6s -> %s\n", $ad, count($degisiklik), $kodSonra, $ciktiOnce === $ciktiSonra ? 'ayni' : 'farkli', $sinif ); return $sinif; }
Çakışma: Ad Alanı Olan ve Olmayan Kod
ad-alani gözleminde bizim.php, Uygulama ad alanında Kayit sınıfını tanımlıyor ve kaynak() yöntemi uygulama döndürüyor. Dış kod ise dis.php’ye kendi ad alanı olan Dis içinde aynı adlı bir Kayit ekliyor. Kullanan use Uygulama\Kayit; ile bizim sınıfı çağırıyor. Karşıtı kuresel-ad: iki sınıf da ad alanı olmadan, küresel ad alanında duruyor.
<?php // adalani.php — ayni adli sinif ad alaniyla ve ad alani olmadan require_once 'yayilim.php'; // 8. Dis kod, kendi ad alaninda bizim sinifimizla ayni adda bir sinif ekler $siniflar[] = olc('ad-alani', [ 'bizim.php' => <<<'PHP' <?php namespace Uygulama; class Kayit { public function kaynak(): string { return 'uygulama'; } } PHP, 'dis.php' => <<<'PHP' <?php namespace Dis; class Gunluk {} PHP, 'main.php' => <<<'PHP' <?php use Uygulama\Kayit; require 'bizim.php'; require 'dis.php'; echo (new Kayit())->kaynak(), "\n"; PHP, ], [ 'dis.php' => <<<'PHP' <?php namespace Dis; class Gunluk {} class Kayit { public function kaynak(): string { return 'dis'; } } PHP, ]); $siniflar[] = olc('kuresel-ad', [ 'bizim.php' => "<?php\nclass Kayit\n{\n public function kaynak(): string { return 'uygulama'; }\n}\n", 'dis.php' => "<?php\nclass Gunluk {}\n", 'main.php' => "<?php\nrequire 'bizim.php';\nrequire 'dis.php';\necho (new Kayit())->kaynak(), \"\\n\";\n", ], [ 'dis.php' => "<?php\nclass Gunluk {}\nclass Kayit\n{\n public function kaynak(): string { return 'dis'; }\n}\n", ]); echo kostur('olcum/ad-alani/once')[1], ' -> ', kostur('olcum/ad-alani/sonra')[1], "\n";
ad-alani 1 dosya cikis=0 cikti ayni -> yerinde kuresel-ad 1 dosya cikis=255 cikti farkli -> kiran uygulama -> uygulama
İlk satır ad-alani: dış kod aynı adı getirdi, bir dosya değişti, çıkış 0, çıktı uygulama iken uygulama kaldı. Bağlantı listesi boş verildiği için sınıf yerinde: değişiklik dis.php’nin içinde kaldı ve kullanana ulaşmadı. Ad alanı çakışmayı tanımın içine hapsetti.
İkinci satır karşıtı: aynı tek dosyalık ekleme ad alanı olmadan yapılınca program durdu, çıkış 255 verdi. Sınıf kıran. Aradaki fark dosya sayısında değil; iki durumda da bir dosya değişti. Fark, adın nerede yaşadığında.
Ad alanının işe yarayabilmesi için adın kendisinin de çakışmaması gerekir. Bu yüzden ad alanının ilk bölümü genellikle projeye ya da onu yazan ekibe ait bir ad olur ve alt bölümler dizin yapısını izler: Magaza\Model, Magaza\Servis. İki ayrı kitaplığın aynı ilk bölümü seçmesi, ad alanını koruyucu olmaktan çıkarır. Önek seçimi bir sözleşme sorunudur, dilin denetlediği bir şey değildir.
ST1. Bu ders ad alanını yalnız ad önekleri açısından ele alır. Dosyaların nasıl bulunup yüklendiği Otomatik Yükleme dersinindir; burada her sınıf require ile elle çekilir.
Sınırlayıcı Ölçüm
Ad alanı sınıfları korudu ama her şeyi korumaz. Nitelenmemiş bir fonksiyon çağrısında PHP önce çağrının bulunduğu ad alanına bakar, orada yoksa küresel fonksiyona düşer. Aşağıdaki sürücüde kullanan namespace Uygulama; içinde strlen çağırıyor. Dahil ettiği yardimci.php önce yalnız selam() taşıyor; değişiklikte aynı ad alanına bir strlen fonksiyonu da giriyor. İkinci ölçümde çağrı \strlen diye yazılmış.
<?php // fonksiyon.php — nitelenmemis fonksiyon cagrisi kuresele duser require_once 'yayilim.php'; $yardimci = <<<'PHP' <?php namespace Uygulama; function selam(): string { return 'merhaba'; } PHP; $ekli = $yardimci . "\n" . 'function strlen(string $metin): int { return 0; }' . "\n"; $main = "<?php\nnamespace Uygulama;\nrequire 'yardimci.php';\necho %s('kalem'), \"\\n\";\n"; olc('fonksiyon-dusmesi', ['yardimci.php' => $yardimci, 'main.php' => sprintf($main, 'strlen')], ['yardimci.php' => $ekli]); olc('kok-cagri', ['yardimci.php' => $yardimci, 'main.php' => sprintf($main, '\strlen')], ['yardimci.php' => $ekli]); echo kostur('olcum/fonksiyon-dusmesi/once')[1], ' -> ', kostur('olcum/fonksiyon-dusmesi/sonra')[1], "\n";
fonksiyon-dusmesi 1 dosya cikis=0 cikti farkli -> ortuk kok-cagri 1 dosya cikis=0 cikti ayni -> yerinde 5 -> 0
İlk satır fonksiyon-dusmesi: strlen('kalem') çağrısı Uygulama\strlen fonksiyonunu buldu ve 5 yerine 0 yazdı. Program durmadı, çıkış 0 kaldı; çıktı değişti, bu yüzden sınıf örtük. Kullanan hiç düzenlenmedi, değişiklik ona yine de ulaştı: nitelenmemiş çağrı hangi strlen’in kastedildiğini söylemiyor. İkinci satır kok-cagri: aynı çağrı \strlen diye yazılınca PHP ad alanına bakmadan küresel fonksiyonu seçti ve sınıf yerinde kaldı. Ters eğik çizgi küresel ad alanını açıkça seçer.
Ad alanı sınıf çakışmasına karşı korudu, ama fonksiyon çağrısını kendiliğinden korumadı. Aynı düzenek, çağrının nasıl yazıldığına göre ya korur ya korumaz. Ölçülen yine tanımın kendisi değil, kullananın neye dokunduğu.
Özet
- Bu dersin ölçü sınıfı yerinde.
ad-alanigözleminde dış kod aynı adlı sınıfı kendi ad alanında getirdi, bir dosya değişti, çıktıuygulamakaldı. - Karşıtı
kuresel-adkıran: ad alanı olmadan aynı ekleme programı 255 çıkışıyla durdurdu. İki durumda da bir dosya değişti; fark adın yaşadığı yerdi. namespaceadlara önek verir,usetam nitelikli adı kısa adla anılabilir yapar,asçakışan kısa adlara takma ad verir.- Sınırlayıcı ölçüm: nitelenmemiş fonksiyon çağrısı küresele düşer.
strlençağrısıUygulama\strlen’e yönelince5→0, sınıf örtük;\strlenile yerinde. - Ad alanı klasör değil ad önekidir: sınıfın hangi dosyada durduğunu söylemez. Nitelenmemiş, nitelenmiş ve tam nitelikli ad ayrı çözülür;
use functionveuse constfonksiyon ile sabiti içe alır;namespacebildirimi dosyanın ilk deyimidir.
Sonraki Adım
Bu derste sınıfı bulmak require ile elle yapıldı. Sınıf dosyası başka bir kök dizine taşınırsa kaç dosya değişir ve kullanan bundan etkilenir mi? Otomatik Yükleme dersi bu soruyu, yükleyiciyi tek bir bağlantı noktası olarak ölçerek yanıtlıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.