Presenter'lar

Nette'de presenter'ların ve şablonların nasıl yazıldığını öğreneceğiz. Okuduktan sonra şunları bileceksiniz:

  • presenter nasıl çalışır
  • kalıcı parametreler nedir
  • şablonlar nasıl oluşturulur

Zaten biliyoruz, presenter'ın bir web uygulamasının belirli bir sayfasını temsil eden bir sınıf olduğunu, örn. ana sayfa; e-ticaretteki bir ürün; giriş formu; site haritası beslemesi vb. Bir uygulamanın bir ila binlerce presenter'ı olabilir. Diğer framework'lerde bunlara controller da denir.

Genellikle presenter terimiyle, web arayüzleri oluşturmak için uygun olan ve bu bölümün geri kalanında ele alacağımız Nette\Application\UI\Presenter sınıfının bir alt sınıfı kastedilir. Genel anlamda, presenter Nette\Application\IPresenter arayüzünü uygulayan herhangi bir nesnedir.

Presenter Yaşam Döngüsü

Presenter'ın görevi, isteği işlemek ve bir yanıt döndürmektir (bu bir HTML sayfası, bir resim, bir yönlendirme vb. olabilir).

Yani başlangıçta ona bir istek iletilir. Bu doğrudan bir HTTP isteği değil, HTTP isteğinin yönlendirici yardımıyla dönüştürüldüğü Nette\Application\Request nesnesidir. Bu nesneyle genellikle temas etmeyiz, çünkü presenter isteğin işlenmesini akıllıca şimdi göstereceğimiz diğer metotlara devreder.

Presenter yaşam döngüsü

Resim, mevcutsa yukarıdan aşağıya sırayla çağrılan metotların bir listesini temsil eder. Hiçbirinin mevcut olması gerekmez, tek bir metodu olmayan tamamen boş bir presenter'a sahip olabilir ve üzerine basit bir statik web sitesi kurabiliriz.

__construct()

Kurucu, nesnenin oluşturulma anında çağrıldığı için tam olarak presenter yaşam döngüsüne ait değildir. Ancak önemi nedeniyle bahsediyoruz. Kurucu (inject metodu ile birlikte) bağımlılıkları iletmek için kullanılır.

Presenter, uygulamanın iş mantığını işlememeli, veritabanından yazıp okumamalı, hesaplamalar yapmamalı vb. Bunun için model olarak adlandırdığımız katmandan sınıflar vardır. Örneğin, ArticleRepository sınıfı makaleleri yüklemek ve kaydetmekten sorumlu olabilir. Presenter'ın onunla çalışabilmesi için, onu bağımlılık enjeksiyonu aracılığıyla iletmesini ister:

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ArticleRepository $articles,
	) {
	}
}

startup()

İstek alındıktan hemen sonra startup() metodu çağrılır. Özellikleri başlatmak, kullanıcı izinlerini doğrulamak vb. için kullanabilirsiniz. Metodun her zaman atası parent::startup()'ı çağırması gerekir.

action<Action>(args...)

render<View>() metodunun bir benzeri. render<View>() belirli bir şablon için veri hazırlamak ve ardından onu oluşturmak için tasarlanmışken, action<Action>()'da istek şablon oluşturmaya bağlı kalmadan işlenir. Örneğin, veriler işlenir, kullanıcı giriş yapar veya çıkış yapar vb. ve ardından başka bir yere yönlendirilir.

Önemli olan, action<Action>()'ın render<View>()'dan önce çağrılmasıdır, bu yüzden içinde olayların sonraki seyrini değiştirebiliriz, yani oluşturulacak şablonu ve ayrıca çağrılacak render<View>() metodunu değiştirebiliriz. Ve bunu setView('jineView') kullanarak yaparız.

Metoda istekten parametreler iletilir. Parametrelere tür belirtmek mümkündür ve önerilir, örn. actionShow(int $id, ?string $slug = null) – eğer id parametresi eksikse veya tamsayı değilse, presenter 404 hatası döndürür ve çalışmayı sonlandırır.

handle<Signal>(args...)

Metot, bileşenler bölümünde tanışacağımız sözde sinyalleri işler. Aslında özellikle bileşenler ve AJAX isteklerinin işlenmesi için tasarlanmıştır.

Metoda, action<Action>() durumunda olduğu gibi, tür kontrolü dahil olmak üzere istekten parametreler iletilir.

beforeRender()

beforeRender metodu, adından da anlaşılacağı gibi, her render<View>() metodundan önce çağrılır. Şablonun ortak yapılandırması, layout için değişkenlerin iletilmesi vb. için kullanılır.

render<View>(args...)

Şablonu sonraki oluşturma için hazırladığımız, ona veri ilettiğimiz vb. yer.

Metoda, action<Action>() durumunda olduğu gibi, tür kontrolü dahil olmak üzere istekten parametreler iletilir.

public function renderShow(int $id): void
{
	// modelden verileri alır ve şablona iletiriz
	$this->template->article = $this->articles->getById($id);
}

afterRender()

afterRender metodu, adından da anlaşılacağı gibi, her render<View>() metodundan sonra çağrılır. Daha çok istisnai durumlarda kullanılır.

shutdown()

Presenter yaşam döngüsünün sonunda çağrılır.

Devam etmeden önce iyi bir tavsiye. Gördüğünüz gibi presenter birden fazla eylemi/view'i işleyebilir, yani birden fazla render<View>() metoduna sahip olabilir. Ancak, bir veya mümkün olduğunca az eyleme sahip presenter'lar tasarlamanızı öneririz.

Yanıt Gönderme

Presenter'ın yanıtı genellikle bir HTML sayfasıyla şablonun oluşturulmasıdır, ancak bir dosya gönderimi, JSON veya başka bir sayfaya yönlendirme de olabilir.

Yaşam döngüsünün herhangi bir anında, aşağıdaki metotlardan biriyle bir yanıt gönderebilir ve aynı zamanda presenter'ı sonlandırabiliriz:

  • redirect(), redirectPermanent(), redirectUrl() ve forward() yönlendirir
  • error() presenter'ı bir hata nedeniyle sonlandırır
  • sendJson($data) presenter'ı sonlandırır ve verileri JSON formatında gönderir
  • sendTemplate() presenter'ı sonlandırır ve hemen şablonu oluşturur
  • sendResponse($response) presenter'ı sonlandırır ve özel bir yanıt gönderir
  • terminate() presenter'ı yanıtsız sonlandırır

Bu metotlardan hiçbirini çağırmazsanız, presenter otomatik olarak şablonu oluşturmaya devam eder. Neden? Çünkü vakaların %99'unda bir şablon oluşturmak istiyoruz, bu yüzden presenter bu davranışı varsayılan olarak kabul eder ve işimizi kolaylaştırmak ister.

Bağlantı Oluşturma

Presenter, diğer presenter'lara URL bağlantıları oluşturmak için kullanılabilecek link() metoduna sahiptir. İlk parametre hedef presenter ve eylemdir, ardından bir dizi olarak belirtilebilecek iletilen argümanlar gelir:

$url = $this->link('Product:show', $id);

$url = $this->link('Product:show', [$id, 'lang' => 'tr']);

Şablonda, diğer presenter'lara ve eylemlere bağlantılar şu şekilde oluşturulur:

<a n:href="Product:show $id">ürün detayı</a>

Sadece gerçek URL yerine bilinen Presenter:action çiftini yazın ve olası parametreleri belirtin. İşin püf noktası, bu niteliğin Latte tarafından işleneceğini ve gerçek bir URL oluşturacağını söyleyen n:href'tir. Nette'de URL'leri hiç düşünmenize gerek yoktur, sadece presenter'ları ve eylemleri düşünmeniz yeterlidir.

Daha fazla bilgi için URL Bağlantıları Oluşturma bölümünde bulabilirsiniz.

Yönlendirme

Başka bir presenter'a geçmek için, link() metoduyla çok benzer bir sözdizimine sahip olan redirect() ve forward() metotları kullanılır.

forward() metodu, HTTP yönlendirmesi olmadan hemen yeni presenter'a geçer:

$this->forward('Product:show');

HTTP kodu 302 (veya mevcut isteğin metodu POST ise 303) ile sözde geçici yönlendirme örneği:

$this->redirect('Product:show', $id);

HTTP kodu 301 ile kalıcı yönlendirmeyi şu şekilde başarırsınız:

$this->redirectPermanent('Product:show', $id);

Uygulama dışındaki başka bir URL'ye redirectUrl() metoduyla yönlendirilebilir. İkinci parametre olarak HTTP kodu belirtilebilir, varsayılan 302'dir (veya mevcut isteğin metodu POST ise 303):

$this->redirectUrl('https://nette.org');

Yönlendirme, sözde sessiz sonlandırma istisnası Nette\Application\AbortException atarak presenter'ın etkinliğini hemen sonlandırır.

Yönlendirmeden önce, yönlendirmeden sonra şablonda görüntülenecek olan flash mesajları (geçici mesajlar) gönderilebilir.

Flash Mesajları

Bunlar genellikle bir işlemin sonucunu bildiren mesajlardır. Flash mesajlarının önemli bir özelliği, yönlendirmeden sonra bile şablonda kullanılabilir olmalarıdır. Görüntülendikten sonra bile 30 saniye daha canlı kalırlar – örneğin, hatalı bir aktarım nedeniyle kullanıcının sayfayı yenilemesi durumunda mesaj hemen kaybolmaz.

Sadece flashMessage() metodunu çağırmanız yeterlidir ve presenter şablona iletilmesini halleder. İlk parametre mesaj metni ve isteğe bağlı ikinci parametre türüdür (error, warning, info vb.). flashMessage() metodu, flash mesajının bir örneğini döndürür ve buna ek bilgiler eklenebilir.

$this->flashMessage('Öğe silindi.');
$this->redirect(/* ... */); // ve yönlendiririz

Şablonda bu mesajlar $flashes değişkeninde stdClass nesneleri olarak bulunur ve message (mesaj metni), type (mesaj türü) özelliklerini içerir ve daha önce bahsedilen kullanıcı bilgilerini içerebilir. Onları örneğin şu şekilde oluştururuz:

{foreach $flashes as $flash}
	<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}

Hata 404 ve Benzerleri

İstek yerine getirilemiyorsa, örneğin görüntülemek istediğimiz makale veritabanında mevcut olmadığı için, error(?string $message = null, int $httpCode = 404) metoduyla 404 hatası atarız.

public function renderShow(int $id): void
{
	$article = $this->articles->getById($id);
	if (!$article) {
		$this->error();
	}
	// ...
}

Hatanın HTTP kodu ikinci parametre olarak iletilebilir, varsayılan 404'tür. Metot, Nette\Application\BadRequestException istisnası atarak çalışır, bunun üzerine Application kontrolü error-presenter'a devreder. Bu, meydana gelen hatayı bildiren bir sayfa görüntülemekle görevli bir presenter'dır. Error-presenter ayarı application yapılandırmasında yapılır.

JSON Gönderme

Verileri JSON formatında gönderen ve presenter'ı sonlandıran bir action-metodu örneği:

public function actionData(): void
{
	$data = ['hello' => 'nette'];
	$this->sendJson($data);
}

İstek Parametreleri

Presenter ve ayrıca her bileşen, HTTP isteğinden parametrelerini alır. Değerlerini getParameter($name) veya getParameters() metoduyla öğrenebilirsiniz. Değerler dizeler veya dize dizileridir, aslında doğrudan URL'den alınan ham verilerdir.

Daha fazla kolaylık için parametreleri özellikler aracılığıyla erişilebilir hale getirmenizi öneririz. Bunları #[Parameter] niteliğiyle işaretlemeniz yeterlidir:

use Nette\Application\Attributes\Parameter;  // bu satır önemlidir

class HomePresenter extends Nette\Application\UI\Presenter
{
	#[Parameter]
	public string $theme; // public olmalı
}

Özellik için veri türünü (örn. string) belirtmenizi öneririz ve Nette değeri buna göre otomatik olarak dönüştürür. Parametre değerleri ayrıca doğrulanabilir.

Bir bağlantı oluştururken, parametrelere doğrudan değer atanabilir:

<a n:href="Home:default theme: dark">tıkla</a>

Kalıcı Parametreler

Kalıcı parametreler, farklı istekler arasında durumu korumak için kullanılır. Değerleri, bir bağlantıya tıklandıktan sonra bile aynı kalır. Oturumdaki verilerin aksine, URL'de aktarılırlar. Ve bu tamamen otomatiktir, bu yüzden onları link() veya n:href içinde açıkça belirtmeye gerek yoktur.

Kullanım örneği? Çok dilli bir uygulamanız var. Mevcut dil, sürekli olarak URL'nin bir parçası olması gereken bir parametredir. Ancak her bağlantıda onu belirtmek inanılmaz derecede yorucu olurdu. Bu yüzden onu kalıcı bir lang parametresi yaparsınız ve kendi kendine aktarılır. Harika!

Nette'de kalıcı bir parametre oluşturmak son derece basittir. Sadece genel bir özellik oluşturmanız ve onu bir nitelikle işaretlemeniz yeterlidir: (eskiden /** @persistent */ kullanılırdı)

use Nette\Application\Attributes\Persistent;  // bu satır önemlidir

class ProductPresenter extends Nette\Application\UI\Presenter
{
	#[Persistent]
	public string $lang; // public olmalı
}

Eğer $this->lang değeri örneğin 'tr' ise, link() veya n:href kullanılarak oluşturulan bağlantılar da lang=tr parametresini içerecektir. Ve bağlantıya tıklandıktan sonra yine $this->lang = 'tr' olacaktır.

Özellik için veri türünü (örn. string) belirtmenizi öneririz ve varsayılan bir değer de belirtebilirsiniz. Parametre değerleri doğrulanabilir.

Kalıcı parametreler standart olarak söz konusu presenter'ın tüm eylemleri arasında aktarılır. Birden fazla presenter arasında da aktarılmaları için, ya:

  • presenter'ların miras aldığı ortak bir atada tanımlanmaları gerekir
  • presenter'ların kullandığı bir trait'te tanımlanmaları gerekir:
trait LanguageAware
{
	#[Persistent]
	public string $lang;
}

class ProductPresenter extends Nette\Application\UI\Presenter
{
	use LanguageAware;
}

Bir bağlantı oluştururken, kalıcı parametrenin değeri değiştirilebilir:

<a n:href="Product:show $id, lang: en">İngilizce detay</a>

Veya sıfırlanabilir, yani URL'den kaldırılabilir. O zaman varsayılan değerini alacaktır:

<a n:href="Product:show $id, lang: null">tıkla</a>

İnteraktif Bileşenler

Presenter'ların yerleşik bir bileşen sistemi vardır. Bileşenler, presenter'lara eklediğimiz bağımsız, yeniden kullanılabilir birimlerdir. Bunlar formlar, veri ızgaraları, menüler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir.

Bileşenler presenter'a nasıl eklenir ve ardından kullanılır? Bunu Bileşenler bölümünde öğreneceksiniz. Hatta Hollywood ile ortak yanlarının ne olduğunu bile keşfedeceksiniz.

Ve bileşenleri nereden alabilirim? Componette sayfasında, framework etrafındaki topluluktan gönüllülerin buraya yerleştirdiği açık kaynaklı bileşenleri ve Nette için bir dizi başka eklentiyi bulabilirsiniz.

Derinlemesine İnceliyoruz

Bu bölümde şimdiye kadar gösterdiklerimizle muhtemelen tamamen idare edeceksiniz. Aşağıdaki satırlar, presenter'ları derinlemesine merak eden ve kesinlikle her şeyi bilmek isteyenler içindir.

Parametre Doğrulaması

URL'den alınan istek parametrelerinin ve kalıcı parametrelerin değerleri loadState() metodu tarafından özelliklere yazılır. Bu metot ayrıca özellikte belirtilen veri türünün eşleşip eşleşmediğini de kontrol eder, aksi takdirde 404 hatasıyla yanıt verir ve sayfa görüntülenmez.

Parametrelere asla körü körüne güvenmeyin, çünkü kullanıcı tarafından URL'de kolayca üzerine yazılabilirler. Örneğin, $this->lang dilinin desteklenenler arasında olup olmadığını bu şekilde doğrularız. Uygun yol, bahsedilen loadState() metodunu geçersiz kılmaktır:

class ProductPresenter extends Nette\Application\UI\Presenter
{
	#[Persistent]
	public string $lang;

	public function loadState(array $params): void
	{
		parent::loadState($params); // burada $this->lang ayarlanır
		// ardından kendi değer kontrolümüz gelir:
		if (!in_array($this->lang, ['en', 'tr'])) {
			$this->error();
		}
	}
}

İsteği Kaydetme ve Geri Yükleme

Presenter'ın işlediği istek Nette\Application\Request nesnesidir ve presenter'ın getRequest() metodu tarafından döndürülür.

Mevcut istek oturuma kaydedilebilir veya tam tersi, ondan geri yüklenebilir ve presenter'ın onu tekrar yürütmesi sağlanabilir. Bu, örneğin kullanıcı bir form doldururken ve oturumu sona erdiğinde kullanışlıdır. Verileri kaybetmemek için, giriş sayfasına yönlendirmeden önce mevcut isteği $reqId = $this->storeRequest() kullanarak oturuma kaydederiz, bu da kısa bir dize şeklinde kimliğini döndürür ve bunu giriş presenter'ına parametre olarak iletiriz.

Giriş yaptıktan sonra, isteği oturumdan alan ve ona yönlendiren $this->restoreRequest($reqId) metodunu çağırırız. Metot bu arada isteği oluşturan kullanıcının şimdi giriş yapanla aynı olduğunu doğrular. Farklı bir kullanıcı giriş yaparsa veya anahtar geçersizse, hiçbir şey yapmaz ve program devam eder.

Önceki sayfaya nasıl geri dönülür kılavuzuna bakın.

Kanonikleştirme

Presenter'ların SEO'ya (arama motoru optimizasyonu) katkıda bulunan gerçekten harika bir özelliği vardır. Farklı URL'lerde yinelenen içeriğin varlığını otomatik olarak önlerler. Belirli bir hedefe birden fazla URL adresi yönlendiriyorsa, örn. /index ve /index?page=1, framework bunlardan birini birincil (kanonik) olarak belirler ve diğerlerini HTTP kodu 301 ile ona yönlendirir. Bu sayede arama motorları sayfalarınızı iki kez indekslemez ve sayfa sıralamalarını seyreltmez.

Bu sürece kanonikleştirme denir. Kanonik URL, yönlendirici tarafından oluşturulan URL'dir, yani genellikle koleksiyondaki ilk eşleşen rotadır.

Kanonikleştirme varsayılan olarak açıktır ve $this->autoCanonicalize = false aracılığıyla kapatılabilir.

AJAX veya POST isteği sırasında yönlendirme gerçekleşmez, çünkü veri kaybına neden olur veya SEO açısından ek bir değeri olmaz.

Kanonikleştirmeyi manuel olarak canonicalize() metoduyla da tetikleyebilirsiniz; bu metoda link() metoduna benzer şekilde presenter, eylem ve parametreler iletilir. Bir bağlantı oluşturur ve onu mevcut URL adresiyle karşılaştırır. Farklıysa, oluşturulan bağlantıya yönlendirir.

public function actionShow(int $id, ?string $slug = null): void
{
	$realSlug = $this->facade->getSlugForId($id);
	// $slug, $realSlug'dan farklıysa yönlendirir
	$this->canonicalize('Product:show', [$id, $realSlug]);
}

Olaylar

Presenter yaşam döngüsünün bir parçası olarak çağrılan startup(), beforeRender() ve shutdown() metotlarına ek olarak, otomatik olarak çağrılması gereken başka fonksiyonlar da tanımlanabilir. Presenter, işleyicilerini $onStartup, $onRender ve $onShutdown dizilerine ekleyeceğiniz sözde olayları tanımlar.

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	public function __construct()
	{
		$this->onStartup[] = function () {
			// ...
		};
	}
}

$onStartup dizisindeki işleyiciler startup() metodundan hemen önce, $onRender beforeRender() ile render<View>() arasında ve son olarak $onShutdown shutdown() metodundan hemen önce çağrılır.

Yanıtlar

Presenter'ın döndürdüğü yanıt, Nette\Application\Response arayüzünü uygulayan bir nesnedir. Bir dizi hazır yanıt mevcuttur:

Yanıtlar sendResponse() metoduyla gönderilir:

use Nette\Application\Responses;

// Düz metin
$this->sendResponse(new Responses\TextResponse('Merhaba Nette!'));

// Bir dosya gönderir
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));

// Yanıt bir geri çağırma olacak
$callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) {
	if ($httpResponse->getHeader('Content-Type') === 'text/html') {
		echo '<h1>Merhaba</h1>';
	}
};
$this->sendResponse(new Responses\CallbackResponse($callback));

#[Requires] ile Erişim Kısıtlaması

#[Requires] niteliği, presenter'lara ve metotlarına erişimi kısıtlamak için gelişmiş seçenekler sunar. HTTP metotlarını belirtmek, AJAX isteği gerektirmek, aynı kaynağa (same origin) kısıtlamak ve yalnızca yönlendirme (forwarding) yoluyla erişime izin vermek için kullanılabilir. Nitelik, hem presenter sınıflarına hem de action<Action>(), render<View>(), handle<Signal>() ve createComponent<Name>() bireysel metotlarına uygulanabilir.

Şu kısıtlamaları belirleyebilirsiniz:

  • HTTP metotlarına: #[Requires(methods: ['GET', 'POST'])]
  • AJAX isteği gerektirme: #[Requires(ajax: true)]
  • yalnızca aynı kaynaktan erişim: #[Requires(sameOrigin: true)]
  • yalnızca yönlendirme (forward) yoluyla erişim: #[Requires(forward: true)]
  • belirli eylemlere kısıtlama: #[Requires(actions: 'default')]

Ayrıntılar için [#[Requires] Niteliği Nasıl Kullanılır |best-practices:attribute-requires] kılavuzuna bakın.

HTTP Metodu Kontrolü

Nette'deki presenter'lar, her gelen isteğin HTTP metodunu otomatik olarak doğrular. Bu kontrolün nedeni öncelikle güvenliktir. Standart olarak GET, POST, HEAD, PUT, DELETE, PATCH metotlarına izin verilir.

Örneğin OPTIONS metoduna ek olarak izin vermek istiyorsanız, #[Requires] niteliğini kullanın (Nette Application v3.2'den itibaren):

#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}

Sürüm 3.1'de doğrulama, istekte belirtilen metodun $presenter->allowedMethods dizisinde bulunup bulunmadığını kontrol eden checkHttpMethod() içinde yapılır. Metodu şu şekilde ekleyin:

class MyPresenter extends Nette\Application\UI\Presenter
{
    protected function checkHttpMethod(): void
    {
        $this->allowedMethods[] = 'OPTIONS';
        parent::checkHttpMethod();
    }
}

OPTIONS metoduna izin verirseniz, onu daha sonra presenter'ınız içinde uygun şekilde işlemeniz gerektiğini vurgulamak önemlidir. Metot genellikle, isteğin CORS (Cross-Origin Resource Sharing) politikası açısından izinli olup olmadığını belirlemek gerektiğinde tarayıcının gerçek istekten önce otomatik olarak gönderdiği sözde bir preflight isteği olarak kullanılır. Metoda izin verir ancak doğru yanıtı uygulamazsanız, bu tutarsızlıklara ve potansiyel güvenlik sorunlarına yol açabilir.

Daha Fazla Okuma

versiyon: 4.0