Ders 04 / 21
Denetleyiciler
Denetleyici sınıfı ve eylem yöntemi, kaynak denetleyicisinin yedi eylemi rotalara açması ve tek eylemli denetleyici; yapıcı bağımlılıklarının nesne kabı tarafından çözülmesi.
Yayın Güncelleme
Bu derste
- Rotanın bir denetleyici sınıfına ve eylem yöntemine bağlanmasını ve yapıcı bağımlılığının nesne kabıyla çözülmesini okur
- Kaynak denetleyicisinin yedi eylemini yedi rotaya açan kuralı ve tek eylemli denetleyiciyi karşılaştırır
- Eylemin koştu sınıfına düşmesi için denetleyicinin kurulmasının gerektiğini ölçer
- Tanımsız eylemin rotasının kaydedilmediği durumda isteğin 404 ya da başka bir eyleme düştüğünü ölçer
İçindekiler
Önceki ders isteği bir rotaya düşürdü, ama rotanın gösterdiği şeyi bir fonksiyon olarak varsaydı. Gerçek uygulamalarda eylem bir sınıfın yöntemidir ve sınıf başka nesnelere dayanır. Bu ders rotadan eyleme giden yolu açar: sınıfı kim kurar, bağımlılığını kim sağlar ve ancak o zaman eylem çağrılır.
Denetleyici Sınıfı ve Eylem
Denetleyici (controller), ilgili eylemleri bir sınıfta toplar. Eylem, o sınıfın bir yöntemidir ve bir isteği karşılar: parametreleri alır, bir iş yapar ve bir yanıt döndürür. Rota artık bir fonksiyon değil, bir sınıf adı ve bir yöntem adı taşır. Çerçeve isteği bu rotaya eşledikten sonra üç şey yapar: sınıfı kurar, yöntemi çağırır ve yöntemin döndürdüğü yanıtı geri verir.
Sınıfın kurulması kolay bir adım değildir, çünkü denetleyici başka nesnelere dayanabilir. Bir yazı denetleyicisi yazıları saklayan bir depoya ihtiyaç duyar ve bunu yapıcısında bir parametre olarak ister. Rotanın bu depoyu sağlaması beklenemez; kuran taraf çerçevedir. Çerçeve bunu nesne kabına (object container) bırakır: ayrıntısı sonraki derslerde açılan, sınıf adından nesne kuran fabrikaya. Burada bilinmesi gereken tek şey, kabın yapıcı parametrelerinin tipine bakıp onları da kurmasıdır. Bağımlılık Enjeksiyonu dersinde ölçülen şey bu çözümlemenin yapısıydı; burada yalnız denetleyicinin kurulup eylemin çağrılması önemlidir.
denetleyici.php bu zinciri tek dosyada kurar. Yonlendirici rotaya bir sınıf ve yöntem adı kaydeder, isle() eşleşen rotanın denetleyicisini kaptan alır ve eylemi çağırır. Kap, yalnız sınıf tipli yapıcı parametresini çözer; sınıf tipi olmayan bir parametre bu modelin kapsamı dışındadır. Dosya KAYNAK olarak verilir, ölçümler onu yükler.
<?php // denetleyici.php — rotayı bir denetleyici sınıfına ve eylem yöntemine bağlar declare(strict_types=1); require_once 'kostu.php'; final class Yanit { public function __construct(public int $durum, public string $govde = '') {} } final class NesneKabi { public function al(string $ad): object { $bagimliliklar = []; foreach ((new ReflectionClass($ad))->getConstructor()?->getParameters() ?? [] as $p) { $bagimliliklar[] = $this->al($p->getType()->getName()); } return new $ad(...$bagimliliklar); } } final class Yonlendirici { private array $rotalar = []; public function __construct(private NesneKabi $kap) {} public function rotalar(): array { return $this->rotalar; } public function rota(string $yontem, string $kalip, string $parca, string $sinif, string $eylem = '__invoke'): void { $this->rotalar[] = [$yontem, $kalip, $parca, $sinif, $eylem]; } public function kaynak(string $ad, string $sinif): void { $yedi = [ ['GET', "/$ad", 'liste'], ['GET', "/$ad/olustur", 'olustur'], ['POST', "/$ad", 'kaydet'], ['GET', "/$ad/{id}", 'goster'], ['GET', "/$ad/{id}/duzenle", 'duzenle'], ['PUT', "/$ad/{id}", 'guncelle'], ['DELETE', "/$ad/{id}", 'sil'], ]; foreach ($yedi as [$yontem, $kalip, $eylem]) { if (method_exists($sinif, $eylem)) $this->rota($yontem, $kalip, $eylem, $sinif, $eylem); } } public function isle(string $yontem, string $yol): Yanit { Gunluk::yeniIstek(); foreach ($this->rotalar as [$y, $kalip, $parca, $sinif, $eylem]) { // ilk eşleşen kazanır $desen = '#^' . preg_replace('#\{(\w+)\}#', '(?P<$1>[^/]+)', $kalip) . '$#'; if ($y === $yontem && preg_match($desen, $yol, $m)) { Gunluk::yola($parca); $denetleyici = $this->kap->al($sinif); // yapıcı bağımlılıkları çözülür Gunluk::kostu($parca); return $denetleyici->$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.
<?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ı', }; }
ÇT12. isle() iki ayrı bildirim yapar ve ikisinin arasında denetleyici kurulur. Gunluk::yola() rota eşleştiğinde çağrılır; Gunluk::kostu() ise $this->kap->al($sinif) satırından sonra, yani denetleyici kurulduktan sonra çağrılır. Kurulum bir istisna atsaydı, parça yola konmuş ama koştu listesine girmemiş olurdu; sinifla() kuralına göre o parça atlandı sayılırdı. Bu sıra bilinçlidir: koştu, eylemin çağrılacağı noktaya gelindiğini söyler; çağrıdan önceki her adım yoldadır, ama henüz koşmamıştır.
Kaynak Denetleyicisi ve Tek Eylemli Denetleyici
Çoğu uygulamada bir varlık aynı yedi eylem etrafında döner: listele, oluşturma formunu göster, kaydet, göster, düzenleme formunu göster, güncelle ve sil. Her biri için bir rota yazmak yinelemedir. Kaynak denetleyicisi (resource controller) bu yedi eylemi tek bir kayıtla rotalara açar. kaynak() yedi satırlık bir tablo taşır ve tanımlı her eylem için bir rota kaydeder.
| Yöntem | Kalıp | Eylem |
|---|---|---|
| GET | /yazilar | liste |
| GET | /yazilar/olustur | olustur |
| POST | /yazilar | kaydet |
| GET | /yazilar/{id} | goster |
| GET | /yazilar/{id}/duzenle | duzenle |
| PUT | /yazilar/{id} | guncelle |
| DELETE | /yazilar/{id} | sil |
Tablodaki sıra rastgele değildir. /yazilar/olustur kalıbı /yazilar/{id} kalıbından önce gelir, çünkü önceki derste ölçüldüğü gibi ilk eşleşen kazanır: sıra ters olsaydı olustur sözcüğü bir id olarak okunurdu. Kaynak denetleyicisi yedi rotayı bu sırayla kaydeder ve dar kalıbı geniş kalıptan önce koyar.
Her eylem bir yöntemdir, ama bazı eylemler tek başına bir sınıfı hak eder. Tek eylemli denetleyici yalnızca __invoke yöntemini taşır ve rota yalnızca sınıf adını verir. Yayınlama gibi, bir varlığın standart yedi eylemine girmeyen bir işlem için uygundur. Aşağıdaki dosya hem kaynak denetleyicisini hem tek eylemli denetleyiciyi kaydeder, yedi rotayı ve bir ek rotayı yazdırır, sonra iki isteği sınıflar.
<?php // denetleyiciler.php — kaynak denetleyicisi yedi eylemi yedi rotaya açar declare(strict_types=1); require_once 'denetleyici.php'; final class Depo {} final class YaziDenetleyicisi { public function __construct(private Depo $depo) {} public function liste(): Yanit { return new Yanit(200, 'liste'); } public function olustur(): Yanit { return new Yanit(200, 'form'); } public function kaydet(): Yanit { return new Yanit(201, 'kaydedildi'); } public function goster(array $p): Yanit { return new Yanit(200, 'yazı ' . $p['id'] . ' (' . $this->depo::class . ')'); } public function duzenle(array $p): Yanit { return new Yanit(200, "düzenle {$p['id']}"); } public function guncelle(array $p): Yanit { return new Yanit(200, "güncellendi {$p['id']}"); } public function sil(array $p): Yanit { return new Yanit(204); } } final class YayinlaDenetleyicisi { public function __invoke(array $p): Yanit { return new Yanit(200, "yayında {$p['id']}"); } } $y = new Yonlendirici(new NesneKabi()); $y->kaynak('yazilar', YaziDenetleyicisi::class); $y->rota('POST', '/yazilar/{id}/yayinla', 'yayinla', YayinlaDenetleyicisi::class); foreach ($y->rotalar() as [$yontem, $kalip, $parca]) { printf("%-6s %-22s %s\n", $yontem, $kalip, $parca); } foreach ([['GET', '/yazilar/7', 'goster'], ['POST', '/yazilar/7/yayinla', 'yayinla']] as [$yontem, $yol, $parca]) { $yanit = $y->isle($yontem, $yol); printf("%-4s %-19s %d %-17s %s %s\n", $yontem, $yol, $yanit->durum, $yanit->govde, $parca, sinifla($parca)); }
GET /yazilar liste
GET /yazilar/olustur olustur
POST /yazilar kaydet
GET /yazilar/{id} goster
GET /yazilar/{id}/duzenle duzenle
PUT /yazilar/{id} guncelle
DELETE /yazilar/{id} sil
POST /yazilar/{id}/yayinla yayinla
GET /yazilar/7 200 yazı 7 (Depo) goster koştu
POST /yazilar/7/yayinla 200 yayında 7 yayinla koştu
ÇT13. Yedi eylem yedi rotaya açıldı ve sekizinci rota tek eylemli denetleyiciye ait. /yazilar/7 isteğinde YaziDenetleyicisi kuruldu ve yapıcısındaki Depo parametresi kap tarafından ReflectionClass ile çözüldü; yanıt gövdesindeki Depo adı bunun kanıtıdır. Hiçbir yerde new Depo() yazılmadı. Eylem koştu. Tek eylemli denetleyici de aynı biçimde kuruldu ve __invoke çağrıldı; yayinla da koştu.
Bu dersin ölçü sınıfı koştu’dur. Ama koştu sınıfı yeni bir şeyi söylüyor: bir eylemin koşması için yalnız rotanın eşleşmesi yetmez, denetleyicinin kurulabilmesi de gerekir. Kurulamayan bir denetleyicinin eylemi çağrılmaz; sinifla() kuralını okursak o parça yolda kalır ve atlandı sınıfına düşer. Bu dersin ölçümleri bunu koşturmaz, yalnız kurala dayanır.
Denetleyicinin İşi
Denetleyici, isteğin sınırında durur. İsteğin parametrelerini alır, işi başka nesnelere yaptırır ve yanıtı kurar. İşin kendisini, yani bir yazının nasıl saklanacağını ya da hangi kuralla doğrulanacağını denetleyicide yazmak, o kodu isteğe bağlar: bir komut satırı betiğinden ya da bir işçiden çağrılamaz. Bu yüzden eylem küçük tutulur ve gerçek iş, yapıcıda istenen Depo gibi bir nesnede durur. Denetleyicinin yapıcıda bağımlılık istemesinin nedeni de budur: eylem işi yapan nesneyi kendisi kurmaz, kendisine verilmesini bekler.
Bu ölçümde yanıtlar bilerek kısa tutuldu: sil eylemi yalnızca 204 durumuyla bir yanıt döndürür, kaydet 201 ile bir ileti verir. Eylemin döndürdüğü her şey Yanit nesnesidir; eylem çıktıyı kendisi yazmaz. Çerçeve yanıtı alır ve göndermeyi üstlenir. Eylemin yanıt yerine doğrudan çıktı yazması, yanıttan sonra koşan işlerin ve ara katmanların dönüş yolunda yanıta dokunmasını imkânsız kılar.
İki denetleyici biçimi arasındaki seçim bir üslup kararıdır. Kaynak denetleyicisi, standart yedi eylem etrafında dönen bir varlık için tek sınıfla yedi rotayı kaydeder ve adresleri kuralla belirler. Tek eylemli denetleyici, yedi eylemin hiçbirine uymayan bir işlem için kullanılır ve rota tablosunda sınıfın adı işlemin adıdır. Birini seçmek eşleşmeyi değiştirmez; ikisinde de ilk eşleşen kazanır ve ikisinde de eylemin koşması denetleyicinin kurulmasına bağlıdır.
Sınırlayıcı Ölçüm
Kaynak denetleyicisi, tanımlı olmayan eylemler için rota kaydetmez: kaynak() içindeki method_exists denetimi yedi eylemi süzer. TaslakDenetleyicisi yalnız liste ve goster taşır. Bu durumda GET /taslaklar/7/duzenle isteği ne olur? Parça yoldaki başka bir adıma mı bağlanır, yoksa istek 404 mü verir? İkinci istek GET /taslaklar/olustur, yani tanımsız bir eylemin sabit adresidir.
<?php // eksik.php — tanımsız eylemin rotası hiç kaydedilmez declare(strict_types=1); require_once 'denetleyici.php'; final class TaslakDenetleyicisi { public function liste(): Yanit { return new Yanit(200, 'taslaklar'); } public function goster(array $p): Yanit { return new Yanit(200, "taslak {$p['id']}"); } } $y = new Yonlendirici(new NesneKabi()); $y->kaynak('taslaklar', TaslakDenetleyicisi::class); foreach ([['/taslaklar/7/duzenle', 'duzenle'], ['/taslaklar/olustur', 'olustur']] as [$yol, $parca]) { $yanit = $y->isle('GET', $yol); printf("%-21s %d %-16s %s %s\n", $yol, $yanit->durum, $yanit->govde, $parca, sinifla($parca)); }
/taslaklar/7/duzenle 404 duzenle bağlanmadı /taslaklar/olustur 200 taslak olustur olustur bağlanmadı
ÇT14. Sonuç iki ayrı biçimde çıktı. duzenle için rota hiç kaydedilmediğinden hiçbir kalıp isteğe uymadı: yanıt 404 oldu ve duzenle bağlanmadı. Bu bir bağlanmadı olmasına rağmen sessiz değildir, çünkü 404 durum kodu isteğin hiçbir rotaya düşmediğini söyler. olustur için ise tablo sabit adresi kaydetmedi, ama /taslaklar/{id} kalıbı olustur sözcüğünü bir id olarak kabul etti: yanıt 200 ve gövde taslak olustur, parça yine bağlanmadı. Aynı sınıf, bu kez bir durum koduyla değil sessizce göründü. İki durumda da geliştiricinin beklediği parça aynı adlandırmayla sorgulanıp aynı sınıfa düştü; ayrımı yanıtın durumu yaptı.
Bu sonuç önceki dersle birleşir: bir rotanın hiç kaydedilmemesi ile bir rotanın gölgelenmesi aynı bağlanmadı sınıfına düşer, ama biri 404 ile, öteki 200 ile gelir. Kaynak denetleyicisinin eksik bıraktığı eylemi geliştirici fark etmeyebilir; çerçeve bunu bir uyarı olarak vermez, çünkü method_exists süzmesi bilerek sessizdir.
Özet
- Rota bir sınıf adı ve yöntem adı taşır; çerçeve eşleşmeden sonra denetleyiciyi nesne kabından alır ve yapıcı bağımlılıklarını
ReflectionClassile çözdürür. - Kaynak denetleyicisi yedi eylemi yedi rotaya açar ve dar kalıbı geniş kalıptan önce kaydeder; tek eylemli denetleyici yalnızca
__invoketaşır. /yazilar/7isteğinde denetleyici kuruldu,Depobağımlılığı çözüldü ve eylem koştu; yayinla da aynı biçimde koştu.- Tanımsız
duzenleeyleminin rotası kaydedilmediği için istek 404 verdi ve parça bağlanmadı; tanımsızolustureylemi/taslaklar/{id}kalıbına düştü, yanıt 200 oldu ve parça yine bağlanmadı. - Bu dersin ölçü sınıfı koştu’dur: eylemin koşması için rotanın eşleşmesi ve denetleyicinin kurulması gerekir.
Sonraki Adım
Rota denetleyiciyi kurdu ve eylemi çağırdı. Ama her isteğin eylemden önce geçmesi gereken adımlar da vardır: oturumun açık olup olmadığı, girdinin geçerli olup olmadığı. Bu denetimler eylemin içine yazılırsa her denetleyicide yinelenir. Sonraki ders, Ara Katmanlar, bu denetimleri eylemin önüne koyar ve tek bir soruyu soruyor: önündeki katman yanıt verirse eylem ve arkadaki katmanlar ne olur?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.