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()
inforward()
preusmeritveerror()
zaradi napake zaključi predstavitveni programsendJson($data)
zapusti predstavitveno orodje in pošlje podatke v obliki JSONsendTemplate()
zapusti predstavitveno orodje in takoj izriše predlogo.sendResponse($response)
zaključi predstavitev in pošlje lasten odgovorterminate()
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.
Ustvarjanje povezav
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:
- Nette\Application\Responses\CallbackResponse – pošlje povratni klic
- Nette\Application\Responses\FileResponse – pošlje datoteko
- Nette\Application\Responses\ForwardResponse – posreduje ()
- Nette\Application\Responses\JsonResponse – pošlje JSON
- Nette\Application\Responses\RedirectResponse – preusmeri
- Nette\Application\Responses\TextResponse – pošlje besedilo
- Nette\Application\Responses\VoidResponse – prazen odgovor
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.