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() és forward() átirányítás.
  • error() hibamiatt kilép a bemutatóból
  • sendJson($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.

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:

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.

További olvasmányok

verzió: 4.0