İçeriğe geç
academia.sh

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

Sonraki Adım

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

İ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