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() și forward() redirecționări.
  • error() părăsește prezentatorul din cauza unei erori
  • sendJson($data) părăsește prezentatorul și trimite datele în format JSON
  • sendTemplate() părăsește prezentatorul și redă imediat șablonul
  • sendResponse($response) părăsește prezentatorul și trimite propriul răspuns
  • terminate() 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.

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 redirecționare temporară cu codul HTTP 302 sau 303:

$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 un alt URL din afara aplicației cu metoda redirectUrl():

$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 persistenți

Parametrii persistenți sunt transferați automat în legături. Acest lucru înseamnă că nu trebuie să îi specificăm în mod explicit în fiecare link() sau n:href din șablon, dar ei vor fi totuși transferați.

Dacă aplicația dvs. are mai multe versiuni lingvistice, atunci limba curentă este un parametru care trebuie să facă întotdeauna parte din URL. Și ar fi incredibil de obositor să îl menționați în fiecare link. Acest lucru nu este necesar cu Nette. Pur și simplu marcăm parametrul lang ca fiind persistent în acest mod:

class ProductPresenter extends Nette\Application\UI\Presenter
{
	/** @persistent */
	public $lang;
}

Dacă valoarea curentă a parametrului lang este 'en', atunci URL-ul creat cu link() sau n:href în șablon va conține lang=en. Minunat!

Cu toate acestea, putem adăuga și parametrul lang și, prin aceasta, putem schimba valoarea acestuia:

<a n:href="Product:show $id, lang: en">detail in English</a>

Sau, dimpotrivă, poate fi eliminat prin setarea la null:

<a n:href="Product:show $id, lang: null">click here</a>

Variabila persistentă trebuie să fie declarată ca fiind publică. De asemenea, putem specifica o valoare implicită. Dacă parametrul are aceeași valoare ca cea implicită, acesta nu va fi inclus în URL.

Persistența reflectă ierarhia claselor de prezentatori, astfel încât parametrul definit într-un anumit prezentator sau trăsătură este apoi transferat automat la fiecare prezentator care moștenește din acesta sau care utilizează aceeași trăsătură.

În PHP 8, puteți utiliza, de asemenea, atribute pentru a marca parametrii persistenți:

use Nette\Application\Attributes\Persistent;

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

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.

Cerință și parametri

Cererea gestionată de prezentator este obiectul Nette\Application\Request și este returnată de metoda prezentatorului getRequest(). Acesta include o matrice de parametri și fiecare dintre ei aparține fie unora dintre componente, fie direct prezentatorului (care este de fapt tot o componentă, deși una specială). Așadar, Nette redistribui parametrii și trece între componentele individuale (și prezentator) prin apelarea metodei loadState(array $params), care este descrisă în continuare în capitolul Componente. Parametrii pot fi obținuți prin metoda getParameters(): array, folosind individual getParameter($name). Valorile parametrilor sunt șiruri de caractere sau array-uri de șiruri de caractere, acestea fiind practic date brute obținute direct dintr-un URL.

Salvarea și restaurarea cererii

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 autentificare, salvăm cererea curentă în sesiune folosind $reqId = $this->storeRequest(), care returnează un identificator sub forma unui șir scurt și îl transmite ca parametru către prezentatorul de autentificare.

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:

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));

Lecturi suplimentare

versiune: 4.0