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 egy úgynevezett ideiglenes átirányításra 302-es HTTP-kóddal (vagy 303-as kóddal, ha az aktuális kérési mód POST):
$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. A HTTP-kódot a
második paraméterként lehet megadni, az alapértelmezett érték 302 (vagy 303, ha az aktuális kérési mód POST):
$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 perzisztens paraméterek a különböző kérések közötti állapot fenntartására szolgálnak. Értékük a linkre való
kattintás után is ugyanaz marad. A munkamenetadatokkal ellentétben ezek az URL-ben kerülnek átadásra. Ez teljesen
automatikus, így nincs szükség a link()
vagy a n:href
oldalon történő kifejezett megadásukra.
Felhasználási példa? Van egy többnyelvű alkalmazása. Az aktuális nyelv egy olyan paraméter, amelynek mindig az URL
része kell, hogy legyen. De hihetetlenül fárasztó lenne minden linkben feltüntetni. Ezért egy állandó paramétert hozol
létre, amelynek a neve lang
, és ez önmagát hordozza. Király!
A tartós paraméter létrehozása rendkívül egyszerű a Nette-ben. Csak hozzon létre egy nyilvános tulajdonságot, és
jelölje meg az attribútummal: (korábban a /** @persistent */
volt használatos).
use Nette\Application\Attributes\Persistent; // ez a sor fontos
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang; // nyilvánosnak kell lennie
}
Ha a $this->lang
értéke például 'en'
, akkor a link()
vagy n:href
használatával létrehozott linkek a lang=en
paramétert is tartalmazni fogják. És amikor a linkre kattintunk,
akkor ismét a $this->lang = 'en'
lesz.
A tulajdonságok esetében javasoljuk, hogy adja meg az adattípust (pl. string
), és egy alapértelmezett
értéket is megadhat. A paraméterek értékei érvényesíthetők.
A perzisztens paraméterek alapértelmezés szerint egy adott bemutató összes művelete között átadásra kerülnek. Több prezenter között történő átadásukhoz vagy meg kell határozni őket:
- egy közös ősben, amelytől az előadók öröklik a paramétereket.
- abban a tulajdonságban, amelyet az előadók használnak:
trait LangAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LangAware;
}
Megváltoztathatja egy állandó paraméter értékét a hivatkozás létrehozásakor:
<a n:href="Product:show $id, lang: cs">detail in Czech</a>
Vagy visszaállítható, azaz eltávolítható az URL-ből. Ekkor az alapértelmezett értéket veszi fel:
<a n:href="Product:show $id, lang: null">click</a>
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. A paramétereket a
getParameters(): array
metódussal lehet megszerezni, egyenként a getParameter($name)
segítségével.
A paraméterek értékei karakterláncok vagy karakterláncok tömbjei, alapvetően közvetlenül az URL-ből nyert nyers
adatok.
A tartós paraméterek validálása
Az URL-ekből kapott állandó paraméterek értékeit a loadState()
módszer írja a tulajdonságokba. A módszer azt is ellenőrzi, hogy a tulajdonságban megadott adattípus megfelel-e,
ellenkező esetben 404-es hibával válaszol, és az oldal nem jelenik meg.
Soha ne bízzunk vakon a perzisztens paraméterekben, mivel azokat a felhasználó könnyen felülírhatja az URL-ben.
Például így ellenőrizzük, hogy a $this->lang
szerepel-e a támogatott nyelvek között. Jó megoldás erre 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); // itt van beállítva a $this->lang
// követi a felhasználói értékek ellenőrzését:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
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));