Előadók
Megtanuljuk, hogyan kell prezentereket és sablonokat írni a Nette-ben. Az olvasás után tudni fogja:
- hogyan működik a prezenter
- mik a tartós paraméterek
- hogyan kell megjeleníteni egy sablont
Azt már tudjuk, hogy a prezenter egy olyan osztály, amely egy webes alkalmazás egy adott oldalát reprezentálja, például a kezdőlapot; a terméket a webáruházban; a bejelentkezési űrlapot; a webhelytérképet stb. Az alkalmazásnak egytől akár több ezer prezenter is lehet. Más keretrendszerekben ezeket kontrollereknek is nevezik.
Általában a prezenter kifejezés a Nette\Application\UI\Presenter osztály leszármazottjára utal, amely a webes felületekre alkalmas, és amelyet a fejezet további részében tárgyalunk. Általános értelemben a prezenter bármely olyan objektum, amely megvalósítja a Nette\Application\IPresenter interfészt.
A bemutató életciklusa
A bemutató feladata a kérés feldolgozása és a válasz visszaküldése (ami lehet HTML oldal, kép, átirányítás stb.).
Az elején tehát egy kérés áll. Ez nem közvetlenül egy HTTP-kérés, hanem egy Nette\Application\Request objektum, amelybe a HTTP-kérést egy útválasztó segítségével átalakították. Ezzel az objektummal általában nem kerülünk kapcsolatba, mert a bemutató okosan delegálja a kérés feldolgozását speciális metódusokba, amelyeket most látni fogunk.
A prezenter életciklusa
Az ábra a metódusok listáját mutatja, amelyeket – ha léteznek – felülről lefelé haladva egymás után hívunk meg. Egyiknek sem kell léteznie, lehet egy teljesen üres presenterünk egyetlen metódus nélkül, és építhetünk rá egy egyszerű statikus webet.
__construct()
A konstruktor nem tartozik pontosan a bemutató életciklusához, mert az objektum létrehozásának pillanatában hívjuk meg. De fontossága miatt megemlítjük. A konstruktor (az inject metódussal együtt) a függőségek átadására szolgál.
A prezenternek nem kell gondoskodnia az alkalmazás üzleti logikájáról, írnia és olvasnia az adatbázisból,
számításokat végeznie stb. Ez a feladata egy réteg osztályainak, amit modellnek nevezünk. Például a
ArticleRepository
osztály lehet felelős a cikkek betöltéséért és mentéséért. Ahhoz, hogy a prezenter
használhassa, függőségi injektálással adjuk
át:
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articles,
) {
}
}
startup()
A kérés fogadása után azonnal meghívásra kerül a startup ()
módszer. Ezt használhatja a tulajdonságok
inicializálására, a felhasználói jogosultságok ellenőrzésére stb. Mindig meg kell hívni a parent::startup()
elődjét.
action<Action>(args...)
Hasonlóan a módszerhez render<View>()
. Míg a render<View>()
célja, hogy
előkészítse az adatokat egy adott sablonhoz, amelyet később renderel, a action<Action>()
a kérés
feldolgozása az azt követő sablon renderelés nélkül történik. Például az adatok feldolgozása, a felhasználó
bejelentkezése vagy kijelentkezése stb. történik, majd máshová irányít át.
Fontos, hogy action<Action>()
előbb hívódik meg, mint a render<View>()
, így ezen
belül esetleg megváltoztathatjuk az életciklus következő menetét, azaz megváltoztathatjuk a megjelenítendő sablont és a
metódust is. render<View>()
ami meghívásra kerül, a setView('otherView')
segítségével.
A kérésből származó paramétereket átadjuk a metódusnak. Lehetséges és ajánlott a paraméterek típusainak
megadása, pl. actionShow(int $id, string $slug = null)
– ha a id
paraméter hiányzik, vagy nem
egész szám, a prezenter 404-es hibát ad vissza, és megszakítja a műveletet.
handle<Signal>(args...)
Ez a módszer az úgynevezett jeleket dolgozza fel, amelyeket a komponensekről szóló fejezetben tárgyalunk. Elsősorban komponensekhez és az AJAX-kérések feldolgozásához készült.
A paramétereket a metódusnak átadjuk, mint a action<Action>()
, beleértve a típusellenőrzést is.
beforeRender()
A beforeRender
metódus, ahogy a neve is mutatja, minden egyes metódus előtt meghívásra kerül.
render<View>()
. A sablonok általános konfigurálására, az elrendezéshez szükséges változók
átadására és így tovább.
render<View>(args...)
Az a hely, ahol előkészítjük a sablont a későbbi rendereléshez, adatokat adunk át neki, stb.
A paramétereket átadjuk a metódusnak, mint a action<Action>()
, beleértve a típusellenőrzést is.
public function renderShow(int $id): void
{
// adatokat kapunk a modellből és átadjuk a sablonhoz.
$this->template->article = $this->articles->getById($id);
}
afterRender()
A afterRender
metódus, ahogy a neve is mutatja, minden egyes render<View>()
módszer után.
Meglehetősen ritkán használják.
shutdown()
A bemutató életciklusának végén hívják meg.
Jó tanács, mielőtt továbblépnénk. Mint látható, a prezenter több műveletet/nézetet tud kezelni, azaz több
metódusa van. render<View>()
. De javasoljuk, hogy a prezentereket egy vagy a lehető legkevesebb akcióval
tervezzük.
Válasz küldése
A bemutató válasza általában a sablon renderelése a HTML oldallal, de lehet egy fájl, JSON küldése vagy akár egy másik oldalra való átirányítás is.
Az életciklus során bármikor használhatja az alábbi módszerek bármelyikét a válasz elküldésére és a prezentálóból való kilépésre egyidejűleg:
redirect()
,redirectPermanent()
,redirectUrl()
ésforward()
átirányítás.error()
hibamiatt kilép a bemutatóbólsendJson($data)
kilép a prezentálóból és elküldi az adatokat JSON formátumban.sendTemplate()
kilép a prezenterből és azonnal rendereli a sablont.sendResponse($response)
kilép a prezenterből és elküldi a saját válaszát.terminate()
válasz nélkül kilép a prezenterből
Ha nem hívja meg egyik módszert sem, a prezenter automatikusan folytatja a sablon renderelését. Miért? Nos, mert az esetek 99%-ában egy sablont akarunk kirajzolni, ezért a prezenter ezt a viselkedést veszi alapértelmezettnek, és ezzel szeretné megkönnyíteni a munkánkat.
Linkek létrehozása
A Presenter rendelkezik egy link()
metódussal, amely más Presenterekre mutató URL-linkek létrehozására
szolgál. Az első paraméter a célelőadó és a művelet, majd az argumentumok következnek, amelyek tömbként
adhatók át:
$url = $this->link('Product:show', $id);
$url = $this->link('Product:show', [$id, 'lang' => 'en']);
A sablonban más prezenterekre és akciókra mutató linkeket hozunk létre a következőképpen:
<a n:href="Product:show $id">product detail</a>
Egyszerűen csak írjuk a megszokott Presenter:action
párost a valódi URL helyett, és adjunk meg minden
paramétert. A trükk a n:href
, amely azt mondja, hogy ezt az attribútumot a Latte feldolgozza, és valódi URL-t
generál. A Nette-ben egyáltalán nem kell URL-ekre gondolni, csak az előadókra és az akciókra.
További információért lásd a Linkek létrehozása című részt.
Átirányítás
A redirect()
és a forward()
metódusok egy másik bemutatóra való átugrásra szolgálnak, amelyek
szintaxisa nagyon hasonló a link() metóduséhoz.
A forward()
azonnal átvált az új bemutatóra HTTP átirányítás nélkül:
$this->forward('Product:show');
Példa ideiglenes átirányításra 302 vagy 303 HTTP-kóddal:
$this->redirect('Product:show', $id);
A 301-es kódú HTTP-kóddal történő állandó átirányítás eléréséhez használja a következőt:
$this->redirectPermanent('Product:show', $id);
A redirectUrl()
módszerrel átirányíthat egy másik, az alkalmazáson kívüli URL-címre:
$this->redirectUrl('https://nette.org');
Az átirányítás azonnal befejezi a bemutató életciklusát az úgynevezett csendes befejezési kivétel dobásával
Nette\Application\AbortException
.
Az átirányítás előtt lehetőség van flash üzenetet küldeni, olyan üzeneteket, amelyek az átirányítás után megjelennek a sablonban.
Flash-üzenetek
Ezek olyan üzenetek, amelyek általában egy művelet eredményéről tájékoztatnak. A flash üzenetek fontos jellemzője, hogy a sablonban az átirányítás után is elérhetőek. Megjelenésük után is még 30 másodpercig életben maradnak – például abban az esetben, ha a felhasználó véletlenül frissítené az oldalt – az üzenet nem vész el.
Csak hívjuk meg a flashMessage()
metódust, és a presenter gondoskodik az üzenet átadásáról a sablonban. Az első argumentum az üzenet szövege, a második
opcionális argumentum pedig az üzenet típusa (hiba, figyelmeztetés, info stb.). A flashMessage()
metódus egy
flash message példányt ad vissza, hogy további információkat adhassunk hozzá.
$this->flashMessage('Item was removed.');
$this->redirect(/* ... */);
A sablonban ezek az üzenetek a $flashes
változóban stdClass
objektumként állnak rendelkezésre,
amelyek tartalmazzák a message
(üzenet szövege), type
(üzenet típusa) tulajdonságokat, és
tartalmazhatják a már említett felhasználói információkat. Ezeket a következőképpen rajzoljuk meg:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
404-es hiba stb.
Ha nem tudjuk teljesíteni a kérést, mert például a megjeleníteni kívánt cikk nem létezik az adatbázisban, akkor a
404-es hibát dobjuk ki a error(string $message = null, int $httpCode = 404)
módszerrel, amely a 404-es HTTP hibát
jelenti:
public function renderShow(int $id): void
{
$article = $this->articles->getById($id);
if (!$article) {
$this->error();
}
// ...
}
A HTTP hibakódot második paraméterként adhatjuk meg, az alapértelmezett érték a 404. A módszer úgy működik, hogy
kivételt dob a Nette\Application\BadRequestException
, ami után a Application
átadja a vezérlést a
hiba bemutatójának. Ami egy prezenter, amelynek az a feladata, hogy megjelenítsen egy, a hibáról tájékoztató oldalt.
A hiba-prezenter az alkalmazás konfigurációjában van
beállítva.
JSON küldése
Példa az action-módszerre, amely JSON formátumban küldi az adatokat és kilép a bemutatóból:
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
Tartós paraméterek
A tartós paraméterek automatikusan kerülnek átvitelre a hivatkozásokban. Ez azt jelenti, hogy nem kell őket
explicit módon megadni minden egyes link()
vagy n:href
sablonban, de ettől még átvitelre
kerülnek.
Ha az alkalmazásunknak több nyelvi változata van, akkor az aktuális nyelv olyan paraméter, amelynek mindig az URL része
kell, hogy legyen. És hihetetlenül fárasztó lenne minden linkben megemlíteni. A Nette esetében erre nincs szükség.
Egyszerűen csak a lang
paramétert jelöljük így állandónak:
use Nette\Application\Attributes\Persistent;
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
}
Ha a $this->lang
értéke 'en'
, akkor a link()
vagy n:href
segítségével létrehozott hivatkozások a lang=en
paramétert is tartalmazni fogják. Király!
Vegye figyelembe, hogy a tartós tulajdonságokat nyilvánosnak kell deklarálni. Megadhat egy alapértelmezett értéket is, amely nem kerül be az URL-be.
Korábban az attribútum helyett a #[Persistent]
annotáció helyett a
/** @persistent */
megjegyzést használták.
A hivatkozás létrehozásakor megváltoztathatja a tartós paraméter értékét:
<a n:href="Product:show $id, lang: en">detail in English</a>
Vagy visszaállítható, azaz eltávolítható az URL-ből:
<a n:href="Product:show $id, lang: null">click here</a>
A kérési URL-ben kapott paraméterek értékeit a loadState()
módszer írja ki a tulajdonságokba. Azt is
ellenőrzi, hogy a tulajdonsághoz megadott adattípus megfelel-e, ellenkező esetben 404-es hibával válaszol, és az oldal nem
jelenik meg.
Soha ne bízzon vakon a tartós tulajdonságokban, mivel azokat a felhasználó könnyen felülírhatja az URL-ben.
Ellenőrizze például, hogy a $this->lang
nyelv a támogatottak között van-e. Ennek jó módja a fent említett
loadState()
metódus felülírása:
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
public function loadState(array $params): void
{
parent::loadState($params); // here is set the $this->lang
// follows the user value check:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
Ahhoz, hogy több bemutató között állandó paramétereket adjon át, meg kell határoznia a következőket:
- a közös ősben, akitől mindannyian örökölnek.
- az általuk használt tulajdonságban
Példa egy tartós paraméterrel rendelkező tulajdonságra:
trait LangAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LangAware;
}
Interaktív komponensek
A prezenterek beépített komponensrendszerrel rendelkeznek. A komponensek különálló, újrafelhasználható egységek, amelyeket a prezenterekbe helyezünk. Ezek lehetnek űrlapok, adagramok, menük, tulajdonképpen bármi, amit érdemes ismételten használni.
Hogyan kerülnek a komponensek a prezenterben elhelyezésre és a későbbiekben felhasználásra? Ezt a Komponensek fejezetben magyarázzuk el. Még azt is megtudhatjuk, mi közük van Hollywoodhoz.
Hol szerezhetek be komponenseket? A Componette oldalon találsz néhány nyílt forráskódú komponenst és egyéb kiegészítőt a Nette-hez, amelyeket a Nette Framework közössége készített és osztott meg.
Mélyebbre ásva
Amit eddig ebben a fejezetben bemutattunk, valószínűleg elegendő lesz. A következő sorok azoknak szólnak, akiket a prezenterek mélységében érdekelnek, és mindent tudni akarnak.
Követelmények és paraméterek
A bemutató által kezelt kérés a Nette\Application\Request objektum, amelyet a
bemutató getRequest()
metódusa ad vissza. Ez paraméterek tömbjét tartalmazza, és mindegyik paraméter vagy
valamelyik komponenshez, vagy közvetlenül a prezentálóhoz tartozik (amely valójában szintén egy komponens, bár egy
speciális komponens). A Nette tehát a loadState(array $params)
metódus meghívásával újraosztja a
paramétereket és átadja az egyes komponensek (és a prezenter) között (lásd még: Tartós paraméterek). A paramétereket a getParameters(): array
metódussal
lehet megszerezni, egyenként a getParameter($name)
segítségével. A paraméterértékek karakterláncok vagy
karakterláncok tömbjei, alapvetően közvetlenül az URL-ből nyert nyers adatok.
A kérelem mentése és visszaállítása
Az aktuális kérést elmentheti egy munkamenetbe, vagy visszaállíthatja a munkamenetből, és hagyhatja, hogy az előadó
újra végrehajtsa azt. Ez például akkor hasznos, ha egy felhasználó kitölt egy űrlapot, és a bejelentkezése lejár. Annak
érdekében, hogy ne veszítsünk el adatokat, a bejelentkezési oldalra való átirányítás előtt az aktuális kérést a
munkamenetbe mentjük a $reqId = $this->storeRequest()
segítségével, amely egy rövid karakterlánc
formájában visszaad egy azonosítót, és paraméterként átadja a bejelentkezési prezenternek.
A bejelentkezés után meghívjuk a $this->restoreRequest($reqId)
metódust, amely átveszi a kérést a
munkamenetből, és továbbítja azt. A módszer ellenőrzi, hogy a kérést ugyanaz a felhasználó hozta-e létre, mint aki
most bejelentkezett. Ha egy másik felhasználó jelentkezik be, vagy a kulcs érvénytelen, akkor nem tesz semmit, és a program
folytatódik.
Lásd a Hogyan térjünk vissza egy korábbi oldalra című szakácskönyvben.
Kanonizálás
A bemutatóknak van egy igazán nagyszerű funkciója, amely javítja a SEO-t (az internetes kereshetőség optimalizálása).
Automatikusan megakadályozzák a duplikált tartalmak meglétét a különböző URL-címeken. Ha több URL vezet egy bizonyos
célhoz, pl. /index
és /index?page=1
, a keretrendszer az egyiket elsődlegesnek (kanonikusnak) jelöli
ki, és a többit erre irányítja át a 301-es HTTP-kóddal. Ennek köszönhetően a keresőmotorok nem indexelik kétszer az
oldalakat, és nem gyengítik azok oldalrangsorát.
Ezt a folyamatot kanonizációnak nevezzük. A kanonikus URL az útválasztó által generált URL, általában az első megfelelő útvonal a gyűjteményben.
A kanonizálás alapértelmezés szerint be van kapcsolva, és kikapcsolható a
$this->autoCanonicalize = false
címen keresztül.
Az átirányítás nem történik AJAX vagy POST kérés esetén, mivel az adatvesztéssel járna, vagy nem eredményezne SEO hozzáadott értéket.
A kanonizálás manuálisan is meghívható a canonicalize()
módszerrel, amely a link()
módszerhez
hasonlóan a prezentálót, a műveleteket és a paramétereket kapja argumentumként. Létrehoz egy linket, és összehasonlítja
azt az aktuális URL-lel. Ha eltér, akkor átirányít a létrehozott linkre.
public function actionShow(int $id, string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// átirányít, ha a $slug különbözik a $realSlug-tól.
$this->canonicalize('Product:show', [$id, $realSlug]);
}
Események
A startup()
, beforeRender()
és shutdown()
metódusokon kívül, amelyeket a
prezentáló életciklusának részeként hívunk meg, más funkciók is definiálhatók, amelyeket automatikusan hívunk.
A prezenter definiálja az úgynevezett eseményeket, és a
kezelőiket a $onStartup
, $onRender
és $onShutdown
tömbökhöz adjuk hozzá.
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct()
{
$this->onStartup[] = function () {
// ...
};
}
}
A $onStartup
tömbben lévő kezelőket közvetlenül a startup()
módszer előtt hívja meg a
rendszer, majd a $onRender
a beforeRender()
és a között. render<View>()
és
végül a $onShutdown
közvetlenül a shutdown()
előtt.
Válaszok
A bemutató által visszaküldött válasz egy objektum, amely a Nette\Application\Response interfészt valósítja meg. Számos kész válasz létezik:
- Nette\Application\Responses\CallbackResponse – visszahívást küld
- Nette\Application\Responses\FileResponse – elküldi a fájlt
- Nette\Application\Responses\ForwardResponse – forward ()
- Nette\Application\Responses\JsonResponse – JSON-t küld
- Nette\Application\Responses\RedirectResponse – átirányítás
- Nette\Application\Responses\TextResponse – szöveget küld
- Nette\Application\Responses\VoidResponse – üres válasz
A válaszok küldése a sendResponse()
módszerrel történik:
use Nette\Application\Responses;
// Sima szöveg
$this->sendResponse(new Responses\TextResponse('Hello Nette!'));
// Fájl küldése
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));
// Elküld egy visszahívást
$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));