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);
}
Kérési paraméterek
A bemutató, valamint minden komponens a HTTP-kérésből kapja a paramétereit. Értékeik a getParameter($name)
módszerrel vagy a getParameters()
segítségével kérhetők le. Az értékek karakterláncok vagy karakterláncok
tömbjei, lényegében közvetlenül az URL-ből nyert nyers adatok.
A további kényelem érdekében javasoljuk, hogy a paraméterek tulajdonságokon keresztül legyenek elérhetők. Egyszerűen
jegyzeteljük őket a #[Parameter]
attribútummal:
use Nette\Application\Attributes\Parameter; // ez a sor fontos
class HomePresenter extends Nette\Application\UI\Presenter
{
#[Parameter]
public string $theme; // nyilvánosnak kell lennie
}
A tulajdonságok esetében javasoljuk, hogy adja meg az adattípust (pl. string
). A Nette ezután automatikusan
ennek alapján alakítja ki az értéket. A paraméterek értékei is érvényesíthetők.
A hivatkozás létrehozásakor közvetlenül megadhatja a paraméterek értékét:
<a n:href="Home:default theme: dark">click</a>
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 megadhatja az
alapértelmezett értéket is. 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 LanguageAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LanguageAware;
}
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.
A paraméterek validálása
Az URL-ekből kapott kérési paraméterek és állandó paraméterek értékeit a loadState()
módszer írja a
tulajdonságokba. 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ízzon vakon a paraméterekben, mert 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. 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); // 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
A kérés, amelyet a bemutató kezel, egy objektum Nette\Application\Request és a bemutató
getRequest()
metódusa adja vissza.
Az aktuális kérést elmentheti egy munkamenetbe, vagy visszaállíthatja a munkamenetből, és hagyhatja, hogy a prezenter
ismét futtassa. 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));
Hozzáférés korlátozása #[Requires]
A #[Requires]
attribútum speciális lehetőségeket biztosít az előadókhoz és módszereikhez való
hozzáférés korlátozására. Használható HTTP-módszerek megadására, AJAX-kérések megkövetelésére, az azonos eredetű
hozzáférések korlátozására és a hozzáférésnek csak a továbbításra való korlátozására. Az attribútum
alkalmazható a prezenter osztályokra, valamint az egyes metódusokra, mint például a action<Action>()
,
render<View>()
, handle<Signal>()
, és createComponent<Name>()
.
Ezeket a korlátozásokat megadhatja:
- a HTTP-módszerekre:
#[Requires(methods: ['GET', 'POST'])]
- AJAX-kérést igényel:
#[Requires(ajax: true)]
- hozzáférés csak ugyanabból az eredetből:
#[Requires(sameOrigin: true)]
- hozzáférés csak továbbítással:
#[Requires(forward: true)]
- korlátozások bizonyos műveletekre:
#[Requires(actions: 'default')]
A részletekért lásd Hogyan használjuk a Requires attribútum használata.
HTTP módszer ellenőrzése
A Nette rendszerben az előadók elsősorban biztonsági okokból automatikusan ellenőrzik minden bejövő kérés
HTTP-módszerét. Alapértelmezés szerint a GET
, POST
, HEAD
, PUT
,
DELETE
, PATCH
módszerek engedélyezettek.
Ha további módszereket, például a OPTIONS
, szeretne engedélyezni, akkor használhatja a
#[Requires]
attribútumot (a Nette Application v3.2-től):
#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
A 3.1-es verzióban az ellenőrzést a checkHttpMethod()
végzi, amely ellenőrzi, hogy a kérelemben megadott
módszer szerepel-e a $presenter->allowedMethods
tömbben. Adjon hozzá egy ilyen módszert:
class MyPresenter extends Nette\Application\UI\Presenter
{
protected function checkHttpMethod(): void
{
$this->allowedMethods[] = 'OPTIONS';
parent::checkHttpMethod();
}
}
Fontos hangsúlyozni, hogy ha engedélyezi a OPTIONS
módszert, akkor azt megfelelően kell kezelnie a prezenteren
belül is. Ezt a módszert gyakran használják úgynevezett preflight-kérelemként, amelyet a böngészők automatikusan
elküldenek a tényleges kérés előtt, amikor meg kell határozni, hogy a kérés engedélyezett-e a CORS (Cross-Origin Resource
Sharing) irányelv szempontjából. Ha engedélyezi ezt a módszert, de nem valósít meg megfelelő választ, az
következetlenségekhez és potenciális biztonsági problémákhoz vezethet.