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 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:

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>().

Iată un exemplu de utilizare pentru a restricționa accesul doar la metoda HTTP POST:

use Nette\Application\Attributes\Requires;

#[Requires(methods: 'POST')]
class MyPresenter extends Nette\Application\UI\Presenter
{
}

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')]

Condițiile pot fi combinate prin enumerarea mai multor atribute sau prin unirea lor într-unul singur:

#[Requires(methods: 'POST', ajax: true)]
public function actionDelete(int $id)
{
}

// or

#[Requires(methods: 'POST')]
#[Requires(ajax: true)]
public function actionDelete(int $id)
{
}

Verificarea metodei HTTP

Prezentatorii din Nette verifică automat metoda HTTP a fiecărei cereri primite. Motivul principal al acestei verificări este securitatea. Verificarea metodei se efectuează în checkHttpMethod(), care determină dacă metoda specificată în cerere este inclusă în matricea $presenter->allowedMethods. În mod implicit, această matrice conține elementele GET, POST, HEAD, , PUT, DELETE, PATCH, ceea ce înseamnă că aceste metode sunt permise.

Dacă doriți să permiteți în plus metoda OPTIONS, acest lucru poate fi realizat în felul 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.

Lecturi suplimentare

versiune: 4.0