Vortragende

Wir werden lernen, wie man Presenter und Vorlagen in Nette schreibt. Nach der Lektüre werden Sie wissen:

  • wie der Presenter funktioniert
  • was persistente Parameter sind
  • wie man eine Vorlage rendert

Wir wissen bereits, dass ein Presenter eine Klasse ist, die eine bestimmte Seite einer Webanwendung darstellt, z. B. eine Homepage, ein Produkt im E-Shop, ein Anmeldeformular, ein Sitemap-Feed usw. Die Anwendung kann von einem bis zu Tausenden von Presentern haben. In anderen Frameworks werden sie auch als Controller bezeichnet.

Normalerweise bezieht sich der Begriff Presenter auf einen Abkömmling der Klasse Nette\Application\UI\Presenter, die für Web-Interfaces geeignet ist und die wir im weiteren Verlauf dieses Kapitels besprechen werden. Im allgemeinen Sinne ist ein Presenter jedes Objekt, das die Schnittstelle Nette\Application\IPresenter implementiert.

Lebenszyklus eines Presenters

Die Aufgabe des Presenters besteht darin, die Anfrage zu verarbeiten und eine Antwort zu liefern (die eine HTML-Seite, ein Bild, eine Routing usw. sein kann).

Am Anfang steht also eine Anfrage. Dabei handelt es sich nicht direkt um eine HTTP-Anfrage, sondern um ein Nette\Application\Request -Objekt, in das die HTTP-Anfrage mithilfe eines Routers umgewandelt wurde. Mit diesem Objekt kommen wir in der Regel nicht in Berührung, da der Präsentator die Verarbeitung der Anfrage geschickt an spezielle Methoden delegiert, die wir jetzt sehen werden.

Lebenszyklus des Presenters

Die Abbildung zeigt eine Liste von Methoden, die nacheinander von oben nach unten aufgerufen werden, sofern sie existieren. Keine von ihnen muss existieren, wir können einen völlig leeren Presenter ohne eine einzige Methode haben und ein einfaches statisches Web darauf aufbauen.

__construct()

Der Konstruktor gehört nicht direkt zum Lebenszyklus des Präsentators, da er zum Zeitpunkt der Erstellung des Objekts aufgerufen wird. Aber wir erwähnen ihn wegen seiner Bedeutung. Der Konstruktor (zusammen mit der Methode inject) wird verwendet, um Abhängigkeiten zu übergeben.

Der Presenter sollte sich nicht um die Geschäftslogik der Anwendung kümmern, nicht in die Datenbank schreiben und aus ihr lesen, keine Berechnungen durchführen, usw. Dies ist die Aufgabe für Klassen aus einer Schicht, die wir Modell nennen. Zum Beispiel kann die Klasse ArticleRepository für das Laden und Speichern von Artikeln zuständig sein. Damit der Presenter sie verwenden kann, wird sie mittels Dependency Injection übergeben:

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ArticleRepository $articles,
	) {
	}
}

startup()

Unmittelbar nach Erhalt der Anfrage wird die Methode startup () aufgerufen. Sie können sie verwenden, um Eigenschaften zu initialisieren, Benutzerrechte zu prüfen, usw. Es ist erforderlich, immer den Vorgänger parent::startup() aufzurufen.

action<Action>(args...)

Ähnlich wie bei der Methode render<View>(). Während render<View>() dazu gedacht ist, Daten für eine bestimmte Vorlage vorzubereiten, die anschließend gerendert wird, wird in action<Action>() wird eine Anfrage ohne anschließendes Rendering der Vorlage verarbeitet. So werden z. B. Daten verarbeitet, ein Benutzer an- oder abgemeldet usw., und dann wird er an eine andere Stelle weitergeleitet.

Es ist wichtig, dass action<Action>() vor aufgerufen wird render<View>()aufgerufen wird, damit wir darin möglicherweise den weiteren Verlauf des Lebenszyklus ändern können, d. h. die Vorlage, die gerendert wird, und auch die Methode render<View>() die aufgerufen wird, mit setView('otherView').

Die Parameter der Anfrage werden an die Methode übergeben. Es ist möglich und empfehlenswert, Typen für die Parameter anzugeben, z. B. actionShow(int $id, string $slug = null) – wenn der Parameter id fehlt oder keine ganze Zahl ist, gibt der Präsentator den Fehler 404 zurück und bricht die Operation ab.

handle<Signal>(args...)

Diese Methode verarbeitet die sogenannten Signale, die wir im Kapitel über Komponenten besprechen werden. Sie ist hauptsächlich für Komponenten und die Verarbeitung von AJAX-Anfragen gedacht.

Die Parameter werden an die Methode übergeben, wie im Fall von action<Action>()übergeben, einschließlich der Typüberprüfung.

beforeRender()

Die Methode beforeRender wird, wie der Name schon sagt, vor jeder Methode aufgerufen render<View>(). Sie wird für die allgemeine Konfiguration von Vorlagen, die Übergabe von Variablen für das Layout und so weiter verwendet.

render<View>(args...)

Der Ort, an dem wir die Vorlage für das spätere Rendering vorbereiten, Daten an sie übergeben usw.

Die Parameter werden an die Methode übergeben, wie im Fall von action<Action>()übergeben, einschließlich der Typüberprüfung.

public function renderShow(int $id): void
{
	// wir beziehen Daten aus dem Modell und übergeben sie an die Vorlage
	$this->template->article = $this->articles->getById($id);
}

afterRender()

Die Methode afterRender wird, wie der Name schon sagt, nach jeder render<View>() Methode aufgerufen. Sie wird eher selten verwendet.

shutdown()

Sie wird am Ende des Lebenszyklus des Präsentators aufgerufen.

Guter Rat, bevor wir weitermachen. Wie Sie sehen können, kann der Präsentator mehr Aktionen/Ansichten verarbeiten, d.h. mehr Methoden haben render<View>(). Wir empfehlen jedoch, Presenter mit einer oder so wenigen Aktionen wie möglich zu entwerfen.

Senden einer Antwort

Die Antwort des Präsentators ist in der Regel das Rendern der Vorlage mit der HTML-Seite, aber es kann auch das Senden einer Datei, JSON oder sogar die Routing zu einer anderen Seite sein.

Zu jedem Zeitpunkt des Lebenszyklus können Sie eine der folgenden Methoden verwenden, um eine Antwort zu senden und gleichzeitig den Präsentator zu beenden:

Wenn Sie keine dieser Methoden aufrufen, fährt der Presenter automatisch mit dem Rendern der Vorlage fort. Und warum? Nun, weil wir in 99% der Fälle eine Vorlage zeichnen wollen, also nimmt der Präsentator dieses Verhalten als Standard an und will uns die Arbeit erleichtern.

Presenter hat eine Methode link(), die verwendet wird, um URL-Links zu anderen Presentern zu erstellen. Der erste Parameter ist der Zielmoderator und die Aktion, gefolgt von den Argumenten, die als Array übergeben werden können:

$url = $this->link('Product:show', $id);

$url = $this->link('Product:show', [$id, 'lang' => 'en']);

In der Vorlage erstellen wir Links zu anderen Präsentatoren und Aktionen wie folgt:

<a n:href="Product:show $id">product detail</a>

Schreiben Sie einfach das bekannte Paar Presenter:action anstelle der echten URL und fügen Sie beliebige Parameter ein. Der Trick ist n:href, das besagt, dass dieses Attribut von Latte verarbeitet wird und eine echte URL erzeugt. In Nette müssen Sie überhaupt nicht über URLs nachdenken, sondern nur über Presenter und Aktionen.

Weitere Informationen finden Sie unter Links erstellen.

Umleitung

Die Methoden redirect() und forward() werden verwendet, um zu einem anderen Präsentator zu springen. Sie haben eine sehr ähnliche Syntax wie die Methode link().

Die forward() schaltet ohne HTTP-Umleitung sofort auf den neuen Präsentator um:

$this->forward('Product:show');

Beispiel für eine so genannte temporäre Umleitung mit HTTP-Code 302 (oder 303, wenn die aktuelle Anfragemethode POST ist):

$this->redirect('Product:show', $id);

Um eine dauerhafte Umleitung mit HTTP-Code 301 zu erreichen, verwenden Sie:

$this->redirectPermanent('Product:show', $id);

Sie können mit der Methode redirectUrl() zu einer anderen URL außerhalb der Anwendung umleiten. Der HTTP-Code kann als zweiter Parameter angegeben werden, wobei der Standardwert 302 ist (oder 303, wenn die aktuelle Anforderungsmethode POST ist):

$this->redirectUrl('https://nette.org');

Die Umleitung beendet sofort den Lebenszyklus des Präsentators, indem sie die sogenannte Silent Termination Exception Nette\Application\AbortException auslöst.

Vor der Routing ist es möglich, eine Flash-Nachricht zu senden, die nach der Routing in der Vorlage angezeigt wird.

Flash-Nachrichten

Dies sind Meldungen, die in der Regel über das Ergebnis eines Vorgangs informieren. Ein wichtiges Merkmal von Flash-Meldungen ist, dass sie auch nach einer Routing in der Vorlage verfügbar sind. Selbst nachdem sie angezeigt wurden, bleiben sie noch 30 Sekunden lang erhalten – zum Beispiel für den Fall, dass der Benutzer die Seite unbeabsichtigt aktualisiert – die Nachricht geht nicht verloren.

Rufen Sie einfach die Methode flashMessage() auf und Presenter kümmert sich um die Übergabe der Nachricht an die Vorlage. Das erste Argument ist der Text der Nachricht und das zweite optionale Argument ist ihr Typ (Fehler, Warnung, Info usw.). Die Methode flashMessage() gibt eine Instanz von flash message zurück, damit wir weitere Informationen hinzufügen können.

$this->flashMessage('Item was removed.');
$this->redirect(/* ... */);

In der Vorlage sind diese Meldungen in der Variablen $flashes als Objekte stdClass verfügbar, die die Eigenschaften message (Meldungstext) und type (Meldungstyp) enthalten und die bereits erwähnten Benutzerinformationen enthalten können. Wir zeichnen sie wie folgt:

{foreach $flashes as $flash}
	<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}

Fehler 404 usw.

Wenn wir die Anfrage nicht erfüllen können, weil z.B. der Artikel, den wir anzeigen wollen, nicht in der Datenbank existiert, werden wir den Fehler 404 mit der Methode error(string $message = null, int $httpCode = 404) ausgeben, die den HTTP-Fehler 404 darstellt:

public function renderShow(int $id): void
{
	$article = $this->articles->getById($id);
	if (!$article) {
		$this->error();
	}
	// ...
}

Der HTTP-Fehlercode kann als zweiter Parameter übergeben werden, der Standardwert ist 404. Die Methode funktioniert, indem sie die Ausnahme Nette\Application\BadRequestException auslöst, woraufhin Application die Kontrolle an den Fehler-Moderator weitergibt. Dabei handelt es sich um einen Presenter, dessen Aufgabe es ist, eine Seite anzuzeigen, die über den Fehler informiert. Der Fehler-Presenter wird in der Anwendungskonfiguration festgelegt.

Senden von JSON

Beispiel für eine Action-Methode, die Daten im JSON-Format sendet und den Präsentator verlässt:

public function actionData(): void
{
	$data = ['hello' => 'nette'];
	$this->sendJson($data);
}

Dauerhafte Parameter

Persistente Parameter werden verwendet, um den Zustand zwischen verschiedenen Anfragen zu erhalten. Ihr Wert bleibt gleich, auch wenn ein Link angeklickt wird. Im Gegensatz zu Sitzungsdaten werden sie in der URL übergeben. Dies geschieht völlig automatisch, so dass es nicht notwendig ist, sie in link() oder n:href explizit anzugeben.

Beispiel für die Verwendung? Sie haben eine mehrsprachige Anwendung. Die aktuelle Sprache ist ein Parameter, der immer Teil der URL sein muss. Es wäre aber unglaublich mühsam, ihn in jeden Link aufzunehmen. Also machen Sie ihn zu einem dauerhaften Parameter mit dem Namen lang und er wird sich selbst tragen. Toll!

Das Erstellen eines dauerhaften Parameters ist in Nette extrem einfach. Erstellen Sie einfach eine öffentliche Eigenschaft und kennzeichnen Sie sie mit dem Attribut: (früher wurde /** @persistent */ verwendet)

use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig

class ProductPresenter extends Nette\Application\UI\Presenter
{
	#[Persistent]
	public string $lang; // muss öffentlich sein
}

Wenn $this->lang einen Wert wie 'en' hat, dann werden Links, die mit link() oder n:href erstellt werden, auch den Parameter lang=en enthalten. Und wenn der Link angeklickt wird, wird er wieder $this->lang = 'en' sein.

Für Eigenschaften wird empfohlen, den Datentyp anzugeben (z. B. string), und Sie können auch einen Standardwert angeben. Parameterwerte können validiert werden.

Persistente Parameter werden standardmäßig zwischen allen Aktionen eines bestimmten Präsentators weitergegeben. Um sie zwischen mehreren Präsentatoren zu übergeben, müssen Sie sie entweder definieren:

  • in einem gemeinsamen Vorfahren, von dem die Präsentatoren erben
  • in der Eigenschaft, die die Präsentatoren verwenden:
trait LangAware
{
	#[Persistent]
	public string $lang;
}

class ProductPresenter extends Nette\Application\UI\Presenter
{
	use LangAware;
}

Sie können den Wert eines dauerhaften Parameters ändern, wenn Sie einen Link erstellen:

<a n:href="Product:show $id, lang: cs">detail in Czech</a>

Oder er kann zurückgesetzt werden, d.h. aus der URL entfernt werden. Er nimmt dann seinen Standardwert an:

<a n:href="Product:show $id, lang: null">click</a>

Interaktive Komponenten

Presenter haben ein eingebautes Komponentensystem. Komponenten sind separate, wiederverwendbare Einheiten, die wir in Presenter einfügen. Es kann sich dabei um Formulare, Datenfelder, Menüs und alles handeln, was für eine wiederholte Verwendung sinnvoll ist.

Wie werden Komponenten im Presenter platziert und anschließend verwendet? Das wird im Kapitel Komponenten erklärt. Sie werden sogar erfahren, was sie mit Hollywood zu tun haben.

Wo kann ich einige Komponenten bekommen? Auf der Seite Componette finden Sie einige Open-Source-Komponenten und andere Erweiterungen für Nette, die von der Nette-Framework-Gemeinschaft erstellt und geteilt werden.

Tiefer gehen

Was wir bisher in diesem Kapitel gezeigt haben, wird wahrscheinlich ausreichen. Die folgenden Zeilen sind für diejenigen gedacht, die sich eingehend mit Moderatoren beschäftigen und alles wissen wollen.

Anforderung und Parameter

Die vom Präsentator bearbeitete Anfrage ist das Objekt Nette\Application\Request und wird von der Methode getRequest() des Präsentators zurückgegeben. Sie enthält ein Array von Parametern, und jeder von ihnen gehört entweder zu einer der Komponenten oder direkt zum Präsentator (der eigentlich auch eine Komponente ist, wenn auch eine spezielle). Nette verteilt also die Parameter um und übergibt sie zwischen den einzelnen Komponenten (und dem Präsentator) durch Aufruf der Methode loadState(array $params). Die Parameter können mit der Methode getParameters(): array, einzeln mit getParameter($name) abgerufen werden. Bei den Parameterwerten handelt es sich um Strings oder Arrays von Strings, also im Grunde um Rohdaten, die direkt aus einer URL bezogen werden.

Validierung von persistenten Parametern

Die Werte von persistenten Parametern, die von URLs empfangen werden, werden von der Methode loadState() in Eigenschaften geschrieben. Sie prüft auch, ob der in der Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt.

Verlassen Sie sich niemals blind auf persistente Parameter, da sie leicht vom Benutzer in der URL überschrieben werden können. So überprüfen wir zum Beispiel, ob $this->lang zu den unterstützten Sprachen gehört. Eine gute Möglichkeit, dies zu tun, besteht darin, die oben erwähnte Methode loadState() zu überschreiben:

class ProductPresenter extends Nette\Application\UI\Presenter
{
	#[Persistent]
	public string $lang;

	public function loadState(array $params): void
	{
		parent::loadState($params); // hier wird die $this->lang gesetzt
		// nach der Prüfung der Benutzerwerte:
		if (!in_array($this->lang, ['en', 'cs'])) {
			$this->error();
		}
	}
}

Speichern und Wiederherstellen der Anfrage

Sie können die aktuelle Anfrage in einer Session speichern oder sie aus der Session wiederherstellen und den Präsentator sie erneut ausführen lassen. Dies ist z. B. nützlich, wenn ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft. Um keine Daten zu verlieren, speichern wir vor der Routing zur Anmeldeseite die aktuelle Anfrage in der Session mit $reqId = $this->storeRequest(), die einen Bezeichner in Form einer kurzen Zeichenkette zurückgibt und ihn als Parameter an den Anmeldepräsentator übergibt.

Nach der Anmeldung rufen wir die Methode $this->restoreRequest($reqId) auf, die die Anfrage aus der Session abholt und an diese weiterleitet. Die Methode prüft, ob die Anfrage von demselben Benutzer erstellt wurde, der jetzt angemeldet ist. Wenn sich ein anderer Benutzer anmeldet oder der Schlüssel ungültig ist, tut sie nichts und das Programm läuft weiter.

Siehe das Kochbuch How to return to a earlier page.

Kanonisierung

Presenter haben eine wirklich großartige Funktion, die SEO (Optimierung der Auffindbarkeit im Internet) verbessert. Sie verhindern automatisch das Vorhandensein von doppeltem Inhalt unter verschiedenen URLs. Wenn mehrere URLs zu einem bestimmten Ziel führen, z. B. /index und /index?page=1, bestimmt das Framework eine von ihnen als die primäre (kanonische) und leitet die anderen mit dem HTTP-Code 301 auf diese um. Auf diese Weise werden die Seiten von den Suchmaschinen nicht doppelt indiziert und ihr Page Rank nicht geschwächt.

Dieser Vorgang wird als Kanonisierung bezeichnet. Die kanonische URL ist die vom Router generierte URL, in der Regel die erste geeignete Route in der Sammlung.

Die Kanonisierung ist standardmäßig aktiviert und kann über $this->autoCanonicalize = false ausgeschaltet werden.

Eine Umleitung findet bei einer AJAX- oder POST-Anfrage nicht statt, da dies zu Datenverlust oder keinem SEO-Mehrwert führen würde.

Sie können die Kanonisierung auch manuell mit der Methode canonicalize() aufrufen, die wie die Methode link() den Präsentator, Aktionen und Parameter als Argumente erhält. Sie erstellt einen Link und vergleicht ihn mit der aktuellen URL. Wenn sie sich unterscheidet, wird sie auf den erzeugten Link umgeleitet.

public function actionShow(int $id, string $slug = null): void
{
	$realSlug = $this->facade->getSlugForId($id);
	// leitet um, wenn $slug nicht mit $realSlug übereinstimmt
	$this->canonicalize('Product:show', [$id, $realSlug]);
}

Ereignisse

Zusätzlich zu den Methoden startup(), beforeRender() und shutdown(), die im Rahmen des Lebenszyklus des Presenters aufgerufen werden, können weitere Funktionen definiert werden, die automatisch aufgerufen werden. Der Präsentator definiert die sogenannten Ereignisse, und Sie fügen deren Handler zu den Arrays $onStartup, $onRender und $onShutdown hinzu.

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	public function __construct()
	{
		$this->onStartup[] = function () {
			// ...
		};
	}
}

Die Handler im Array $onStartup werden kurz vor der Methode startup() aufgerufen, dann $onRender zwischen beforeRender() und render<View>() und schließlich $onShutdown kurz vor shutdown().

Antworten

Die vom Präsentator zurückgegebene Antwort ist ein Objekt, das die Schnittstelle Nette\Application\Response implementiert. Es gibt eine Reihe von vorgefertigten Antworten:

Antworten werden mit der Methode sendResponse() gesendet:

use Nette\Application\Responses;

// Klartext
$this->sendResponse(new Responses\TextResponse('Hello Nette!'));

// Sendet eine Datei
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));

// Sendet einen Rückruf
$callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) {
	if ($httpResponse->getHeader('Content-Type') === 'text/html') {
		echo '<h1>Hallo</h1>';
	}
};
$this->sendResponse(new Responses\CallbackResponse($callback));

Weitere Lektüre

Version: 4.0