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);
}

Parameter anfordern

Der Präsentator, wie auch jede Komponente, bezieht seine Parameter aus der HTTP-Anfrage. Ihre Werte können mit der Methode getParameter($name) oder getParameters() abgerufen werden. Bei den Werten handelt es sich um Strings oder Arrays von Strings, also im Wesentlichen um Rohdaten, die direkt aus der URL bezogen werden.

Für zusätzlichen Komfort empfiehlt es sich, die Parameter über Eigenschaften zugänglich zu machen. Beschriften Sie sie einfach mit dem #[Parameter] Attribut:

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

class HomePresenter extends Nette\Application\UI\Presenter
{
	#[Parameter]
	public string $theme; // muss öffentlich sein
}

Für Eigenschaften empfehlen wir die Angabe des Datentyps (z. B. string). Nette wird den Wert dann automatisch auf der Grundlage dieses Typs umwandeln. Parameterwerte können auch validiert werden.

Beim Erstellen einer Verknüpfung können Sie den Wert für die Parameter direkt festlegen:

<a n:href="Home:default theme: dark">click</a>

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 LanguageAware
{
	#[Persistent]
	public string $lang;
}

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

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.

Validierung von Parametern

Die Werte von Anfrageparametern und dauerhaften 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 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

Die Anfrage, die der Präsentator bearbeitet, ist ein Objekt Nette\Application\Request und wird von der Methode getRequest() des Präsentators zurückgegeben.

Sie können die aktuelle Anfrage in einer Sitzung speichern oder sie aus der Sitzung wiederherstellen und den Präsentator sie erneut ausführen lassen. Dies ist z. B. nützlich, wenn ein Benutzer ein Formular ausfüllt und sein Login abläuft. Um keine Daten zu verlieren, speichern wir vor der Weiterleitung zur Anmeldeseite die aktuelle Anfrage in der Sitzung mit $reqId = $this->storeRequest(), die einen Bezeichner in Form eines kurzen Strings zurückgibt und diesen 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));

Zugangsbeschränkung mit #[Requires]

Das Attribut #[Requires] Attribut bietet erweiterte Optionen zur Einschränkung des Zugriffs auf Präsentatoren und ihre Methoden. Es kann verwendet werden, um HTTP-Methoden zu spezifizieren, AJAX-Anfragen zu verlangen, den Zugriff auf denselben Ursprung zu beschränken und den Zugriff nur auf Weiterleitungen zu beschränken. Das Attribut kann sowohl auf Presenter-Klassen als auch auf einzelne Methoden angewendet werden, z. B. action<Action>(), render<View>(), handle<Signal>(), und createComponent<Name>().

Sie können diese Einschränkungen angeben:

  • auf HTTP-Methoden: #[Requires(methods: ['GET', 'POST'])]
  • die eine AJAX-Anfrage erfordern: #[Requires(ajax: true)]
  • Zugriff nur vom selben Ursprung: #[Requires(sameOrigin: true)]
  • Zugriff nur über Weiterleitung: #[Requires(forward: true)]
  • Einschränkungen für bestimmte Aktionen: #[Requires(actions: 'default')]

Für Einzelheiten siehe Verwendung des Requires Attributs.

HTTP-Methodenprüfung

In Nette überprüfen die Moderatoren die HTTP-Methode jeder eingehenden Anfrage automatisch, hauptsächlich aus Sicherheitsgründen. Standardmäßig sind die Methoden GET, POST, HEAD, PUT, DELETE, PATCH zugelassen.

Wenn Sie zusätzliche Methoden wie OPTIONS aktivieren möchten, können Sie das #[Requires] Attribut verwenden (ab Nette Application v3.2):

#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}

In Version 3.1 wird die Überprüfung in checkHttpMethod() durchgeführt, das prüft, ob die in der Anfrage angegebene Methode im Array $presenter->allowedMethods enthalten ist. Fügen Sie eine Methode wie diese hinzu:

class MyPresenter extends Nette\Application\UI\Presenter
{
    protected function checkHttpMethod(): void
    {
        $this->allowedMethods[] = 'OPTIONS';
        parent::checkHttpMethod();
    }
}

Es ist wichtig zu betonen, dass, wenn Sie die OPTIONS -Methode zulassen, Sie sie auch in Ihrem Präsentator richtig behandeln müssen. Diese Methode wird häufig als so genannte Preflight-Anfrage verwendet, die von Browsern automatisch vor der eigentlichen Anfrage gesendet wird, wenn es darum geht, festzustellen, ob die Anfrage aus Sicht der CORS-Richtlinie (Cross-Origin Resource Sharing) zulässig ist. Wenn Sie diese Methode zulassen, aber keine angemessene Antwort implementieren, kann dies zu Inkonsistenzen und potenziellen Sicherheitsproblemen führen.

Weitere Lektüre

Version: 4.0