İçeriğe geç
academia.sh

Ders 10 / 12

Ortak Arayüz Standartları

PSR-3 gibi ortak arayüzler; kullananın arayüze bağlı olduğu ve gerçekleştirimin kurulum dosyasında değiştiği durum; arayüzün imzayı denetleyip anlamı denetlememesi; yer tutucuyu doldurmayan gerçekleştirimin çıktıyı sessizce değiştirmesi.

Yayın Güncelleme

Bu derste

  • Ortak bir arayüze bağlı kullananın gerçekleştirim değişince düzenlenmediğini ölçer
  • Değişikliğin kurulum dosyasındaki bağlantı noktasında karşılandığını gösterir
  • Arayüzün imzayı denetleyip anlamı denetlemediğini bir örtük ölçümle gösterir
İçindekiler

Otomatik Yükleme dersi sınıfın nereden geldiğini tek bir yerde topladı. Bu derste soru sınıfın içine dönüyor: kullanan bir ortak arayüze bağlıysa ve gerçekleştirim değişirse bu değişiklik nereye ulaşıyor? Aynı soru, aynı sürücü, aynı sınıf ölçüsü.

Ortak arayüz standardı, birçok kitaplığın ve uygulamanın aynı imzayı kabul etmesidir. PHP topluluğunun kayıt tutma için önerdiği PSR-3 bunun bilinen örneğidir: bir kaydediciyi iletiyle ve bir bağlam dizisiyle çağırırsınız, ileti içindeki yer tutucuları bağlam değerleriyle değiştirilir. Bu derste PSR-3’ün tamamı değil, aynı fikrin küçük bir hâli kullanılıyor: tek yöntemli Kaydedici arayüzü ve gibi yer tutucular.

Standart Öneri Ne Demektir

PSR, PHP topluluğunun yayımladığı standart önerilerin kısaltmasıdır ve numarayla anılır: PSR-3 kayıt tutmayı, PSR-4 otomatik yüklemeyi tanımlar. Adındaki “öneri” sözü gerçektir: dil bir önerinin uygulanıp uygulanmadığını denetlemez. Bir kitaplığın PSR-3’ü benimsemesi o kitaplığın kararıdır. Değeri uyguladıkça doğar: iki kitaplık aynı arayüzü benimserse birbirinden haberi olmadan aynı uygulamada çalışabilir.

Kim Neyi Bilir

Ortak arayüzün asıl işi, birbirini tanımayan iki kod parçasını buluşturmaktır. Aşağıdaki sürücüde bir kitaplık ödeme alır ve bunu kaydetmek ister; bir uygulama ise kaydın nereye gideceğine karar verir. Kitaplık yalnız Kaydedici arayüzünü bilir. Uygulama arayüzü bilir ve iki gerçekleştirim yazar. İkisi yalnız arayüzde buluşur: kitaplık dosyası hiçbir gerçekleştirimin adını içermez.

<?php
// kimbilir.php — kitaplik arayuzu bilir, uygulama gerceklestirimi secer
mkdir('olcum/kimbilir', 0777, true);
file_put_contents('olcum/kimbilir/kitaplik.php', <<<'PHP'
    <?php
    interface Kaydedici
    {
        public function kaydet(string $ileti): void;
    }
    class OdemeServisi
    {
        public function __construct(private Kaydedici $kaydedici) {}
        public function al(int $tutar): void { $this->kaydedici->kaydet("odeme alindi: $tutar"); }
    }
    PHP);
file_put_contents('olcum/kimbilir/uygulama.php', <<<'PHP'
    <?php
    class EkranKaydedici implements Kaydedici
    {
        public function kaydet(string $ileti): void { echo $ileti, "\n"; }
    }
    class BuyukHarfKaydedici implements Kaydedici
    {
        public function kaydet(string $ileti): void { echo strtoupper($ileti), "\n"; }
    }
    PHP);

$kitaplik = file_get_contents('olcum/kimbilir/kitaplik.php');
$anar = str_contains($kitaplik, 'Ekran') || str_contains($kitaplik, 'Buyuk');
echo 'kitaplik gerceklestirimi anar mi: ', $anar ? 'evet' : 'hayir', "\n";

require 'olcum/kimbilir/kitaplik.php';
require 'olcum/kimbilir/uygulama.php';
(new OdemeServisi(new EkranKaydedici()))->al(50);
(new OdemeServisi(new BuyukHarfKaydedici()))->al(50);
kitaplik gerceklestirimi anar mi: hayir
odeme alindi: 50
ODEME ALINDI: 50

İlk satır kitaplığın gerçekleştirimlerden habersiz olduğunu gösteriyor. Sonraki iki satırda aynı OdemeServisi iki farklı kaydediciyle çalıştı ve çıktısı ona göre değişti. Kitaplık tek satır değişmedi. Bu, önceki bölümün ölçtüğü şeyin nedenidir: bağlantı noktası kitaplığın içinde değil, gerçekleştirimi seçen kurulum.php benzeri yerdedir. Arayüz sınırı çizer, sınırın iki yanındaki kod birbirinin değişimine kapalı kalır.

Kaydedici Verilmediğinde

Kitaplık arayüze bağlıysa uygulamanın bir gerçekleştirim vermemesi de düşünülmelidir. Yaygın çözüm, hiçbir şey yapmayan bir gerçekleştirimi varsayılan olarak kullanmaktır; PSR-3 bunun için ayrıca bir boş kaydedici sınıfı da önerir. Bu sayede kitaplığın her çağrıda null denetlemesi yapması gerekmez ve uygulama kaydı istemiyorsa hiçbir şey bağlamaz.

<?php
// bos.php — kaydedici verilmezse kitaplik sessiz gerceklestirimle calisir
interface Kaydedici
{
    public function kaydet(string $ileti): void;
}
class SessizKaydedici implements Kaydedici
{
    public function kaydet(string $ileti): void {}
}
class EkranKaydedici implements Kaydedici
{
    public function kaydet(string $ileti): void { echo $ileti, "\n"; }
}
class RaporServisi
{
    private Kaydedici $kaydedici;

    public function __construct(?Kaydedici $kaydedici = null)
    {
        $this->kaydedici = $kaydedici ?? new SessizKaydedici();
    }

    public function uret(): int
    {
        $this->kaydedici->kaydet('rapor uretildi');
        return 3;
    }
}

$sessiz = (new RaporServisi())->uret();
echo "kaydedicisiz: $sessiz\n";
$ekranli = (new RaporServisi(new EkranKaydedici()))->uret();
echo "ekranli: $ekranli\n";
kaydedicisiz: 3
rapor uretildi
ekranli: 3

İki çağrı da aynı değeri döndürdü; yalnız ikincisi bir kayıt satırı yazdı. Kayıt tutmak kitaplığın asıl işine karışmıyor. Bu yüzden kayıt arayüzü çoğu zaman kitaplığın yanında değil, ayrı ve çok küçük bir pakette dağıtılır: kitaplıklar yalnız arayüze, uygulamalar da arayüzü gerçekleştiren pakete bağlanır. Bağımlılıklar arayüze doğru akar, birbirine doğru akmaz.

Arayüz Bir Sınırdır

Arayüzler dersinde (M08/K03) arayüzün söz değil sınır olduğu görüldü: PHP imzayı denetler, davranışı denetlemez. Bu derste o ayrım bir ölçüme bağlanıyor. Önce arayüzün iyi çalıştığı durum, sonra denetlemediği durum.

ST3. Ders Kaydedici arayüzünü PSR-3’ün tamamına değil, tek kaydet yöntemine indirgenmiş hâliyle kullanır. Seviye adları, istisna bağlamı ve Stringable iletiler ölçülmez.

PSR-3: Seviyeler ve Bağlam Dizisi

PSR-3 arayüzü her seviye için bir yöntem tanımlar ve genel bir log yöntemi ekler. Seviyeler en ağırdan en hafife doğru sekizdir: emergency, alert, critical, error, warning, notice, info, debug. İleti bir dizgidir ve içinde biçiminde yer tutucular taşıyabilir; ikinci argüman olan bağlam dizisi bu yer tutucuların değerlerini verir. Bağlamda exception anahtarı bir istisna nesnesi için ayrılmıştır. Standart hangi seviyenin yazılıp hangisinin atılacağını söylemez; bu bir gerçekleştirim kararıdır.

Aşağıdaki sürücü küçük bir gerçekleştirimi modeller: ağırlık sırası dizi sırasıdır ve bir eşik seviyesi eşiğin altındaki, yani daha hafif kayıtları atar. Aynı dört kayıt iki ayrı eşikle yazılır.

<?php
// seviyeler.php — sekiz seviye, esik ve baglam dizisi
$seviyeler = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug'];

function yaz(array $seviyeler, string $esik, string $seviye, string $ileti, array $baglam): void
{
    if (array_search($seviye, $seviyeler) > array_search($esik, $seviyeler)) {
        return;
    }
    $yerler = [];
    foreach ($baglam as $anahtar => $deger) {
        $yerler['{' . $anahtar . '}'] = $deger;
    }
    echo str_pad($seviye, 10), strtr($ileti, $yerler), "\n";
}

$kayitlar = [
    ['critical', 'veritabani {sunucu} yanit vermiyor', ['sunucu' => 'db1']],
    ['warning', 'siparis {no} gecikti', ['no' => 42]],
    ['info', 'kullanici {kisi} giris yapti', ['kisi' => 'ayse']],
    ['debug', 'sorgu suresi {sure} ms', ['sure' => 12]],
];
foreach (['warning', 'debug'] as $esik) {
    echo "-- esik: $esik\n";
    foreach ($kayitlar as [$seviye, $ileti, $baglam]) {
        yaz($seviyeler, $esik, $seviye, $ileti, $baglam);
    }
}
-- esik: warning
critical  veritabani db1 yanit vermiyor
warning   siparis 42 gecikti
-- esik: debug
critical  veritabani db1 yanit vermiyor
warning   siparis 42 gecikti
info      kullanici ayse giris yapti
debug     sorgu suresi 12 ms

warning eşiğinde yalnız critical ve warning kayıtları yazıldı; info ve debug atıldı. debug eşiğinde dördü de yazıldı. İletiler her iki durumda yer tutucuları doldurarak çıktı: yerine db1, yerine 42. İletiyi bir kez, sabit bir kalıpla yazıp değişken kısmı bağlamda taşımanın yararı, aynı kalıbın farklı gerçekleştirimlerce işlenebilmesidir: biri düz metin, biri yapılandırılmış kayıt üretebilir.

Ölçüm Düzeneği

yayilim.php önceki derslerle aynıdı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;
}

Gerçekleştirimi Değiştirmek: Bağlantıda

Gözlem ortak-arayuz: Siparis sınıfı yalnız Kaydedici arayüzünü tanır ve ona bir sipariş iletisi yazdırır. Hangi gerçekleştirimin kullanılacağını kurulum.php seçer; main.php onu require ile alıp Siparis’e verir. Tabanda EkranKaydedici iletiyi doğrudan ekrana basıyor. Değişiklik tek dosya: kurulum.php artık iletiyi bir tamponda biriktiren TamponKaydedici döndürüyor ve program sonunda hepsini basıyor. Dördüncü argüman bağlantı listesidir ve kurulum.php’yi bağlantı noktası olarak adlandırır.

Siparis yer tutucuyu kendisi doldurmaz. yer tutucusunu dolduran yerlestir() fonksiyonu arayüzle aynı dosyada durur ve iki gerçekleştirim de onu çağırır; Siparis yalnız kaydet yönteminu bilir.

<?php
// kurulum.php degisimi — gerceklestirim kurulum dosyasinda degisir
require_once 'yayilim.php';

// 10. Ortak bir kayit arayuzunun gerceklestirimi degistirilir
$siniflar[] = olc('ortak-arayuz', [
    'kaydedici.php' => <<<'PHP'
        <?php
        interface Kaydedici
        {
            public function kaydet(string $ileti, array $baglam = []): void;
        }
        function yerlestir(string $ileti, array $baglam): string
        {
            $yerler = [];
            foreach ($baglam as $anahtar => $deger) {
                $yerler['{' . $anahtar . '}'] = $deger;
            }
            return strtr($ileti, $yerler);
        }
        class EkranKaydedici implements Kaydedici
        {
            public function kaydet(string $ileti, array $baglam = []): void
            {
                echo yerlestir($ileti, $baglam), "\n";
            }
        }
        PHP,
    'kurulum.php' => <<<'PHP'
        <?php
        return new EkranKaydedici();
        PHP,
    'main.php' => <<<'PHP'
        <?php
        require 'kaydedici.php';
        class Siparis
        {
            public function __construct(private Kaydedici $kaydedici) {}
            public function ver(int $no): void { $this->kaydedici->kaydet('siparis {no} alindi', ['no' => $no]); }
        }
        (new Siparis(require 'kurulum.php'))->ver(7);
        PHP,
], [
    'kurulum.php' => <<<'PHP'
        <?php
        class TamponKaydedici implements Kaydedici
        {
            private array $satirlar = [];
            public function kaydet(string $ileti, array $baglam = []): void
            {
                $this->satirlar[] = yerlestir($ileti, $baglam);
            }
            public function __destruct() { echo implode("\n", $this->satirlar), "\n"; }
        }
        return new TamponKaydedici();
        PHP,
], ['kurulum.php']);

echo kostur('olcum/ortak-arayuz/once')[1], "\n";
echo kostur('olcum/ortak-arayuz/sonra')[1], "\n";
$adlar = array_keys(oku('olcum/ortak-arayuz/sonra'));
sort($adlar);
echo implode(', ', $adlar), "\n";
ortak-arayuz       1 dosya  cikis=0   cikti ayni   -> baglantida
siparis 7 alindi
siparis 7 alindi
kaydedici.php, kurulum.php, main.php

İlk satır ölçümün sonucu: bir dosya değişti, çıkış 0 kaldı, çıktı aynı kaldı ve değişen dosya bağlantı listesinde olduğu için sınıf bağlantıda. Sonraki iki satır programın iki sürümünün çıktısıdır: ekrana yazan gerçekleştirim de, tamponlayan gerçekleştirim de aynı siparis 7 alindi satırını verdi. Son satır olcum/ortak-arayuz/sonra dizinindeki dosyaların sıralı listesidir: main.php listede var ve ölçüm sırasında hiç düzenlenmedi.

Sonuç şu: kullanan arayüze bağlı olduğu için gerçekleştirim değişince Siparis ve main.php yerinde kaldı. Değişiklik kurulum.php noktasında karşılandı. Ekrana basan gerçekleştirimden tamponlayana geçmek tek dosyalık bir iş oldu ve çıktı aynı kaldı.

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

Arayüz imzayı denetler, anlamı denetlemez. Aynı tabana bir gerçekleştirim daha uygulanıyor: kaydet yöntemi arayüzü imza olarak eksiksiz karşılar, ama bağlamı kullanmaz ve iletiyi yer tutucusu doldurulmadan basar. Sürücü ayrı bir ölçümdür ve olcum/yer-tutucusuz altında çalışır; taban önceki ölçümün tabanıdır.

<?php
// yer-tutucusuz.php — imzayi karsilayan ama anlami tasimayan gerceklestirim
require_once 'yayilim.php';

$taban = oku('olcum/ortak-arayuz/once');
olc('yer-tutucusuz', $taban, [
    'kurulum.php' => <<<'PHP'
        <?php
        class DuzKaydedici implements Kaydedici
        {
            public function kaydet(string $ileti, array $baglam = []): void
            {
                echo $ileti, "\n";
            }
        }
        return new DuzKaydedici();
        PHP,
], ['kurulum.php']);

echo kostur('olcum/yer-tutucusuz/once')[1], ' -> ', kostur('olcum/yer-tutucusuz/sonra')[1], "\n";
yer-tutucusuz      1 dosya  cikis=0   cikti farkli -> ortuk
siparis 7 alindi -> siparis {no} alindi

Sınıf örtük oldu: bir dosya değişti, çıkış 0 kaldı, ama çıktı siparis 7 alindi iken siparis {no} alindi oldu. DuzKaydedici arayüzü imza olarak eksiksiz karşılıyor; PHP bir şey söylemedi, program çökmedi. Değişen dosya bağlantı listesinde olsa da çıktı değiştiği için sınıf örtük sayıldı, çünkü sıralamada çıktı farkı bağlantıdan önce gelir. Arayüz sınırı korudu, anlamı korumadı. Yer tutucuyu doldurma sözü yalnız belgede ve ortak standardın metninde duruyor; onu denetleyen bir ölçüm ya da test gerekir.

Özet

  • Bu dersin ölçü sınıfı bağlantıda. ortak-arayuz gözleminde gerçekleştirim kurulum.php dosyasında değişti, Siparis ve main.php düzenlenmedi, çıktı siparis 7 alindi kaldı.
  • Ortak arayüz (PSR-3 gibi) kullananı gerçekleştirimden ayırır; değişiklik kurulum dosyasındaki bağlantı noktasında karşılanır.
  • Sınırlayıcı ölçüm yer-tutucusuz örtük: imzayı karşılayan ama yer tutucusunu doldurmayan gerçekleştirim çıkış 0 verdi, çıktı siparis {no} alindi oldu.
  • Kitaplık yalnız arayüzü bilir, uygulama gerçekleştirimi seçer; PSR-3 sekiz seviye ve yer tutuculu bir bağlam dizisi tanımlar, hangi seviyenin yazılacağına gerçekleştirim karar verir.
  • Arayüz imzayı denetler, anlamı denetlemez; anlam için ayrı ölçüm gerekir.

Sonraki Adım

Kurulum dosyası gerçekleştirimi seçti. Bir bağımlılık ise başka birinin yayımladığı kodsa ve o kod güncellenirse kaç dosya düzenlenir? Bağımlılık Yöneticisi 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