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.
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()
veforward()
yönlendirirerror()
presenter'ı bir hata nedeniyle sonlandırırsendJson($data)
presenter'ı sonlandırır ve verileri JSON formatında gönderirsendTemplate()
presenter'ı sonlandırır ve hemen şablonu oluşturursendResponse($response)
presenter'ı sonlandırır ve özel bir yanıt gönderirterminate()
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:
- Nette\Application\Responses\CallbackResponse – bir geri çağırma gönderir
- Nette\Application\Responses\FileResponse – bir dosya gönderir
- Nette\Application\Responses\ForwardResponse – forward()
- Nette\Application\Responses\JsonResponse – JSON gönderir
- Nette\Application\Responses\RedirectResponse – yönlendirme
- Nette\Application\Responses\TextResponse – metin gönderir
- Nette\Application\Responses\VoidResponse – boş yanıt
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.