Predavatelji

Naučili se bomo pisati predstavnike in predloge v Nette. Po branju boste vedeli:

  • kako deluje predstavnik
  • kaj so trajni parametri
  • kako izrisati predlogo

Vemo že, da je predstavnik razred, ki predstavlja določeno stran spletne aplikacije, na primer domačo stran, izdelek v e-trgovini, obrazec za prijavo, vir zemljevida spletne strani itd. Aplikacija ima lahko od enega do več tisoč predstavnikov. V drugih ogrodjih so znani tudi kot krmilniki.

Običajno se izraz presenter nanaša na potomca razreda Nette\Application\UI\Presenter, ki je primeren za spletne vmesnike in ga bomo obravnavali v nadaljevanju tega poglavja. V splošnem smislu je predstavnik vsak objekt, ki implementira vmesnik Nette\Application\IPresenter.

Življenjski cikel predstavnika

Naloga predstavnika je obdelati zahtevo in vrniti odgovor (ki je lahko stran HTML, slika, preusmeritev itd.).

Na začetku je torej zahteva. To ni neposredno zahteva HTTP, temveč predmet Nette\Application\Request, v katerega je bila zahteva HTTP pretvorjena s pomočjo usmerjevalnika. S tem objektom običajno ne pridemo v stik, saj predstavnik obdelavo zahteve spretno prenese na posebne metode, ki si jih bomo zdaj ogledali.

***Življenjski cikel predstavnika*

Na sliki je prikazan seznam metod, ki se kličejo zaporedno od zgoraj navzdol, če obstajajo. Nobeni od njih ni treba obstajati, lahko imamo popolnoma prazen predstavnik brez ene same metode in na njem zgradimo preprost statični splet.

__construct()

Konstruktor ne sodi ravno v življenjski cikel predstavnika, saj se pokliče v trenutku ustvarjanja predmeta. Vendar ga omenjamo zaradi njegove pomembnosti. Konstruktor (skupaj z metodo inject) se uporablja za posredovanje odvisnosti.

Predstavnik ne sme skrbeti za poslovno logiko aplikacije, pisati in brati iz podatkovne zbirke, izvajati izračunov itd. To je naloga za razrede iz plasti, ki jo imenujemo model. Na primer, razred ArticleRepository je lahko odgovoren za nalaganje in shranjevanje člankov. Da bi ga lahko predstavnik uporabljal, ga posredujemo z uporabo vbrizgavanja odvisnosti:

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ArticleRepository $articles,
	) {
	}
}

startup()

Takoj po prejemu zahteve se sproži metoda startup (). Uporabite jo lahko za inicializacijo lastnosti, preverjanje uporabniških pravic itd. Vedno je treba poklicati prednika parent::startup().

action<Action>(args...)

Podobno kot pri metodi render<View>(). Medtem ko render<View>() je namenjena pripravi podatkov za določeno predlogo, ki se nato izriše, v action<Action>() se zahteva obdela brez naknadnega upodabljanja predloge. Tako se na primer obdelajo podatki, uporabnik se prijavi ali odjavi in podobno, nato pa se preusmeri drugam.

Pomembno je, da action<Action>() se pokliče pred render<View>(), tako da lahko znotraj njega morebiti spremenimo naslednji potek življenjskega cikla, tj. spremenimo predlogo, ki se bo izrisala, in tudi metodo render<View>() ki bo poklicana, z uporabo setView('otherView').

Parametri iz zahteve se posredujejo metodi. Za parametre je mogoče in priporočljivo določiti tipe, npr. actionShow(int $id, ?string $slug = null) – če parameter id manjka ali če ni celo število, predstavnik vrne napako 404 in zaključi operacijo.

handle<Signal>(args...)

Ta metoda obdeluje tako imenovane signale, ki jih bomo obravnavali v poglavju o komponentah. Namenjena je predvsem komponentam in obdelavi zahtevkov AJAX.

Metodi se posredujejo parametri, tako kot v primeru action<Action>(), vključno s preverjanjem tipa.

beforeRender()

Metoda beforeRender, kot pove že ime, se pokliče pred vsako metodo render<View>(). Uporablja se za skupno konfiguracijo predloge, posredovanje spremenljivk za postavitev in tako naprej.

render<View>(args...)

Mesto, kjer pripravimo predlogo za nadaljnje upodabljanje, ji posredujemo podatke itd.

Parametri se posredujejo metodi, kot v primeru action<Action>(), vključno s preverjanjem tipa.

public function renderShow(int $id): void
{
	// pridobimo podatke iz modela in jih posredujemo predlogi
	$this->template->article = $this->articles->getById($id);
}

afterRender()

Metoda afterRender, kot je razvidno iz imena, se pokliče po vsakem render<View>() metodi. Uporablja se precej redko.

shutdown()

Pokliče se na koncu življenjskega cikla predstavnika.

Dober nasvet, preden se premaknemo naprej. Kot lahko vidite, lahko predstavnik obdeluje več akcij/ogledov, tj. ima več metod render<View>(). Vendar priporočamo, da predstavnike oblikujete z enim dejanjem ali čim manjšim številom dejanj.

Pošiljanje odziva

Odziv predstavnika je običajno prikaz predloge s stranjo HTML, lahko pa je tudi pošiljanje datoteke, JSON ali celo preusmeritev na drugo stran.

Kadar koli med življenjskim ciklom lahko uporabite katero koli od naslednjih metod za pošiljanje odziva in hkratni izhod iz predstavitvenega programa:

  • redirect(), redirectPermanent(), redirectUrl() in forward() preusmeritve
  • error() zaradi napake zaključi predstavitveni program
  • sendJson($data) zapusti predstavitveno orodje in pošlje podatke v obliki JSON
  • sendTemplate() zapusti predstavitveno orodje in takoj izriše predlogo.
  • sendResponse($response) zaključi predstavitev in pošlje lasten odgovor
  • terminate() brez odgovora zapusti predstavitveni program

Če ne pokličete nobene od teh metod, bo predstavitveni program samodejno nadaljeval z upodabljanjem predloge. Zakaj? No, ker v 99 % primerov želimo izrisati predlogo, zato predstavnik to obnašanje vzame kot privzeto in nam želi olajšati delo.

Presenter ima metodo link(), ki se uporablja za ustvarjanje povezav URL do drugih presenterjev. Prvi parameter je ciljni predstavnik in dejanje, sledijo pa mu argumenti, ki so lahko posredovani kot polje:

$url = $this->link('Product:show', $id);

$url = $this->link('Product:show', [$id, 'lang' => 'en']);

V predlogi ustvarimo povezave do drugih predstavnikov in akcij na naslednji način:

<a n:href="Product:show $id">product detail</a>

Preprosto zapišite znani par Presenter:action namesto pravega URL in vključite vse parametre. Trik je n:href, ki pove, da bo ta atribut obdelal Latte in ustvaril pravi URL. V programu Nette vam sploh ni treba razmišljati o naslovih URL, temveč le o predstavnikih in akcijah.

Za več informacij glejte Ustvarjanje povezav.

Preusmeritev

Metodi redirect() in forward() se uporabljata za preskok na drug predvajalnik in imata zelo podobno sintakso kot metoda link().

Metoda forward() takoj preklopi na novi predstavnik brez preusmeritve HTTP:

$this->forward('Product:show');

Primer tako imenovane začasne preusmeritve s kodo HTTP 302 (ali 303, če je trenutni način zahteve POST):

$this->redirect('Product:show', $id);

Za trajno preusmeritev s kodo HTTP 301 uporabite:

$this->redirectPermanent('Product:show', $id);

Z metodo redirectUrl() lahko preusmerite na drug naslov URL zunaj aplikacije. Kodo HTTP lahko določite kot drugi parameter, pri čemer je privzeta vrednost 302 (ali 303, če je trenutna metoda zahteve POST):

$this->redirectUrl('https://nette.org');

Preusmeritev nemudoma prekine življenjski cikel predvajalnika, tako da vrže tako imenovano izjemo tihega zaključka Nette\Application\AbortException.

Pred preusmeritvijo je mogoče poslati bliskovito sporočilo, sporočila, ki bodo prikazana v predlogi po preusmeritvi.

Bliskovita sporočila

To so sporočila, ki običajno obveščajo o rezultatu operacije. Pomembna lastnost bliskovitih sporočil je, da so v predlogi na voljo tudi po preusmeritvi. Tudi po prikazu bodo ostala živa še 30 sekund – na primer, če bi uporabnik nenamerno osvežil stran – sporočilo se ne bo izgubilo.

Samo pokličite metodo flashMessage() in presenter bo poskrbel za posredovanje sporočila predlogi. Prvi argument je besedilo sporočila, drugi neobvezni argument pa je njegova vrsta (napaka, opozorilo, informacija itd.). Metoda flashMessage() vrne primerek sporočila flash, da lahko dodamo več informacij.

$this->flashMessage('Item was removed.');
$this->redirect(/* ... */);

V predlogi so ta sporočila na voljo v spremenljivki $flashes kot objekti stdClass, ki vsebujejo lastnosti message (besedilo sporočila), type (vrsta sporočila) in lahko vsebujejo že omenjene podatke o uporabniku. Narišemo jih na naslednji način:

{foreach $flashes as $flash}
	<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}

Napaka 404 itd.

Kadar ne moremo izpolniti zahteve, ker na primer članek, ki ga želimo prikazati, ne obstaja v zbirki podatkov, bomo z metodo error(?string $message = null, int $httpCode = 404), ki predstavlja napako HTTP 404, vrgli napako 404:

public function renderShow(int $id): void
{
	$article = $this->articles->getById($id);
	if (!$article) {
		$this->error();
	}
	// ...
}

Koda napake HTTP se lahko posreduje kot drugi parameter, privzeta vrednost je 404. Metoda deluje tako, da vrže izjemo Nette\Application\BadRequestException, nakar Application preda nadzor predstavniku napake. To je predstavnik, katerega naloga je prikazati stran, ki obvešča o napaki. Predvajalnik napak je nastavljen v konfiguraciji aplikacije.

Pošiljanje JSON

Primer akcijske metode, ki pošlje podatke v obliki JSON in zaključi predstavitev:

public function actionData(): void
{
	$data = ['hello' => 'nette'];
	$this->sendJson($data);
}

Parametri zahtevka

Predstavitelj in vsaka komponenta pridobi svoje parametre iz zahteve HTTP. Njihove vrednosti lahko pridobite z metodo getParameter($name) ali getParameters(). Vrednosti so nizi ali nizi nizov, v bistvu surovi podatki, pridobljeni neposredno iz URL.

Zaradi večjega udobja priporočamo, da so parametri dostopni prek lastnosti. Preprosto jih opišite z ukazom #[Parameter] atributom:

use Nette\Application\Attributes\Parameter;  // ta vrstica je pomembna

class HomePresenter extends Nette\Application\UI\Presenter
{
	#[Parameter]
	public string $theme; // mora biti javna.
}

Za lastnosti predlagamo, da navedete vrsto podatkov (npr. string). Nette bo na podlagi tega samodejno določil vrednost. Vrednosti parametrov je mogoče tudi potrditi.

Pri ustvarjanju povezave lahko neposredno določite vrednost za parametre:

<a n:href="Home:default theme: dark">click</a>

Trajni parametri

Trajni parametri se uporabljajo za ohranjanje stanja med različnimi zahtevami. Njihova vrednost ostane enaka tudi po kliku na povezavo. Za razliko od podatkov seje se posredujejo v naslovu URL. To poteka povsem samodejno, zato jih ni treba izrecno navesti v link() ali n:href.

Primer uporabe? Imate večjezično aplikacijo. Dejanski jezik je parameter, ki mora biti vedno del naslova URL. Vendar bi bilo izjemno zamudno, če bi ga vključili v vsako povezavo. Zato ga naredite za trajni parameter z imenom lang, ki se bo prenašal sam. Super!

Ustvarjanje trajnega parametra je v Nette izjemno enostavno. Preprosto ustvarite javno lastnost in jo označite z atributom: (prej se je uporabljal /** @persistent */ ).

use Nette\Application\Attributes\Persistent; // ta vrstica je pomembna

class ProductPresenter extends Nette\Application\UI\Presenter
{
	#[Persistent]
	public string $lang; // morajo biti javni.
}

Če ima $this->lang vrednost, kot je 'en', bodo povezave, ustvarjene z uporabo link() ali n:href, vsebovale tudi parameter lang=en. In ko boste povezavo kliknili, bo ta spet imela vrednost $this->lang = 'en'.

Za lastnosti priporočamo, da vključite podatkovno vrsto (npr. string), vključite pa lahko tudi privzeto vrednost. Vrednosti parametrov je mogoče potrditi.

Trajni parametri se privzeto posredujejo med vsemi dejanji določenega predstavnika. Če jih želite posredovati med več predstavniki, jih morate opredeliti:

  • v skupnem predniku, od katerega predstavniki dedujejo
  • v lastnosti, ki jo uporabljajo predstavniki:
trait LanguageAware
{
	#[Persistent]
	public string $lang;
}

class ProductPresenter extends Nette\Application\UI\Presenter
{
	use LanguageAware;
}

Pri ustvarjanju povezave lahko spremenite vrednost trajnega parametra:

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

Lahko ga tudi resetirate, tj. odstranite iz URL-ja. Nato bo prevzel privzeto vrednost:

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

Interaktivne komponente

Predstavitveni programi imajo vgrajen sistem komponent. Komponente so ločene enote za večkratno uporabo, ki jih namestimo v predstavnike. To so lahko obrazci, podatkovne mreže, meniji, pravzaprav vse, kar je smiselno večkrat uporabiti.

Kako se komponente namestijo in nato uporabljajo v predstavitvenem programu? To je razloženo v poglavju Komponente. Izvedeli boste celo, kaj imajo skupnega s Hollywoodom.

Kje lahko dobim komponente? Na strani Komponente lahko najdete nekaj odprtokodnih komponent in drugih dodatkov za Nette, ki jih je izdelala in delila skupnost ogrodja Nette.

Poglobitev

To, kar smo doslej prikazali v tem poglavju, bo verjetno zadostovalo. Naslednje vrstice so namenjene tistim, ki jih predstavniki zanimajo poglobljeno in želijo vedeti vse.

Potrjevanje parametrov

Vrednosti parametrov zahteve in trajnih parametrov, prejetih z naslovov URL, se zapišejo v lastnosti z metodo loadState(). Preveri tudi, ali se podatkovna vrsta, navedena v lastnosti, ujema, sicer se odzove z napako 404 in stran se ne prikaže.

Parametrom nikoli ne zaupajte na slepo, saj jih lahko uporabnik zlahka prepiše v naslovu URL. Tako na primer preverimo, ali je $this->lang med podprtimi jeziki. Dober način za to je, da prekrijete zgoraj omenjeno metodo loadState():

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

	public function loadState(array $params): void
	{
		parent::loadState($params); // tukaj je nastavljen $this->lang
		// sledi preverjanju uporabniške vrednosti:
		if (!in_array($this->lang, ['en', 'cs'])) {
			$this->error();
		}
	}
}

Shranjevanje in obnavljanje zahtevka

Zahteva, ki jo predstavnik obravnava, je objekt Nette\Application\Request in jo vrne predstavnikova metoda getRequest().

Trenutni zahtevek lahko shranite v sejo ali pa ga obnovite iz seje in omogočite predstavniku, da ga ponovno izvede. To je na primer uporabno, ko uporabnik izpolni obrazec in mu poteče prijava. Da ne bi izgubili podatkov, pred preusmeritvijo na stran za prijavo shranimo trenutno zahtevo v sejo z uporabo $reqId = $this->storeRequest(), ki vrne identifikator v obliki kratkega niza in ga kot parameter posreduje predstavniku za prijavo.

Po prijavi pokličemo metodo $this->restoreRequest($reqId), ki prevzame zahtevo iz seje in ji jo posreduje naprej. Metoda preveri, ali je zahtevo ustvaril isti uporabnik, kot je zdaj prijavljeni. Če se prijavi drug uporabnik ali je ključ neveljaven, ne stori ničesar in program se nadaljuje.

Oglejte si kuharsko knjigo Kako se vrniti na prejšnjo stran.

Kanonizacija

Predstavniki imajo eno res odlično funkcijo, ki izboljšuje SEO (optimizacijo iskanja na internetu). Samodejno preprečujejo obstoj podvojene vsebine na različnih naslovih URL. Če na določen cilj vodi več naslovov URL, npr. /index in /index?page=1, ogrodje enega od njih označi kot primarnega (kanoničnega) in nanj preusmeri druge z uporabo kode HTTP 301. Zaradi tega iskalniki strani ne indeksirajo dvakrat in ne oslabijo njihovega ranga.

Ta postopek se imenuje kanonizacija. Kanonični URL je URL, ki ga ustvari usmerjevalnik, običajno prva ustrezna pot v zbirki.

Kanonizacija je privzeto vklopljena in jo lahko izklopite prek spletne strani $this->autoCanonicalize = false.

Preusmeritev se ne izvede pri zahtevi AJAX ali POST, ker bi povzročila izgubo podatkov ali ne bi imela dodane vrednosti SEO.

Kanonizacijo lahko sprožite tudi ročno z metodo canonicalize(), ki tako kot metoda link() kot argumente prejme predstavnika, dejanja in parametre. Ustvari povezavo in jo primerja s trenutnim naslovom URL. Če se razlikuje, preusmeri na ustvarjeno povezavo.

public function actionShow(int $id, ?string $slug = null): void
{
	$realSlug = $this->facade->getSlugForId($id);
	// preusmeri, če se $slug razlikuje od $realSlug
	$this->canonicalize('Product:show', [$id, $realSlug]);
}

Dogodki

Poleg metod startup(), beforeRender() in shutdown(), ki se kličejo kot del življenjskega cikla predstavitve, lahko določite tudi druge funkcije, ki se kličejo samodejno. Predstavitelj definira tako imenovane dogodke, njihove izvajalce pa dodate v polja $onStartup, $onRender in $onShutdown.

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	public function __construct()
	{
		$this->onStartup[] = function () {
			// ...
		};
	}
}

Obdelovalci v polju $onStartup se pokličejo tik pred metodo startup(), nato $onRender med beforeRender() in render<View>() in nazadnje $onShutdown tik pred shutdown().

Odzivi

Odziv, ki ga vrne predstavitelj, je objekt, ki implementira vmesnik Nette\Application\Response. Na voljo je več pripravljenih odgovorov:

Odgovori se pošljejo z metodo sendResponse():

use Nette\Application\Responses;

// Preprosto besedilo
$this->sendResponse(new Responses\TextResponse('Hello Nette!'));

// Pošlje datoteko
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));

// Pošlje povratni klic
$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));

Omejitev dostopa z uporabo #[Requires]

. #[Requires] atribut zagotavlja napredne možnosti za omejevanje dostopa do predavateljev in njihovih metod. Z njim lahko določite metode HTTP, zahtevate zahteve AJAX, omejite dostop do istega izvora in omejite dostop samo na posredovanje. Atribut je mogoče uporabiti za razrede predstavnikov in posamezne metode, kot so action<Action>(), render<View>(), handle<Signal>(), in createComponent<Name>().

Določite lahko te omejitve:

  • za metode HTTP: #[Requires(methods: ['GET', 'POST'])]
  • ki zahteva zahtevo AJAX: #[Requires(ajax: true)]
  • dostop samo iz istega izvora: #[Requires(sameOrigin: true)]
  • dostop samo prek posredovanja: #[Requires(forward: true)]
  • omejitve za določena dejanja: #[Requires(actions: 'default')]

Za podrobnosti glejte Kako uporabljati Requires atribut.

Preverjanje metode HTTP

V omrežju Nette predstavniki samodejno preverijo metodo HTTP vsake prejete zahteve predvsem iz varnostnih razlogov. Privzeto so dovoljene metode GET, POST, HEAD, PUT, DELETE, PATCH.

Če želite omogočiti dodatne metode, kot je OPTIONS, lahko uporabite #[Requires] atribut (od različice Nette Application v3.2):

#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}

V različici 3.1 se preverjanje izvaja v checkHttpMethod(), ki preveri, ali je metoda, navedena v zahtevi, vključena v polje $presenter->allowedMethods. Dodajte metodo, kot je ta:

class MyPresenter extends Nette\Application\UI\Presenter
{
    protected function checkHttpMethod(): void
    {
        $this->allowedMethods[] = 'OPTIONS';
        parent::checkHttpMethod();
    }
}

Ključnega pomena je poudariti, da če omogočite metodo OPTIONS, jo morate ustrezno obdelati tudi v svojem predstavitvenem programu. Ta metoda se pogosto uporablja kot tako imenovana zahteva preflight, ki jo brskalniki samodejno pošljejo pred dejansko zahtevo, ko je treba ugotoviti, ali je zahteva dovoljena z vidika politike CORS (Cross-Origin Resource Sharing). Če to metodo dovolite, vendar ne izvedete ustreznega odziva, lahko pride do nedoslednosti in morebitnih varnostnih težav.

Nadaljnje branje

različica: 4.0