Presentere
Vom face cunoștință cu modul în care se scriu presenterele și șabloanele în Nette. După citire, veți ști:
- cum funcționează un presenter
- ce sunt parametrii persistenți
- cum se redau șabloanele
Știm deja că presenterul este o clasă care reprezintă o anumită pagină specifică a aplicației web, de ex. pagina de start; un produs într-un magazin online; formularul de conectare; feed-ul sitemap etc. Aplicația poate avea de la unul la mii de presentere. În alte framework-uri li se mai spune și controllere.
De obicei, prin termenul presenter se înțelege un descendent al clasei Nette\Application\UI\Presenter, care este potrivit pentru generarea interfețelor web și căruia ne vom dedica în restul acestui capitol. În sens general, un presenter este orice obiect care implementează interfața Nette\Application\IPresenter.
Ciclul de viață al presenterului
Sarcina presenterului este de a gestiona cererea și de a returna un răspuns (care poate fi o pagină HTML, o imagine, o redirecționare etc.).
Deci, la început i se transmite cererea. Nu este direct o cerere HTTP, ci obiectul Nette\Application\Request, în care a fost transformată cererea HTTP cu ajutorul routerului. Cu acest obiect, de obicei, nu intrăm în contact, deoarece presenterul deleagă inteligent procesarea cererii către alte metode, pe care le vom prezenta acum.
Imaginea reprezintă lista metodelor care sunt apelate succesiv de sus în jos, dacă există. Niciuna dintre ele nu trebuie să existe, putem avea un presenter complet gol, fără nicio metodă, și să construim pe el un site web static simplu.
__construct()
Constructorul nu face parte în totalitate din ciclul de viață al presenterului, deoarece este apelat în momentul creării obiectului. Dar îl menționăm datorită importanței sale. Constructorul (împreună cu metoda inject) servește la transmiterea dependențelor.
Presenterul nu ar trebui să se ocupe de logica de business a aplicației, să scrie și să citească din baza de date, să
efectueze calcule etc. Pentru asta există clase din stratul pe care îl numim model. De exemplu, clasa
ArticleRepository
poate avea responsabilitatea de a încărca și salva articole. Pentru ca presenterul să poată
lucra cu ea, o primește transmisă prin dependency
injection:
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articles,
) {
}
}
startup()
Imediat după primirea cererii, se apelează metoda startup()
. O puteți utiliza pentru inițializarea
proprietăților, verificarea permisiunilor utilizatorului etc. Este necesar ca metoda să apeleze întotdeauna părintele
parent::startup()
.
action<Action>(args...)
Similară cu metoda render<View>()
. În timp ce render<View>()
este destinată
pregătirii datelor pentru un șablon specific care urmează să fie redat, în action<Action>()
se procesează
cererea fără legătură cu redarea șablonului. De exemplu, se procesează date, se conectează sau deconectează utilizatorul,
și așa mai departe, și apoi se redirecționează în altă parte.
Important este că action<Action>()
se apelează înainte de render<View>()
, deci în ea
putem eventual schimba cursul ulterior al evenimentelor, adică să schimbăm șablonul care va fi redat, și, de asemenea, metoda
render<View>()
care va fi apelată. Și asta folosind setView('jineView')
.
Metodei i se transmit parametri din cerere. Este posibil și recomandat să se specifice tipurile parametrilor, de ex.
actionShow(int $id, ?string $slug = null)
– dacă parametrul id
lipsește sau dacă nu este un
integer, presenterul va returna eroarea 404 și va încheia activitatea.
handle<Signal>(args...)
Metoda procesează așa-numitele semnale, cu care ne vom familiariza în capitolul dedicat componentelor. Este destinată în special componentelor și procesării cererilor AJAX.
Metodei i se transmit parametri din cerere, ca în cazul action<Action>()
, inclusiv verificarea
tipului.
beforeRender()
Metoda beforeRender
, așa cum sugerează și numele, se apelează înainte de fiecare metodă
render<View>()
. Se utilizează pentru configurarea comună a șablonului, transmiterea variabilelor pentru
layout și altele asemenea.
render<View>(args...)
Locul unde pregătim șablonul pentru redarea ulterioară, îi transmitem date etc.
Metodei i se transmit parametri din cerere, ca în cazul action<Action>()
, inclusiv verificarea
tipului.
public function renderShow(int $id): void
{
// obținem datele din model și le transmitem șablonului
$this->template->article = $this->articles->getById($id);
}
afterRender()
Metoda afterRender
, așa cum sugerează din nou numele, se apelează după fiecare metodă
render<View>()
. Se utilizează mai degrabă excepțional.
shutdown()
Se apelează la sfârșitul ciclului de viață al presenterului.
Un sfat bun, înainte de a merge mai departe. Presenterul, după cum se vede, poate gestiona mai multe
acțiuni/view-uri, adică poate avea mai multe metode render<View>()
. Dar recomandăm proiectarea presenterelor
cu una sau cât mai puține acțiuni.
Trimiterea răspunsului
Răspunsul presenterului este, de regulă, redarea unui șablon cu o pagină HTML, dar poate fi și trimiterea unui fișier, JSON sau chiar o redirecționare către o altă pagină.
Oricând în timpul ciclului de viață putem trimite un răspuns folosind una dintre următoarele metode și, în același timp, să încheiem presenterul:
redirect()
,redirectPermanent()
,redirectUrl()
șiforward()
redirecționeazăerror()
încheie presenterul din cauza unei erorisendJson($data)
încheie presenterul și trimite date în format JSONsendTemplate()
încheie presenterul și imediat redă șablonulsendResponse($response)
încheie presenterul și trimite un răspuns propriuterminate()
încheie presenterul fără răspuns
Dacă nu apelați niciuna dintre aceste metode, presenterul va trece automat la redarea șablonului. De ce? Deoarece în 99% din cazuri dorim să redăm un șablon, prin urmare presenterul consideră acest comportament ca fiind implicit și vrea să ne ușureze munca.
Crearea linkurilor
Presenterul dispune de metoda link()
, cu ajutorul căreia se pot crea linkuri URL către alți presenteri. Primul
parametru este presenterul & acțiunea țintă, urmate de argumentele transmise, care pot fi specificate ca array:
$url = $this->link('Product:show', $id);
$url = $this->link('Product:show', [$id, 'lang' => 'cs']);
În șablon se creează linkuri către alți presenteri & acțiuni în acest mod:
<a n:href="Product:show $id">detaliu produs</a>
Pur și simplu, în loc de URL-ul real, scrieți perechea cunoscută Presenter:action
și specificați eventualii
parametri. Trucul este în n:href
, care spune că acest atribut va fi procesat de Latte și va genera URL-ul real.
În Nette, nu trebuie să vă gândiți deloc la URL-uri, ci doar la presentere și acțiuni.
Mai multe informații găsiți în capitolul Crearea linkurilor URL.
Redirecționare
Pentru a trece la un alt presenter se utilizează metodele redirect()
și forward()
, care au
o sintaxă foarte similară cu metoda link().
Metoda forward()
trece imediat la noul presenter fără redirecționare HTTP:
$this->forward('Product:show');
Exemplu de așa-numită redirecționare temporară cu codul HTTP 302 (sau 303, dacă metoda cererii curente este POST):
$this->redirect('Product:show', $id);
Redirecționarea permanentă cu codul HTTP 301 se realizează astfel:
$this->redirectPermanent('Product:show', $id);
Către un alt URL în afara aplicației se poate redirecționa cu metoda redirectUrl()
. Ca al doilea parametru se
poate specifica codul HTTP, implicit este 302 (sau 303, dacă metoda cererii curente este POST):
$this->redirectUrl('https://nette.org');
Redirecționarea încheie imediat activitatea presenterului prin aruncarea așa-numitei excepții de terminare silențioasă
Nette\Application\AbortException
.
Înainte de redirecționare se poate trimite un mesaj flash, adică mesaje care vor fi afișate în șablon după redirecționare.
Mesaje flash
Acestea sunt mesaje care informează de obicei despre rezultatul unei operațiuni. O caracteristică importantă a mesajelor flash este că sunt disponibile în șablon chiar și după redirecționare. Chiar și după afișare, rămân active încă 30 de secunde – de exemplu, în cazul în care utilizatorul ar reîncărca pagina din cauza unei erori de transmisie – mesajul nu dispare imediat.
Este suficient să apelați metoda flashMessage() și
presenterul se va ocupa de transmiterea către șablon. Primul parametru este textul mesajului și al doilea parametru opțional
este tipul său (error, warning, info etc.). Metoda flashMessage()
returnează instanța mesajului flash, căreia
i se pot adăuga informații suplimentare.
$this->flashMessage('Elementul a fost șters.');
$this->redirect(/* ... */); // și redirecționăm
În șablon, aceste mesaje sunt disponibile în variabila $flashes
ca obiecte stdClass
, care conțin
proprietățile message
(textul mesajului), type
(tipul mesajului) și pot conține informațiile
utilizatorului menționate anterior. Le redăm, de exemplu, astfel:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Eroare 404 etc.
Dacă cererea nu poate fi îndeplinită, de exemplu, pentru că articolul pe care dorim să îl afișăm nu există în baza de
date, aruncăm eroarea 404 cu metoda error(?string $message = null, int $httpCode = 404)
.
public function renderShow(int $id): void
{
$article = $this->articles->getById($id);
if (!$article) {
$this->error();
}
// ...
}
Codul HTTP al erorii poate fi transmis ca al doilea parametru, implicit este 404. Metoda funcționează aruncând excepția
Nette\Application\BadRequestException
, după care Application
predă controlul error-presenterului.
Acesta este un presenter a cărui sarcină este să afișeze o pagină care informează despre eroarea apărută. Setarea
error-presenterului se face în configurația application.
Trimiterea JSON
Exemplu de metodă-acțiune care trimite date în format JSON și încheie presenterul:
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
Parametrii cererii
Presenterul și, de asemenea, fiecare componentă obțin parametrii săi din cererea HTTP. Valoarea lor o aflați cu metoda
getParameter($name)
sau getParameters()
. Valorile sunt șiruri de caractere sau array-uri de șiruri de
caractere, sunt în esență date brute obținute direct din URL.
Pentru mai mult confort, recomandăm accesarea parametrilor prin proprietăți. Este suficient să le marcați cu atributul
#[Parameter]
:
use Nette\Application\Attributes\Parameter; // această linie este importantă
class HomePresenter extends Nette\Application\UI\Presenter
{
#[Parameter]
public string $theme; // trebuie să fie publică
}
Pentru proprietate, recomandăm să specificați și tipul de date (de ex. string
), iar Nette va converti automat
valoarea conform acestuia. Valorile parametrilor pot fi, de asemenea, validate.
La crearea unui link, valoarea parametrilor poate fi setată direct:
<a n:href="Home:default theme: dark">click</a>
Parametri persistenți
Parametrii persistenți sunt utilizați pentru a menține starea între diferite cereri. Valoarea lor rămâne aceeași chiar
și după ce se face clic pe un link. Spre deosebire de datele din sesiune, acestea sunt transmise în URL. Și acest lucru se
întâmplă complet automat, deci nu este necesar să le specificați explicit în link()
sau
n:href
.
Exemplu de utilizare? Aveți o aplicație multilingvă. Limba curentă este un parametru care trebuie să fie constant parte a
URL-ului. Dar ar fi incredibil de obositor să îl specificați în fiecare link. Așa că îl faceți un parametru persistent
lang
și se va transmite singur. Minunat!
Crearea unui parametru persistent în Nette este extrem de simplă. Este suficient să creați o proprietate publică și să
o marcați cu un atribut: (anterior se folosea /** @persistent */
)
use Nette\Application\Attributes\Persistent; // această linie este importantă
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang; // trebuie să fie publică
}
Dacă $this->lang
va avea valoarea, de exemplu, 'en'
, atunci și linkurile create folosind
link()
sau n:href
vor conține parametrul lang=en
. Și după ce se face clic pe link, va fi
din nou $this->lang = 'en'
.
Pentru proprietate, recomandăm să specificați și tipul de date (de ex. string
) și puteți specifica și
o valoare implicită. Valorile parametrilor pot fi validate.
Parametrii persistenți sunt transmiși standard între toate acțiunile presenterului respectiv. Pentru a se transmite și între mai mulți presenteri, este necesar să fie definiți fie:
- într-un strămoș comun, de la care moștenesc presenterele
- într-un trait, pe care presenterele îl utilizează:
trait LanguageAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LanguageAware;
}
La crearea unui link, valoarea parametrului persistent poate fi modificată:
<a n:href="Product:show $id, lang: cs">detaliu în cehă</a>
Sau poate fi resetat, adică eliminat din URL. Atunci va lua valoarea sa implicită:
<a n:href="Product:show $id, lang: null">click aici</a>
Componente interactive
Presenterele au încorporat un sistem de componente. Componentele sunt unități separate, reutilizabile, pe care le inserăm în presentere. Acestea pot fi formulare, datagrid-uri, meniuri, de fapt, orice are sens să fie folosit în mod repetat.
Cum se inserează componentele în presenter și cum se utilizează ulterior? Acest lucru îl veți afla în capitolul Componente. Veți descoperi chiar și ce au în comun cu Hollywood-ul.
Și de unde pot obține componente? Pe pagina Componette găsiți componente open-source și, de asemenea, o serie de alte add-on-uri pentru Nette, pe care le-au plasat aici voluntari din comunitatea din jurul framework-ului.
Intrăm în profunzime
Cu ceea ce am arătat până acum în acest capitol, probabil vă veți descurca complet. Următoarele rânduri sunt destinate celor care sunt interesați de presentere în profunzime și doresc să știe absolut totul.
Validarea parametrilor
Valorile parametrilor cererii și ale parametrilor
persistenți primite din URL sunt scrise în proprietăți de către metoda loadState()
. Aceasta verifică, de
asemenea, dacă tipul de date specificat la proprietate corespunde, altfel răspunde cu eroarea 404 și pagina nu se
afișează.
Nu credeți niciodată orbește în parametri, deoarece pot fi ușor suprascriși de utilizator în URL. Astfel, de exemplu,
verificăm dacă limba $this->lang
se află printre cele suportate. O cale potrivită este să suprascriem metoda
menționată loadState()
:
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
public function loadState(array $params): void
{
parent::loadState($params); // aici se setează $this->lang
// urmează controlul propriu al valorii:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
Salvarea și restaurarea cererii
Cererea pe care o gestionează presenterul este obiectul Nette\Application\Request și este returnată
de metoda presenterului getRequest()
.
Cererea curentă poate fi salvată în sesiune sau, invers, restaurată din ea și lăsată presenterului să o execute din
nou. Acest lucru este util, de exemplu, în situația în care utilizatorul completează un formular și îi expiră sesiunea de
conectare. Pentru a nu pierde datele, înainte de redirecționarea către pagina de conectare, salvăm cererea curentă în
sesiune folosind $reqId = $this->storeRequest()
, care returnează identificatorul său sub forma unui șir scurt
și îl transmitem ca parametru presenterului de conectare.
După conectare, apelăm metoda $this->restoreRequest($reqId)
, care preia cererea din sesiune și face forward
către ea. Metoda verifică, în același timp, că cererea a fost creată de același utilizator care s-a conectat acum. Dacă
s-ar conecta un alt utilizator sau cheia ar fi invalidă, nu face nimic și programul continuă.
Consultați ghidul Cum să reveniți la pagina anterioară.
Canonizare
Presenterele au o caracteristică cu adevărat grozavă, care contribuie la un SEO mai bun (optimizarea găsirii pe internet).
Previn automat existența conținutului duplicat la URL-uri diferite. Dacă către o anumită țintă duc mai multe adrese URL,
de ex. /index
și /index?page=1
, framework-ul o determină pe una dintre ele ca fiind primară
(canonică) și le redirecționează pe celelalte către ea folosind codul HTTP 301. Datorită acestui fapt, motoarele de
căutare nu vă indexează paginile de două ori și nu le diluează page rank-ul.
Acest proces se numește canonizare. URL-ul canonic este cel generat de router, de regulă deci prima rută corespunzătoare din colecție.
Canonizarea este activată implicit și poate fi dezactivată prin $this->autoCanonicalize = false
.
Redirecționarea nu are loc la o cerere AJAX sau POST, deoarece s-ar pierde date sau nu ar avea valoare adăugată din punct de vedere SEO.
Canonizarea poate fi invocată și manual folosind metoda canonicalize()
, căreia i se transmit, similar metodei
link()
, presenterul, acțiunea și parametrii. Creează un link și îl compară cu URL-ul curent. Dacă diferă,
redirecționează către linkul generat.
public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// redirecționează, dacă $slug diferă de $realSlug
$this->canonicalize('Product:show', [$id, $realSlug]);
}
Evenimente
Pe lângă metodele startup()
, beforeRender()
și shutdown()
, care sunt apelate ca parte
a ciclului de viață al presenterului, pot fi definite și alte funcții care să fie apelate automat. Presenterul definește
așa-numitele evenimente, ale căror handlere le adăugați în
array-urile $onStartup
, $onRender
și $onShutdown
.
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct()
{
$this->onStartup[] = function () {
// ...
};
}
}
Handlerele din array-ul $onStartup
sunt apelate chiar înainte de metoda startup()
, apoi
$onRender
între beforeRender()
și render<View>()
și în final
$onShutdown
chiar înainte de shutdown()
.
Răspunsuri
Răspunsul returnat de presenter este un obiect care implementează interfața Nette\Application\Response. Există o serie de răspunsuri pregătite disponibile:
- Nette\Application\Responses\CallbackResponse – trimite un callback
- Nette\Application\Responses\FileResponse – trimite un fișier
- Nette\Application\Responses\ForwardResponse – forward()
- Nette\Application\Responses\JsonResponse – trimite JSON
- Nette\Application\Responses\RedirectResponse – redirecționare
- Nette\Application\Responses\TextResponse – trimite text
- Nette\Application\Responses\VoidResponse – răspuns gol
Răspunsurile sunt trimise prin metoda sendResponse()
:
use Nette\Application\Responses;
// Text simplu
$this->sendResponse(new Responses\TextResponse('Hello Nette!'));
// Trimite fișier
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));
// Răspunsul va fi un callback
$callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) {
if ($httpResponse->getHeader('Content-Type') === 'text/html') {
echo '<h1>Hello</h1>';
}
};
$this->sendResponse(new Responses\CallbackResponse($callback));
Restricționarea accesului folosind
#[Requires]
Atributul #[Requires]
oferă opțiuni avansate pentru restricționarea accesului la presentere și metodele lor.
Poate fi utilizat pentru specificarea metodelor HTTP, solicitarea unei cereri AJAX, restricționarea la aceeași origine (same
origin) și accesul doar prin forward. Atributul poate fi aplicat atât claselor presenterelor, cât și metodelor individuale
action<Action>()
, render<View>()
, handle<Signal>()
și
createComponent<Name>()
.
Puteți specifica aceste restricții:
- pentru metode HTTP:
#[Requires(methods: ['GET', 'POST'])]
- solicitarea unei cereri AJAX:
#[Requires(ajax: true)]
- acces doar din aceeași origine:
#[Requires(sameOrigin: true)]
- acces doar prin forward:
#[Requires(forward: true)]
- restricționare la acțiuni specifice:
#[Requires(actions: 'default')]
Detalii găsiți în ghidul Cum se utilizează atributul Requires.
Verificarea metodei HTTP
Presenterele din Nette verifică automat metoda HTTP a fiecărei cereri primite. Motivul acestei verificări este în principal
securitatea. Standard, sunt permise metodele GET
, POST
, HEAD
, PUT
,
DELETE
, PATCH
.
Dacă doriți să permiteți în plus, de exemplu, metoda OPTIONS
, utilizați atributul #[Requires]
(de la Nette Application v3.2):
#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
În versiunea 3.1, verificarea se face în checkHttpMethod()
, care verifică dacă metoda specificată în cerere
este inclusă în array-ul $presenter->allowedMethods
. Adăugarea metodei se face astfel:
class MyPresenter extends Nette\Application\UI\Presenter
{
protected function checkHttpMethod(): void
{
$this->allowedMethods[] = 'OPTIONS';
parent::checkHttpMethod();
}
}
Este important de subliniat că, dacă permiteți metoda OPTIONS
, trebuie ulterior să o gestionați
corespunzător în cadrul presenterului dvs. Metoda este adesea utilizată ca așa-numită cerere preflight, pe care browserul
o trimite automat înainte de cererea reală, când este necesar să se afle dacă cererea este permisă din punctul de vedere al
politicii CORS (Cross-Origin Resource Sharing). Dacă permiteți metoda, dar nu implementați un răspuns corect, acest lucru
poate duce la inconsecvențe și potențiale probleme de securitate.