Prezentatori
Vom învăța cum să scriem prezentări și șabloane în Nette. După ce veți citi veți ști:
- cum funcționează prezentatorul
- ce sunt parametrii persistenți
- cum se redă un șablon
Știm deja că un prezentator este o clasă care reprezintă o anumită pagină a unei aplicații web, cum ar fi o pagină de pornire; un produs în magazinul electronic; un formular de înregistrare; un feed de hartă a site-ului etc. Aplicația poate avea de la unul până la mii de prezentatori. În alte framework-uri, aceștia sunt cunoscuți și sub denumirea de controlori.
De obicei, termenul de prezentator se referă la un descendent al clasei Nette\Application\UI\Presenter, care este potrivită pentru interfețele web și pe care o vom discuta în restul acestui capitol. În sens general, un prezentator este orice obiect care implementează interfața Nette\Application\IPresenter.
Ciclul de viață al unui prezentator
Sarcina prezentatorului este de a procesa cererea și de a returna un răspuns (care poate fi o pagină HTML, o imagine, o redirecționare etc.).
Așadar, la început este o cerere. Nu este direct o cerere HTTP, ci un obiect Nette\Application\Request în care a fost transformată cererea HTTP cu ajutorul unui router. De obicei, nu intrăm în contact cu acest obiect, deoarece prezentatorul deleagă în mod inteligent procesarea cererii către metode speciale, pe care le vom vedea acum.
Ciclul de viață al prezentatorului
Figura prezintă o listă de metode care sunt apelate secvențial de sus în jos, dacă există. Niciuna dintre ele nu trebuie să existe neapărat, putem avea un presenter complet gol, fără nicio metodă, și să construim un simplu web static pe el.
__construct()
Constructorul nu face parte exact din ciclul de viață al prezentatorului, deoarece este apelat în momentul creării obiectului. Dar îl menționăm datorită importanței sale. Constructorul (împreună cu metoda inject) este utilizat pentru a trece dependențele.
Prezentatorul nu trebuie să se ocupe de logica de afaceri a aplicației, să scrie și să citească din baza de date, să
efectueze calcule etc. Aceasta este sarcina pentru clasele dintr-un strat, pe care îl numim model. De exemplu, clasa
ArticleRepository
poate fi responsabilă de încărcarea și salvarea articolelor. Pentru ca prezentatorul să
o folosească, aceasta este transmisă folosind injecția de
dependență:
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articles,
) {
}
}
startup()
Imediat după primirea cererii, se invocă metoda startup ()
. Puteți să o utilizați pentru a inițializa
proprietățile, a verifica privilegiile utilizatorilor etc. Este necesar să se apeleze întotdeauna strămoșul
parent::startup()
.
action<Action>(args...)
Similar cu metoda render<View>()
. În timp ce render<View>()
este destinată să
pregătească datele pentru un anumit șablon, care este ulterior redat, în action<Action>()
o solicitare
este procesată fără a fi urmată de redarea șablonului. De exemplu, datele sunt procesate, un utilizator este logat sau
deconectat și așa mai departe, iar apoi se redirecționează în altă parte.
Este important ca action<Action>()
este apelat înainte de render<View>()
, astfel încât
în interiorul acesteia să putem eventual să schimbăm următorul curs al ciclului de viață, adică să schimbăm șablonul
care va fi redat și, de asemenea, metoda render<View>()
care va fi apelată, folosind
setView('otherView')
.
Parametrii din cerere sunt trecuți către metodă. Este posibil și recomandat să se precizeze tipurile pentru parametri, de
exemplu actionShow(int $id, ?string $slug = null)
– dacă parametrul id
lipsește sau dacă nu este
un număr întreg, prezentatorul returnează eroarea 404 și încheie operațiunea.
handle<Signal>(args...)
Această metodă procesează așa-numitele semnale, pe care le vom discuta în capitolul despre componente. Ea este destinată în principal componentelor și procesării cererilor AJAX.
Parametrii sunt pasați metodei, ca în cazul lui action<Action>()
, inclusiv verificarea tipului.
beforeRender()
Metoda beforeRender
, după cum sugerează și numele, este apelată înainte de fiecare metodă
render<View>()
. Este utilizată pentru configurarea obișnuită a șabloanelor, trecerea variabilelor pentru
aspect și așa mai departe.
render<View>(args...)
Locul în care pregătim șablonul pentru redarea ulterioară, îi transmitem date etc.
Parametrii sunt trecuți la metodă, ca în cazul lui 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
, după cum sugerează și numele, este apelată după fiecare render<View>()
metodă. Ea este utilizată destul de rar.
shutdown()
Este apelată la sfârșitul ciclului de viață al prezentatorului.
Bun sfat înainte de a merge mai departe. După cum vedeți, prezentatorul poate gestiona mai multe acțiuni/viziuni,
adică poate avea mai multe metode. render<View>()
. Dar recomandăm proiectarea de prezentatoare cu una sau
cât mai puține acțiuni.
Trimiterea unui răspuns
Răspunsul prezentatorului este, de obicei, redarea șablonului cu pagina HTML, dar poate fi, de asemenea, trimiterea unui fișier, JSON sau chiar redirecționarea către o altă pagină.
În orice moment în timpul ciclului de viață, puteți utiliza oricare dintre următoarele metode pentru a trimite un răspuns și a ieși din prezentator în același timp:
redirect()
,redirectPermanent()
,redirectUrl()
șiforward()
redirecționări.error()
părăsește prezentatorul din cauza unei erorisendJson($data)
părăsește prezentatorul și trimite datele în format JSONsendTemplate()
părăsește prezentatorul și redă imediat șablonulsendResponse($response)
părăsește prezentatorul și trimite propriul răspunsterminate()
părăsește prezentatorul fără răspuns
Dacă nu apelați niciuna dintre aceste metode, prezentatorul va proceda automat la redarea șablonului. De ce? Ei bine, pentru că în 99% din cazuri dorim să desenăm un șablon, așa că prezentatorul ia acest comportament ca fiind implicit și dorește să ne ușureze munca.
Crearea legăturilor
Prezentatorul are o metodă link()
, care este utilizată pentru a crea legături URL către alți prezentatori.
Primul parametru este prezentatorul și acțiunea țintă, urmat de argumente, care pot fi transmise sub formă de matrice:
$url = $this->link('Product:show', $id);
$url = $this->link('Product:show', [$id, 'lang' => 'en']);
În șablon, creăm legături către alți prezentatori și acțiuni după cum urmează:
<a n:href="Product:show $id">product detail</a>
Pur și simplu scrieți perechea cunoscută Presenter:action
în locul URL-ului real și includeți orice
parametru. Trucul este n:href
, care spune că acest atribut va fi procesat de Latte și generează un URL real. În
Nette, nu trebuie să vă gândiți deloc la URL-uri, ci doar la prezentatori și acțiuni.
Pentru mai multe informații, consultați Crearea de legături.
Redirecționare
Metodele redirect()
și forward()
sunt utilizate pentru a trece la un alt prezentator, care au
o sintaxă foarte asemănătoare cu cea a metodei link().
Metoda forward()
trece imediat la noul prezentator, 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 de solicitare curentă este POST):
$this->redirect('Product:show', $id);
Pentru a obține o redirecționare permanentă cu codul HTTP 301, utilizați:
$this->redirectPermanent('Product:show', $id);
Puteți redirecționa către o altă adresă URL din afara aplicației utilizând metoda redirectUrl()
. Codul
HTTP poate fi specificat ca al doilea parametru, valoarea implicită fiind 302 (sau 303, dacă metoda de solicitare curentă
este POST):
$this->redirectUrl('https://nette.org');
Redirecționarea încheie imediat ciclul de viață al prezentatorului prin aruncarea așa-numitei excepții de terminare
silențioasă Nette\Application\AbortException
.
Înainte de redirecționare, este posibil să se trimită un mesaj flash, mesaje care vor fi afișate în șablon după redirecționare.
Mesaje flash
Acestea sunt mesaje care, de obicei, informează despre rezultatul unei operații. O caracteristică importantă a mesajelor flash este că acestea sunt disponibile în șablon chiar și după redirecționare. Chiar și după ce au fost afișate, ele vor rămâne în viață încă 30 de secunde – de exemplu, în cazul în care utilizatorul ar reîmprospăta involuntar pagina – mesajul nu se va pierde.
Trebuie doar să apelați metoda flashMessage() și
presenter se va ocupa de transmiterea mesajului către șablon. Primul argument este textul mesajului, iar al doilea argument
opțional este tipul acestuia (error, warning, info etc.). Metoda flashMessage()
returnează o instanță de mesaj
flash, pentru a ne permite să adăugăm mai multe informații.
$this->flashMessage('Item was removed.');
$this->redirect(/* ... */);
În șablon, aceste mesaje sunt disponibile în variabila $flashes
sub forma unor obiecte stdClass
,
care conțin proprietățile message
(textul mesajului), type
(tipul mesajului) și pot conține
informațiile despre utilizator deja menționate. Le desenăm după cum urmează:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Eroare 404 etc.
Atunci când nu putem îndeplini cererea deoarece, de exemplu, articolul pe care dorim să îl afișăm nu există în baza de
date, vom arunca eroarea 404 folosind metoda error(?string $message = null, int $httpCode = 404)
, care reprezintă
eroarea HTTP 404:
public function renderShow(int $id): void
{
$article = $this->articles->getById($id);
if (!$article) {
$this->error();
}
// ...
}
Codul de eroare HTTP poate fi trecut ca al doilea parametru, valoarea implicită fiind 404. Metoda funcționează prin
aruncarea excepției Nette\Application\BadRequestException
, după care Application
transmite controlul
către prezentatorul de erori. Care este un prezentator a cărui sarcină este de a afișa o pagină de informare cu privire la
eroare. Error-preseter este setat în configurația aplicației.
Trimiterea JSON
Exemplu de metodă de acțiune care trimite date în format JSON și iese din prezentator:
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
Parametrii cererii
Prezentatorul, ca și fiecare componentă, își obține parametrii din cererea HTTP. Valorile acestora pot fi recuperate cu
ajutorul metodei getParameter($name)
sau getParameters()
. Valorile sunt șiruri de caractere sau rețele
de șiruri de caractere, în esență date brute obținute direct din URL.
Pentru mai multă comoditate, recomandăm ca parametrii să fie accesibili prin intermediul proprietăților. Este suficient
să îi adnotați cu ajutorul caracterului #[Parameter]
atribut:
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 proprietăți, sugerăm să se precizeze tipul de date (de exemplu, string
). Apoi, Nette va transforma
automat valoarea pe baza acestuia. Valorile parametrilor pot fi, de asemenea, validate.
Atunci când creați o legătură, puteți seta direct valoarea parametrilor:
<a n:href="Home:default theme: dark">click</a>
Parametrii 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 de sesiune, aceștia sunt trecuți în URL. Acest lucru este
complet automat, astfel încât nu este nevoie să îi menționăm în mod explicit în link()
sau
n:href
.
Exemplu de utilizare? Aveți o aplicație multilingvă. Limba actuală este un parametru care trebuie să facă parte în
permanență din URL. Dar ar fi incredibil de anevoios să îl includeți în fiecare link. Așa că îl transformați într-un
parametru persistent numit lang
și se va păstra singur. E foarte bine!
Crearea unui parametru persistent este extrem de ușoară în Nette. Trebuie doar să creați o proprietate publică și să
o marcați cu atributul: (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 publice
}
Dacă $this->lang
are o valoare, cum ar fi 'en'
, atunci legăturile create folosind
link()
sau n:href
vor conține și parametrul lang=en
. Iar atunci când se face clic pe
link, acesta va fi din nou $this->lang = 'en'
.
Pentru proprietăți, vă recomandăm să includeți tipul de date (de exemplu, string
) și puteți include și
o valoare implicită. Valorile parametrilor pot fi validate.
Parametrii persistenți sunt trecuți în mod implicit între toate acțiunile unui anumit prezentator. Pentru a-i transmite între mai mulți prezentatori, trebuie să îi definiți fie:
- într-un strămoș comun din care moștenesc prezentatorii
- în trăsătura pe care o utilizează prezentatorii:
trait LanguageAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LanguageAware;
}
Puteți modifica valoarea unui parametru persistent atunci când creați o legătură:
<a n:href="Product:show $id, lang: cs">detail in Czech</a>
Sau poate fi restat, adică eliminat din URL. În acest caz, va lua valoarea implicită:
<a n:href="Product:show $id, lang: null">click</a>
Componente interactive
Prezentatorii au un sistem de componente încorporat. Componentele sunt unități separate reutilizabile pe care le introducem în prezentatoare. Ele pot fi formulare, datagrids, meniuri, de fapt orice lucru care are sens să fie folosit în mod repetat.
Cum se plasează componentele și cum sunt utilizate ulterior în prezentator? Acest lucru este explicat în capitolul Componente. Veți afla chiar și ce legătură au acestea cu Hollywood.
De unde pot obține niște componente? La pagina Componette puteți găsi câteva componente open-source și alte add-on-uri pentru Nette, care sunt realizate și partajate de comunitatea Nette Framework.
Aprofundare
Ceea ce am arătat până acum în acest capitol va fi probabil suficient. Rândurile următoare sunt destinate celor care sunt interesați de prezentatori în profunzime și doresc să știe totul.
Validarea parametrilor
Valorile parametrilor de cerere și ale parametrilor
persistenți primiți de la URL-uri sunt scrise în proprietăți prin metoda loadState()
. Aceasta verifică, de
asemenea, dacă tipul de date specificat în proprietate corespunde, în caz contrar se va răspunde cu o eroare 404 și pagina
nu va fi afișată.
Nu vă încredeți niciodată orbește în parametri, deoarece aceștia pot fi ușor suprascriși de către utilizator în URL.
De exemplu, iată cum verificăm dacă $this->lang
se află printre limbile acceptate. O modalitate bună de a
face acest lucru este să suprascrieți metoda loadState()
menționată mai sus:
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
public function loadState(array $params): void
{
parent::loadState($params); // aici este setat $this->lang
// urmează verificarea valorii utilizatorului:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
Salvarea și restaurarea cererii
Cererea pe care o gestionează prezentatorul este un obiect Nette\Application\Request și este returnată de
metoda prezentatorului getRequest()
.
Puteți salva cererea curentă într-o sesiune sau o puteți restaura din sesiune și permite prezentatorului să o execute
din nou. Acest lucru este util, de exemplu, atunci când un utilizator completează un formular și login-ul său expiră. Pentru
a nu pierde date, înainte de redirecționarea către pagina de conectare, salvăm cererea curentă în sesiune cu ajutorul
aplicației $reqId = $this->storeRequest()
, care returnează un identificator sub forma unui șir scurt și îl
transmite ca parametru către prezentatorul de conectare.
După conectare, apelăm metoda $this->restoreRequest($reqId)
, care preia cererea din sesiune și o transmite
acesteia. Metoda verifică dacă cererea a fost creată de același utilizator ca și cel care s-a logat acum este. Dacă un alt
utilizator se conectează sau dacă cheia nu este validă, nu face nimic și programul continuă.
Consultați cartea de bucate Cum să vă întoarceți la o pagină anterioară.
Canonizare
Prezentatorii au o caracteristică foarte bună care îmbunătățește SEO (optimizarea capacității de căutare pe
Internet). Acestea previn în mod automat existența conținutului duplicat la diferite URL-uri. Dacă mai multe URL-uri duc la
o anumită destinație, de exemplu /index
și /index?page=1
, cadrul desemnează unul dintre ele ca
fiind primar (canonic) și le redirecționează pe celelalte către acesta folosind codul HTTP 301. Datorită acestui lucru,
motoarele de căutare nu indexează paginile de două ori și nu le slăbește page rank-ul.
Acest proces se numește canonizare. URL-ul canonic este URL-ul generat de router, de obicei prima rută adecvată din colecție.
Canonizarea este activată în mod implicit și poate fi dezactivată prin intermediul
$this->autoCanonicalize = false
.
Redirecționarea nu are loc cu o cerere AJAX sau POST, deoarece ar duce la pierderea de date sau nu ar avea nicio valoare adăugată SEO.
De asemenea, puteți invoca manual canonizarea folosind metoda canonicalize()
, care, ca și metoda
link()
, primește ca argumente prezentatorul, acțiunile și parametrii. Aceasta creează o legătură și
o compară cu URL-ul curent. Dacă este diferit, redirecționează către link-ul generat.
public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// redirecționează dacă $slug este diferit 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 prezentatorului, pot fi definite și alte funcții care să fie apelate automat. Prezentatorul definește
așa-numitele evenimente, iar dumneavoastră adăugați gestionarii
acestora la array-urile $onStartup
, $onRender
și $onShutdown
.
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct()
{
$this->onStartup[] = function () {
// ...
};
}
}
Manipulatorii din array-ul $onStartup
sunt apelați chiar înainte de metoda startup()
, apoi
$onRender
între beforeRender()
și render<View>()
și, în sfârșit,
$onShutdown
chiar înainte de shutdown()
.
Răspunsuri
Răspunsul returnat de către prezentator este un obiect care implementează interfața Nette\Application\Response. Există o serie de răspunsuri gata făcute:
- Nette\Application\Responses\CallbackResponse – trimite un callback
- Nette\Application\Responses\FileResponse – trimite fișierul
- Nette\Application\Responses\ForwardResponse – transmite ()
- Nette\Application\Responses\JsonResponse – trimite JSON
- Nette\Application\Responses\RedirectResponse – redirecționează
- 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 un fișier
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));
// Trimite 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ții de acces Utilizarea #[Requires]
Aplicația #[Requires]
oferă opțiuni avansate pentru restricționarea accesului la prezentatori și la metodele
acestora. Acesta poate fi utilizat pentru a specifica metodele HTTP, pentru a solicita cereri AJAX, pentru a limita accesul la
aceeași origine și pentru a restricționa accesul doar la redirecționare. Atributul poate fi aplicat atât claselor de
prezentatori, cât și metodelor individuale, cum ar fi action<Action>()
, render<View>()
,
handle<Signal>()
, și createComponent<Name>()
.
Puteți specifica aceste restricții:
- pe metodele HTTP:
#[Requires(methods: ['GET', 'POST'])]
- care necesită o cerere AJAX:
#[Requires(ajax: true)]
- accesul numai de la aceeași origine:
#[Requires(sameOrigin: true)]
- acces numai prin redirecționare:
#[Requires(forward: true)]
- restricții privind anumite acțiuni:
#[Requires(actions: 'default')]
Pentru detalii, consultați Cum se utilizează Requires.
Verificarea metodei HTTP
În Nette, prezentatorii verifică automat metoda HTTP a fiecărei cereri primite, în primul rând din motive de securitate.
În mod implicit, sunt permise metodele GET
, POST
, HEAD
, PUT
,
DELETE
, PATCH
.
Dacă doriți să activați metode suplimentare, cum ar fi OPTIONS
, puteți utiliza opțiunea
#[Requires]
attribute (din aplicația 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 efectuează în checkHttpMethod()
, care verifică dacă metoda specificată în
cerere este inclusă în matricea $presenter->allowedMethods
. Adăugați o metodă de genul următor:
class MyPresenter extends Nette\Application\UI\Presenter
{
protected function checkHttpMethod(): void
{
$this->allowedMethods[] = 'OPTIONS';
parent::checkHttpMethod();
}
}
Este esențial să subliniem faptul că, dacă activați metoda OPTIONS
, trebuie să o gestionați în mod
corespunzător și în prezentator. Această metodă este adesea utilizată ca o așa-numită cerere de preflight, pe care
browserele o trimit automat înainte de cererea efectivă atunci când este necesar să se determine dacă cererea este permisă
din punctul de vedere al politicii CORS (Cross-Origin Resource Sharing). Dacă permiteți această metodă, dar nu implementați
un răspuns adecvat, aceasta poate duce la inconsecvențe și la potențiale probleme de securitate.