İçeriğe geç
academia.sh

Ders 03 / 21

Yönlendirme

Rota kalıpları, parametreler, gruplar ve adlandırılmış rotalar; kayıt sırasında ilk eşleşen rotanın kazanması ve bir rotanın başka bir rotanın gölgesinde bağlanmadan kalması.

Yayın Güncelleme

Bu derste

  • Rota kalıbındaki parametreyi, grup önekini ve adlandırılmış rotadan URL üretimini bir yönlendirici modelinde okur
  • İlk eşleşen rotanın kazanması nedeniyle geniş bir kalıbın sonradan kaydedilen dar kalıbı gölgelediğini ölçer
  • Kayıt sırası değişince gölgelenen rotanın koştu sınıfına geçtiğini karşılaştırır
  • Gölgeyi doğuran etkenin sıra değil kalıbın genişliği olduğunu desen kısıtıyla ölçer
İçindekiler

Önceki ders isteğin hangi eyleme düştüğünü hazır verdi. Bu ders o adımı açar. Çerçeve bir isteği karşılamak için kayıtlı rotalara tek tek bakar ve bu bakışın kuralı yalnızca bir cümleden oluşur. İki kalıp aynı yola uyduğunda neyin olacağı, o cümlenin nasıl yazıldığına bağlıdır.

Rota Kalıbı ve Parametre

Bir rota (route) dört şeyi bir araya getirir: bir HTTP yöntemi, bir yol kalıbı, bir parça adı ve bir eylem. Kalıp, yolun sabit kısımlarını ve süslü parantezle yazılan parametreleri taşır. /yazilar/{id} kalıbı /yazilar/7 yoluna uyar ve eylem id parametresini 7 olarak alır. Çerçeve kalıbı bir düzenli ifadeye çevirir: her parametre, eğik çizgi içermeyen bir ya da daha çok karakterle eşleşen bir gruba dönüşür.

Aynı kalıbı birçok rotada yinelememek için rotalar gruplanır. Grup ortak bir önek taşır: /yazilar grubu içindeki /{id} kalıbı gerçekte /yazilar/{id} olur. Gerçek çerçevelerde grup ayrıca bir ara katman listesini her üyesine ekleyebilir; ara katmanların kendisi beşinci dersin konusudur, burada yalnızca önek modellenir. Üçüncü araç adlandırılmış rotadır: rota bir adla kaydedilir ve URL, kalıptan o adla üretilir. Böylece bir kalıp değişince adresin yazıldığı her yeri aramak gerekmez.

yonlendirici.php bu üç aracı ve eşleştirme kuralını tek sınıfta toplar. Bu dosya KAYNAK olarak verilir ve sonraki iki ölçüm onu require_once ile yükler.

<?php
// yonlendirici.php — kayıt sırasıyla eşleşen küçük bir yönlendirici
declare(strict_types=1);
require_once 'kostu.php';

final class Yanit {
    public function __construct(public int $durum, public string $govde = '') {}
}

final class Yonlendirici {
    private array $rotalar = [];
    private array $adlar = [];
    private array $onekler = [];
    public function grup(string $onek, Closure $kayit): void {
        $this->onekler[] = $onek;
        $kayit($this);
        array_pop($this->onekler);
    }
    public function rota(string $yontem, string $kalip, string $parca, Closure $eylem, ?string $ad = null): void {
        $kalip = implode('', $this->onekler) . $kalip;
        $this->rotalar[] = [$yontem, $kalip, $parca, $eylem];
        if ($ad !== null) $this->adlar[$ad] = $kalip;
    }
    public function url(string $ad, array $parametre = []): string {
        return preg_replace_callback('#\{(\w+)(?::[^}]+)?\}#',
            fn(array $m) => (string) $parametre[$m[1]], $this->adlar[$ad]);
    }
    public function isle(string $yontem, string $yol): Yanit {
        Gunluk::yeniIstek();
        foreach ($this->rotalar as [$y, $kalip, $parca, $eylem]) {     // ilk eşleşen kazanır
            $desen = '#^' . preg_replace_callback('#\{(\w+)(?::([^}]+))?\}#',
                fn(array $m) => '(?P<' . $m[1] . '>' . ($m[2] ?? '[^/]+') . ')', $kalip) . '$#';
            if ($y === $yontem && preg_match($desen, $yol, $m)) {
                Gunluk::yola($parca);
                Gunluk::kostu($parca);
                return $eylem(array_filter($m, 'is_string', ARRAY_FILTER_USE_KEY));
            }
        }
        return new Yanit(404);
    }
}

Çekirdek kostu.php her derste aynı yirmi iki satırla yeniden verilir. Yönlendirici ona iki çağrıyla bağlanır: eşleşen rotanın adını hem Gunluk::yola() hem Gunluk::kostu() ile bildirir.

<?php
// kostu.php — beklenen bir parça bu istekte koştu mu?
declare(strict_types=1);

final class Gunluk {
    public static array $yol = [];         // isteğin yoluna konan parçalar
    public static array $kostu = [];       // istek içinde çağrılanlar
    public static array $ertelenen = [];   // isteğin dışına bırakılanlar
    public static function yeniIstek(): void { self::$yol = self::$kostu = self::$ertelenen = []; }
    public static function yola(string $parca): void { self::$yol[] = $parca; }
    public static function kostu(string $parca): void { self::$kostu[] = $parca; }
    public static function ertele(string $parca): void { self::$ertelenen[] = $parca; }
}

function sinifla(string $parca): string {
    return match (true) {
        in_array($parca, Gunluk::$kostu, true) => 'koştu',
        in_array($parca, Gunluk::$ertelenen, true) => 'ertelendi',
        in_array($parca, Gunluk::$yol, true) => 'atlandı',
        default => 'bağlanmadı',
    };
}

ÇT9. isle() içindeki foreach rotaları kaydedildikleri sırayla dener ve ilk eşleşende return eder. Bu satır bütün dersin kuralıdır: ilk eşleşen kazanır. Model, hangi kalıbın daha özel olduğunu bir sıralamayla değerlendirmez; “sabit kalıp parametreli kalıptan önce gelir” diye bir kural da yoktur. Sıra, geliştiricinin rota() çağrılarını yazdığı sıradır. Gerçek çerçevelerin bazıları bu kuralı kendi ek sıralamalarıyla yumuşatır; burada ölçülen model yumuşatmaz ve bu bilinçli bir sadeleştirmedir.

Gruplar ve Adlandırılmış Rotalar

grup() bir önek listesiyle çalışır: önek eklenir, grubun içindeki rota() çağrıları çalışır ve önek geri alınır. Önek, rota() içinde kalıbın başına eklendiği için eşleştirici gruptan habersizdir; yalnızca tam kalıba bakar. Gruplar iç içe yazılabilir ve her kademe kendi önekini ekler. Bu tasarımın bir sonucu vardır: grup yalnızca yazım kolaylığıdır, eşleşmeye yeni bir kural eklemez. /yazilar grubunun içinde /{id} ile /yeni kaydedildiğinde, bu iki kalıp gruptan bağımsız yazılmış /yazilar/{id} ve /yazilar/yeni ile aynı şekilde davranır.

url() ise tersini yapar. Kayıtlı kalıbı alır, her yerine verilen değeri koyar ve sonucu döndürür. Rota adı kalıba bağlıdır, kalıp ise önekle birlikte saklanmıştır; bu yüzden grubun önekini değiştirmek, adı kullanan her kodun ürettiği adresi birlikte değiştirir. Adlandırılmış rotanın bu dersteki işi bu kadardır: adres üretimini kalıpta tek bir yere bağlar. Hangi rotanın bir isteğe uyacağı hakkında hiçbir söz taşımaz.

İlk Eşleşen Kazanır

yonlendirme.php bir /yazilar grubu kurar ve grubun içine iki rota koyar: /{id} kalıbıyla goster ve /yeni kalıbıyla form. kur() iki kayıt sırası üretir. İlk sırada goster önce, form sonra kaydedilir. İkinci sırada tersi olur. Çıktıdaki sonra ve önce sözcükleri form’un goster’den sonra mı önce mi kaydedildiğini söyler. İki yönlendirici de aynı yola, /yazilar/yeni adresine sorulur; geliştiricinin beklediği parça form’dur. Grup içindeki goster rotası ayrıca yazilar.goster adını taşır ve son satır bu addan id için 7 değeriyle URL üretir.

<?php
// yonlendirme.php — kayıt sırası hangi rotanın bağlanacağını belirler
declare(strict_types=1);
require_once 'yonlendirici.php';

function kur(bool $yeniOnce): Yonlendirici {
    $y = new Yonlendirici();
    $y->grup('/yazilar', function (Yonlendirici $y) use ($yeniOnce) {
        $form = fn() => new Yanit(200, 'boş form');
        $goster = fn(array $p) => new Yanit(200, "yazı {$p['id']}");
        if ($yeniOnce) $y->rota('GET', '/yeni', 'form', $form);
        $y->rota('GET', '/{id}', 'goster', $goster, ad: 'yazilar.goster');
        if (!$yeniOnce) $y->rota('GET', '/yeni', 'form', $form);
    });
    return $y;
}

foreach (['sonra' => false, 'önce' => true] as $ad => $yeniOnce) {
    $y = kur($yeniOnce);
    $yanit = $y->isle('GET', '/yazilar/yeni');
    printf("yeni %-5s %d %-9s form %s\n", $ad, $yanit->durum, $yanit->govde, sinifla('form'));
}
echo $y->url('yazilar.goster', ['id' => 7]), "\n";
yeni sonra 200 yazı yeni form bağlanmadı
yeni önce 200 boş form form koştu
/yazilar/7

ÇT10. Aynı yol, aynı iki rota, tek fark kayıt sırası. goster önce kaydedildiğinde /yazilar/yeni ona uydu: parametresi yeni değerini aldı, yanıt 200 oldu ve gövdesi yazı yeni çıktı. form eylemi hiç çağrılmadı; Gunluk onu isteğin yoluna koymadı ve karar sırası $kostu, $ertelenen, $yol listelerinde de bulamayınca bağlanmadı dedi. form önce kaydedildiğinde aynı yol ona uydu ve boş form yanıtı geldi: form koştu. Yanıt iki durumda da 200’dür ve bir hata ya da uyarı çıkmaz; iki durumu yalnızca gövde ve Gunluk ayırır.

Sorun bu yüzden sinsidir. Gölgelenen rota kayıtlıdır ve rota tablosunu okuyan biri onu görür. İstek ise ona hiç ulaşmaz ve çerçeve bunu bildirmez; yazı yeni yanıtı da bir hata gibi görünmez, çünkü yeni geçerli bir id değeri gibi okunabilir. Geliştirici bir parçanın koşmadığını yanıtın bir yerinden değil, beklediği parçayı adlandırıp sorgulayarak anlar.

Üretilen URL de modelin parçasıdır: son satırdaki /yazilar/7, kalıptan türetilmiştir. Adresi elle yazmak yerine adı kullanan kod, kalıp değiştiğinde kırılmaz. Ama adlandırılmış rota yalnızca URL üretir; hangi rotanın bir isteğe uyacağını belirleyen şey yine kayıt sırasıdır.

Bu dersin ölçü sınıfı bağlanmadı’dır. Karar sırasının sonuncusu olan bu sınıf, ilk derste en tehlikeli sınıf olarak adlandırılmıştı: atlanan parça bir durum koduyla, ertelenen parça işçide ya da yanıttan sonra görünür, bağlanmayan parça ise hiçbir yerde.

Rota Tablosunu Okumak

Gölgelenen bir rotayı bulmanın tek güvenilir yolu, o rotanın adını önceden yazıp isteğe sormaktır. Rota tablosunu gözle okuyan biri form rotasını kayıtlı görür ve işi bitmiş sanır. Gunluk ise tabloyu değil isteği okur: bu istekte form yoldaydı mı, çağrıldı mı. İki soru ayrıdır ve yalnızca ikincisi geliştiricinin gerçekte merak ettiği şeydir.

Bu okuma alışkanlığının iki pratik sonucu vardır. Birincisi, yeni bir rota eklemek var olan bir rotayı gölgeleyebilir: sonradan eklenen geniş bir kalıp, kendinden sonra kaydedilmiş bütün dar kalıpları örter. Ekleyenin görmediği bu etki, aynı isteği önce ve sonra ölçmekle yakalanır. İkincisi, bir rotanın koşmadığını anlayan geliştirici ilk olarak eylemin içine bakmaya meyleder; oysa eylem hiç çağrılmamıştır ve içinde aranacak bir şey yoktur. Doğru ilk soru, isteğin hangi rotaya düştüğüdür.

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

Gölgeyi sıra mı doğuruyor? Önceki ölçüm sırayı değiştirince sonucun değiştiğini gösterdi; ama bu, sıranın tek etken olduğunu kanıtlamaz. Sınırlayıcı ölçüm sırayı değiştirmez. goster yine önce kaydedilir, yalnız kalıbı {id:\d+} olarak daraltılır: parametre yalnızca rakamlarla eşleşir. Aynı iki yola sorulur: /yazilar/yeni ve /yazilar/7.

<?php
// kisit.php — gölgeyi sıra değil kalıbın genişliği doğurur
declare(strict_types=1);
require_once 'yonlendirici.php';

$y = new Yonlendirici();
$y->grup('/yazilar', function (Yonlendirici $y) {
    $y->rota('GET', '/{id:\d+}', 'goster', fn(array $p) => new Yanit(200, "yazı {$p['id']}"));
    $y->rota('GET', '/yeni', 'form', fn() => new Yanit(200, 'boş form'));
});

foreach (['/yazilar/yeni' => 'form', '/yazilar/7' => 'goster'] as $yol => $parca) {
    $yanit = $y->isle('GET', $yol);
    printf("%-14s %d %-9s %s %s\n", $yol, $yanit->durum, $yanit->govde, $parca, sinifla($parca));
}
/yazilar/yeni  200 boş form form koştu
/yazilar/7     200 yazı 7   goster koştu

ÇT11. Sıra aynı kaldı ve form bu kez koştu. goster kalıbı artık yeni metniyle eşleşmediği için form rotasına sıra geldi; /yazilar/7 ise yine goster’e düştü. Gölgeyi doğuran şey sıra değil, geniş kalıptır: [^/]+ her metni kabul ettiği için yeni sözcüğü de ona uyar. Sıra yalnızca iki kalıp birbirini kapsadığında önem kazanır. Kalıp daraltılırsa sıra önemini yitirir.

Bu iki ölçümün birlikte söylediği şudur: bağlanmadığını gördüğünüz bir rota için iki yoldan biri düzeltir ve ikisi aynı sonucu verir. Dar kalıbı geniş kalıptan önce kaydetmek ya da geniş kalıbı daraltmak. İkincisi daha sağlamdır, çünkü sıra değişse bile sonuç değişmez. Birincisi geliştirici dikkatine dayanır ve bir sonraki rota eklendiğinde yeniden bozulabilir.

Bir sınır daha vardır: bu model, varsayılan olarak eğik çizgi içermeyen tek bir yol parçasını eşleştirir; joker ve isteğe bağlı parametre desteklemez. Gerçek çerçevelerin eşleştiricileri bunları ve alan adı kısıtı gibi daha fazlasını destekler; burada ölçülen yalnızca ilk eşleşen kazanır kuralının sonucudur.

Özet

  • Rota yöntem, kalıp, parça adı ve eylemden oluşur; grup ortak bir önek ekler, adlandırılmış rota ise kalıptan URL üretir.
  • Modelde ilk eşleşen kazanır ve özgüllük sıralaması yoktur; sıra rotaların kaydedildiği sıradır.
  • /yazilar/{id} önce kaydedildiğinde /yazilar/yeni ona uydu, yanıt 200 ve yazı yeni oldu, form bağlanmadı; sıra değişince form koştu.
  • Parametreyi {id:\d+} ile daraltmak, sırayı değiştirmeden form rotasını koştu sınıfına getirdi; gölgeyi sıra değil kalıbın genişliği doğurur.
  • Bu dersin ölçü sınıfı bağlanmadı’dır: gölgelenen rota hata vermez, yanıt başarılı görünür ve rota yalnızca adlandırılıp sorgulanınca bulunur.

Sonraki Adım

Eşleşen rota artık belli: goster ya da form koştu. Ama rota yalnızca bir eylemi gösterir; eylemin hangi sınıfta durduğu, o sınıfın bağımlılıklarını kimin kurduğu ve yedi eylemli bir kaynağın rotalara nasıl yayıldığı henüz görünmedi. Sonraki ders, Denetleyiciler, rotanın gösterdiği sınıfın nasıl kurulduğunu ve eylemin gerçekten çağrılıp çağrılmadığını soruyor.

İ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