Presenter

Wir sehen uns an, wie in Nette Presenter und Templates geschrieben werden. Nach der Lektüre werden Sie wissen:

  • wie Presenter funktionieren
  • was persistente Parameter sind
  • wie Templates gerendert werden

Wir wissen bereits, dass ein Presenter eine Klasse ist, die eine konkrete Seite einer Webanwendung repräsentiert, etwa die Startseite, ein Produkt im E-Shop, ein Anmeldeformular, einen Sitemap-Feed usw. Eine Anwendung kann von einem bis zu Tausenden von Presentern haben. In anderen Frameworks sind sie auch als Controller bekannt.

Üblicherweise meint der Begriff Presenter einen Nachfahren der Klasse Nette\Application\UI\Presenter, die sich zum Erzeugen von Weboberflächen eignet und der sich der Rest dieses Kapitels widmet. Im allgemeinen Sinn ist ein Presenter jedes Objekt, das das Interface Nette\Application\IPresenter implementiert.

Lebenszyklus des Presenters

Die Aufgabe des Presenters ist es, einen Request zu bearbeiten und eine Antwort zurückzugeben (das kann eine HTML-Seite sein, ein Bild, eine Weiterleitung usw.).

Zu Beginn wird ihm also ein Request übergeben. Das ist nicht direkt der HTTP-Request, sondern ein Objekt Nette\Application\Request, in das der HTTP-Request mithilfe des Routers umgewandelt wurde. Mit diesem Objekt kommen wir üblicherweise nicht direkt in Berührung, denn der Presenter delegiert die Bearbeitung des Requests geschickt an andere Methoden, die wir uns nun ansehen.

Lebenszyklus des Presenters

Das Diagramm zeigt eine Liste von Methoden, die der Reihe nach von oben nach unten aufgerufen werden, sofern sie existieren. Keine davon ist verpflichtend; Sie können einen völlig leeren Presenter ohne eine einzige Methode haben und darauf eine einfache statische Website aufbauen.

__construct()

Der Konstruktor gehört streng genommen nicht zum Lebenszyklus des Presenters, da er im Moment der Objekterzeugung aufgerufen wird. Wir erwähnen ihn jedoch wegen seiner Bedeutung. Der Konstruktor (zusammen mit der Inject-Methode) dient der Übergabe von Abhängigkeiten.

Der Presenter sollte nicht die Geschäftslogik der Anwendung erledigen, nicht in die Datenbank schreiben oder aus ihr lesen, keine Berechnungen anstellen usw. Dafür sind Klassen in der Schicht zuständig, die wir Model nennen. Zum Beispiel kann eine Klasse ArticleRepository für das Laden und Speichern von Artikeln verantwortlich sein. Damit der Presenter mit ihr arbeiten kann, muss sie ihm per Dependency Injection übergeben werden:

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

startup()

Unmittelbar nach dem Empfang des Requests wird die Methode startup() aufgerufen. Sie können sie zum Initialisieren von Properties, zum Prüfen von Benutzerberechtigungen usw. verwenden. Es ist erforderlich, dass diese Methode immer ihren Vorfahren aufruft: parent::startup().

action<Action>(args...)

Ähnlich wie die Methode render<View>(). Während render<View>() dazu gedacht ist, Daten für ein konkretes Template vorzubereiten, das anschließend gerendert wird, verarbeitet action<Action>() einen Request, ohne dass danach zwingend ein Template gerendert wird. Sie kann zum Beispiel Daten verarbeiten, einen Benutzer an- oder abmelden und so weiter, und dann woandershin weiterleiten.

Wichtig ist, dass action<Action>() vor render<View>() aufgerufen wird. Dadurch können wir den Verlauf des Requests in der Action-Methode noch ändern, etwa das Template, das gerendert wird, oder sogar die aufzurufende Methode render<View>() mit setView('otherView') austauschen.

Sie können mit der Methode switch('otherAction') sogar zu einer völlig anderen Aktion wechseln. Sie bricht die aktuelle Methode ab und führt stattdessen die Methoden action<Action>() und render<View>() der neuen Aktion aus (und schaltet die automatische Kanonisierung ab). Der Request selbst läuft weiter; unterbrochen wird nur die gerade laufende Methode.

Der Methode werden die Parameter aus dem Request übergeben. Es ist möglich und empfehlenswert, für diese Parameter Typen anzugeben, z. B. actionShow(int $id, ?string $slug = null). Fehlt der Parameter id oder ist er keine ganze Zahl, gibt der Presenter einen Fehler 404 zurück und endet.

handle<Signal>(args...)

Diese Methode verarbeitet die sogenannten Signale, die wir im Kapitel über Komponenten kennenlernen. Sie ist vor allem für Komponenten und die Bearbeitung von AJAX-Requests gedacht.

Der Methode werden, genau wie bei action<Action>(), die Parameter aus dem Request übergeben, samt Typprüfung.

beforeRender()

Die Methode beforeRender wird, wie ihr Name nahelegt, vor jeder Methode render<View>() aufgerufen. Sie dient der gemeinsamen Konfiguration von Templates, der Übergabe von Variablen an das Layout und Ähnlichem.

render<View>(args...)

Hier bereiten wir das Template für das anschließende Rendern vor, übergeben ihm Daten usw.

Der Methode werden, genau wie bei action<Action>(), die Parameter aus dem Request übergeben, samt Typprüfung.

public function renderShow(int $id): void
{
	// Daten aus dem Model holen und an das Template übergeben
	$this->template->article = $this->articles->getById($id);
}

afterRender()

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

shutdown()

Wird am Ende des Lebenszyklus des Presenters aufgerufen.

Events

Neben den Methoden startup(), beforeRender() und shutdown(), die im Rahmen des Lebenszyklus des Presenters aufgerufen werden, lassen sich weitere Funktionen definieren, die automatisch aufgerufen werden. Der Presenter definiert sogenannte Events, und Sie fügen deren Handler in die Arrays $onStartup, $onRender und $onShutdown ein.

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

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

Ein Rat, bevor wir weitermachen: Wie Sie sehen, kann ein Presenter mehrere Aktionen/Views bearbeiten, also mehrere Methoden render<View>() haben. Wir empfehlen jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen.

Eine Antwort senden

Die Antwort des Presenters ist typischerweise das Rendern eines Templates in eine HTML-Seite, es kann aber auch das Senden einer Datei sein, von JSON oder sogar eine Weiterleitung auf eine andere Seite.

Zu jedem Zeitpunkt des Lebenszyklus können wir mit einer der folgenden Methoden eine Antwort senden und den Presenter zugleich beenden:

Jede dieser Methoden beendet den Presenter sofort, indem sie die stille Beendigungs-Exception Nette\Application\AbortException wirft.

Rufen Sie keine dieser Methoden auf, geht der Presenter automatisch zum Rendern des Templates über. Warum? Weil wir in 99 % der Fälle ein Template rendern wollen, macht der Presenter dieses Verhalten zum Standardverhalten und erleichtert uns die Arbeit.

Der Presenter hat die Methode link(), mit der URL-Links auf andere Presenter erstellt werden. Der erste Parameter ist der Ziel-Presenter & die Aktion, gefolgt von Argumenten, die sich auch als Array übergeben lassen:

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

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

Im Template werden Links auf andere Presenter & Aktionen so erstellt:

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

Schreiben Sie einfach das vertraute Paar Presenter:action statt der tatsächlichen URL und ergänzen Sie eventuelle Parameter. Der Trick steckt in n:href, das Latte sagt, dieses Attribut zu verarbeiten und die echte URL zu erzeugen. In Nette müssen Sie überhaupt nicht über URLs nachdenken, sondern nur über Presenter und Aktionen.

Mehr dazu finden Sie im Kapitel Erstellen von URL-Links.

Weiterleitung

Zum Wechsel auf einen anderen Presenter dienen die Methoden redirect() und forward(). Sie haben eine sehr ähnliche Syntax wie die Methode link().

Die Methode forward() wechselt ohne HTTP-Weiterleitung sofort auf den neuen Presenter:

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

Beispiel für eine temporäre Weiterleitung mit dem HTTP-Code 302 (oder 303, wenn die aktuelle Request-Methode POST ist):

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

Für eine dauerhafte Weiterleitung mit dem HTTP-Code 301 verwenden Sie dies:

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

Auf eine andere URL außerhalb der Anwendung leiten Sie mit der Methode redirectUrl() weiter. Als zweiter Parameter lässt sich der HTTP-Code angeben, Standard ist 302 (oder 303, wenn die aktuelle Request-Methode POST ist):

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

Eine Weiterleitung beendet die Tätigkeit des Presenters sofort, indem sie die sogenannte stille Beendigungs-Exception Nette\Application\AbortException wirft.

Vor der Weiterleitung lassen sich Flash-Meldungen senden, also Meldungen, die nach der Weiterleitung im Template angezeigt werden.

Flash-Meldungen

Das sind Meldungen, die typischerweise über das Ergebnis einer Operation informieren. Eine wichtige Eigenschaft von Flash-Meldungen ist, dass sie im Template auch nach einer Weiterleitung verfügbar bleiben. Nach der Anzeige bleiben sie noch 30 Sekunden aktiv – lädt der Benutzer die Seite zum Beispiel wegen eines Übertragungsfehlers neu, verschwindet die Meldung nicht sofort.

Rufen Sie einfach die Methode flashMessage() auf, um die Übergabe an das Template kümmert sich der Presenter. Der erste Parameter ist der Text der Meldung, der optionale zweite Parameter ihr Typ (z. B. error, warning, info). Die Methode flashMessage() gibt eine Instanz der Flash-Meldung zurück, der sich weitere Informationen hinzufügen lassen.

$this->flashMessage('Das Element wurde gelöscht.');
$this->redirect(/* ... */); // und weiterleiten

Im Template stehen diese Meldungen in der Variablen $flashes als Objekte stdClass zur Verfügung, die die Properties message (Text der Meldung) und type (Typ der Meldung) sowie gegebenenfalls die erwähnten eigenen Informationen enthalten. Wir rendern sie so:

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

Fehler 404 usw.

Kann der Request nicht erfüllt werden, zum Beispiel weil der Artikel, den wir anzeigen wollen, nicht in der Datenbank existiert, werfen wir mit der Methode error(string $message = '', int $httpCode = 404) einen Fehler 404.

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

Der HTTP-Fehlercode lässt sich als zweiter Parameter übergeben, Standard ist 404. Die Methode funktioniert so, dass sie eine Nette\Application\BadRequestException wirft, woraufhin die Application die Steuerung an den Error-Presenter übergibt. Das ist ein Presenter, dessen Aufgabe es ist, eine Seite mit Informationen über den aufgetretenen Fehler anzuzeigen. Der Error-Presenter wird in der Konfiguration der Anwendung eingestellt.

JSON senden

Die Methode sendJson($data) kodiert die übergebenen Daten in JSON, sendet sie als HTTP-Antwort und beendet den Presenter. Beispiel:

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

Parameter des Requests

Der Presenter und ebenso jede Komponente beziehen ihre Parameter aus dem HTTP-Request. Ihre Werte holen Sie sich mit den Methoden getParameter($name) oder getParameters(). Die Werte sind Strings oder Arrays von Strings, im Grunde rohe Daten direkt aus der URL.

Für mehr Komfort empfehlen wir, auf die Parameter über Properties zuzugreifen. Kennzeichnen Sie sie einfach mit dem Attribut #[Parameter]:

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

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

Für die Property empfehlen wir, den Datentyp anzugeben (z. B. string), Nette wandelt den Wert dann automatisch entsprechend um. Die Werte der Parameter lassen sich auch validieren.

Beim Erstellen eines Links lässt sich der Wert des Parameters direkt setzen:

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

Persistente Parameter

Persistente Parameter dienen dazu, den Zustand über verschiedene Requests hinweg zu erhalten. Ihr Wert bleibt auch nach dem Klick auf einen Link derselbe. Anders als Daten in der Session werden sie in der URL übertragen. Und das geschieht vollautomatisch, sie müssen also nicht ausdrücklich in link() oder n:href angegeben werden.

Ein Anwendungsbeispiel? Stellen Sie sich vor, Sie haben eine mehrsprachige Anwendung. Die aktuelle Sprache ist ein Parameter, der immer Teil der URL sein muss. Aber ihn in jedem Link anzugeben wäre unglaublich mühsam. Also machen Sie daraus den persistenten Parameter lang, und er wird automatisch mitgeführt. Praktisch!

Einen persistenten Parameter in Nette zu erstellen ist ausgesprochen einfach. Legen Sie einfach eine public Property an 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 public sein
}

Hat $this->lang zum Beispiel den Wert 'en', enthalten auch die mit link() oder n:href erstellten Links den Parameter lang=en. Und nach dem Klick auf den Link ist $this->lang wieder 'en'.

Für die Property empfehlen wir, den Datentyp anzugeben (z. B. string), und Sie können auch einen Standardwert angeben. Die Werte der Parameter lassen sich validieren.

Persistente Parameter werden üblicherweise zwischen allen Aktionen eines bestimmten Presenters übertragen. Damit sie auch über mehrere Presenter hinweg übertragen werden, müssen sie definiert werden entweder:

  • in einem gemeinsamen Vorfahren, von dem die Presenter erben
  • oder in einem Trait, den die Presenter verwenden:
trait LanguageAware
{
	#[Persistent]
	public string $lang;
}

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

Beim Erstellen eines Links lässt sich der Wert eines persistenten Parameters ändern:

<a n:href="Product:show $id, lang: cs">Detail auf Tschechisch</a>

Oder er lässt sich zurücksetzen, also aus der URL entfernen. Er nimmt dann seinen Standardwert an:

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

Gemeinsamer Parameterraum

Die Parameter des Requests, die persistenten Parameter und die Parameter der Methoden action, render und handle (Signal) teilen sich einen einzigen Raum, in dem jeder über seinen Namen identifiziert wird. Erscheint derselbe Name in mehreren von ihnen, bezeichnen sie ein und denselben Wert.

Das nutzt man oft zum Vorteil. Zum Beispiel sind der persistente Parameter lang und das Argument $lang einer Action- oder Signal-Methode ein und dasselbe – Sie können den aktuellen Wert eines persistenten Parameters lesen, indem Sie ihn einfach in der Signatur der Methode aufführen:

#[Persistent]
public string $lang;

public function handleSearch(string $query, string $lang): void
{
	// $lang enthält den aktuellen Wert des persistenten Parameters lang
}

Weil dieser Raum gemeinsam ist, halten Sie die Namen der Parameter eindeutig, sofern sie sich nicht absichtlich einen Wert teilen sollen. Das gilt auch für Signale, die zusätzlich Parameter aus dem POST-Body des Requests lesen, siehe Signale im Detail.

Interaktive Komponenten

Presenter haben ein eingebautes Komponentensystem. Komponenten sind eigenständige, wiederverwendbare Einheiten, die wir in Presenter einbetten. Das können Formulare sein, Datagrids, Menüs – kurz alles, was sich sinnvoll wiederholt verwenden lässt.

Wie werden Komponenten in Presenter eingebettet und anschließend verwendet? Das erfahren Sie im Kapitel Komponenten. Sie erfahren dort sogar, was sie mit Hollywood gemeinsam haben.

Und woher bekomme ich Komponenten? Auf Componette finden Sie Open-Source-Komponenten und viele weitere Erweiterungen für Nette, beigesteuert von Freiwilligen aus der Community des Frameworks.

Tiefer eintauchen

Was wir in diesem Kapitel bisher behandelt haben, dürfte für die meisten Anwendungsfälle genügen. Die folgenden Abschnitte sind für alle gedacht, die sich intensiver mit Presentern beschäftigen und absolut alles wissen wollen.

Validierung der Parameter

Die aus der URL empfangenen Werte der Parameter des Requests und Persistente Parameter werden von der Methode loadState() in die Properties geschrieben. Sie prüft außerdem, ob der in der Property angegebene Datentyp passt; andernfalls antwortet sie mit dem Fehler 404 und die Seite wird nicht angezeigt.

Vertrauen Sie den aus der URL empfangenen Parametern niemals blind, denn sie lassen sich vom Benutzer leicht überschreiben. So würden wir zum Beispiel prüfen, ob die Sprache $this->lang zu den unterstützten gehört. Ein geeigneter Weg dafür ist, die 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 $this->lang gesetzt
		// es folgt die eigene Wertprüfung:
		if (!in_array($this->lang, ['en', 'cs'])) {
			$this->error();
		}
	}
}

Request speichern und wiederherstellen

Der vom Presenter bearbeitete Request ist ein Objekt Nette\Application\Request, das die Methode getRequest() des Presenters zurückgibt.

Der aktuelle Request lässt sich in der Session speichern oder umgekehrt aus ihr wiederherstellen und vom Presenter erneut ausführen. Das ist zum Beispiel nützlich, wenn ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft. Damit keine Daten verloren gehen, speichern wir den aktuellen Request vor der Weiterleitung auf die Anmeldeseite mit $reqId = $this->storeRequest() in der Session. Das gibt seinen Bezeichner als kurzen String zurück, den wir dann als Parameter an den Anmelde-Presenter übergeben.

Nach der Anmeldung rufen wir die Methode $this->restoreRequest($reqId) auf, die den Request aus der Session holt. POST-Requests werden an ihn weitergeleitet, die übrigen (GET) werden auf die URL des Requests weitergeleitet. Die Methode prüft, ob der Request von demselben Benutzer erstellt wurde, der nun angemeldet ist. Meldet sich ein anderer Benutzer an oder ist der Schlüssel ungültig, tut sie nichts und das Programm läuft wie gewohnt weiter.

Siehe die Anleitung Wie man zu einer früheren Seite zurückkehrt.

Kanonisierung

Presenter haben eine wirklich ausgezeichnete Eigenschaft, die zu besserem SEO (Search Engine Optimization) beiträgt. Sie verhindern automatisch, dass derselbe Inhalt unter verschiedenen URLs existiert. Führen mehrere URLs zu einem bestimmten Ziel, z. B. /index und /index?page=1, bestimmt das Framework eine davon als primär (kanonisch) und leitet die übrigen mit dem HTTP-Code 301 dorthin weiter. Dadurch indexieren Suchmaschinen Ihre Seiten nicht doppelt und verwässern deren Page Rank nicht.

Dieser Vorgang heißt Kanonisierung. Die kanonische URL ist diejenige, die der Router erzeugt, typischerweise die erste passende Route in der Collection.

Die Kanonisierung ist standardmäßig eingeschaltet und lässt sich mit $this->autoCanonicalize = false abschalten.

Bei AJAX- oder POST-Requests findet keine Weiterleitung statt, da dies zu Datenverlust führen könnte oder keinen zusätzlichen SEO-Nutzen brächte.

Die Kanonisierung lässt sich auch manuell mit der Methode canonicalize() auslösen. Ähnlich wie der Methode link() übergeben Sie ihr den Presenter, die Aktion und die Parameter. Sie erzeugt einen Link und vergleicht ihn mit der aktuellen URL-Adresse. Unterscheiden sie sich, leitet sie auf den erzeugten Link weiter.

public function actionShow(int $id, ?string $slug = null): void
{
	$realSlug = $this->facade->getSlugForId($id);
	// leitet weiter, wenn $slug von $realSlug abweicht
	$this->canonicalize('Product:show', [$id, $realSlug]);
}

Ein vollständiges Muster, das Route-Filter mit canonicalize() kombiniert, um SEO-freundliche URLs zu erzeugen, finden Sie unter Schöne URLs mit Slugs.

Antworten

Die vom Presenter zurückgegebene Antwort ist ein Objekt, das das Interface Nette\Application\Response implementiert. Es stehen mehrere fertige Antworten zur Verfügung:

Antworten werden mit der Methode sendResponse() gesendet:

use Nette\Application\Responses;

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

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

// Sendet einen Callback
$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));

Sie können auch eine eigene Antwort schreiben. Implementieren Sie einfach das Interface Nette\Application\Response, das eine einzige Methode send() hat, die den HTTP-Request und die HTTP-Response entgegennimmt. Das ist zum Beispiel nützlich, wenn Sie Daten streamen, die Sie nicht im Speicher halten wollen:

class CsvResponse implements Nette\Application\Response
{
	public function __construct(
		private string $fileName,
		private iterable $rows,
	) {
	}

	public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void
	{
		$response->setContentType('text/csv', 'utf-8');
		$response->sendAsFile($this->fileName);

		$handle = fopen('php://output', 'w');
		foreach ($this->rows as $row) {
			fputcsv($handle, $row);
		}

		fclose($handle);
	}
}

Im Presenter senden Sie sie dann wie gewohnt: $this->sendResponse(new CsvResponse('export.csv', $rows));

HTTP-Caching

Die Methode lastModified() macht es leicht, HTTP-Caching zu nutzen. Sie übergeben ihr Datum und Uhrzeit der letzten Änderung des Inhalts (als Timestamp, String oder Objekt DateTimeInterface) und optional einen ETag-Validator (einen kurzen String, der die aktuelle Version des Inhalts identifiziert, etwa deren Hash) sowie eine Ablaufzeit. Hat der Browser bereits eine passende Version, sendet der Presenter die Antwort 304 Not Modified und endet, sodass die Seite nicht unnötig gerendert und übertragen wird:

public function renderArticle(int $id): void
{
	$article = $this->articles->getById($id);
	$this->lastModified($article->updatedAt);
	// ...
}

Fertigstellung des Templates

Wenn der Presenter ein Template rendert, ruft die Methode sendTemplate() unmittelbar vor dem Rendern completeTemplate() auf. Diese Methode füllt die mit dem Attribut #[TemplateVariable] gekennzeichneten Variablen und ermittelt die Template-Datei (die Standardvariablen setzt bereits die TemplateFactory beim Erzeugen des Templates). Sie können diese protected-Methode überschreiben, um Variablen zu ergänzen, die alle Views gemeinsam haben, oder um eine andere Datei zu setzen:

protected function completeTemplate(Nette\Application\UI\Template $template): void
{
	parent::completeTemplate($template);
	$template->siteName = 'Meine App';
}

Zugriffsbeschränkung mit #[Requires]

Das Attribut #[Requires] bietet fortgeschrittene Möglichkeiten, den Zugriff auf Presenter und deren Methoden einzuschränken. Damit lassen sich HTTP-Methoden festlegen, ein AJAX-Request verlangen, der Zugriff auf denselben Origin beschränken und der Zugriff nur über Forwarding erlauben. Das Attribut lässt sich sowohl auf Presenter-Klassen als auch auf einzelne Methoden wie action<Action>(), render<View>(), handle<Signal>() und createComponent<Name>() anwenden.

Sie können diese Einschränkungen angeben:

  • auf HTTP-Methoden: #[Requires(methods: ['GET', 'POST'])]
  • Erfordernis eines AJAX-Requests: #[Requires(ajax: true)]
  • Zugriff nur vom selben Origin: #[Requires(sameOrigin: true)]
  • Zugriff nur über Forwarding: #[Requires(forward: true)]
  • Einschränkung auf bestimmte Aktionen: #[Requires(actions: 'default')]

Seit Version 3.3 wird die Übereinstimmung des Origins über den Browser-Header Sec-Fetch-Site geprüft (früher über ein SameSite-Cookie), was zuverlässiger ist und die exakte Übereinstimmung von Schema, Domain und Port prüft.

Details finden Sie in der Anleitung Wie man das Attribut Requires verwendet.

Prüfung der HTTP-Methode

Presenter in Nette prüfen bei jedem eingehenden Request automatisch die HTTP-Methode, vor allem aus Sicherheitsgründen. Standardmäßig sind die Methoden GET, POST, HEAD, PUT, DELETE, PATCH erlaubt.

Wollen Sie zusätzlich zum Beispiel die Methode OPTIONS erlauben, verwenden Sie das Attribut #[Requires] (seit Nette Application v3.2.3):

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

Seit Version 3.1.13 erfolgt die Prüfung in checkHttpMethod(), die prüft, ob die im Request angegebene Methode im Array $presenter->allowedMethods enthalten ist. Seit Version 3.2.3 ist dieser Ansatz zugunsten von #[Requires] veraltet. Die Methode können Sie so überschreiben:

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

Wichtig ist zu betonen: Wenn Sie die Methode OPTIONS aktivieren, müssen Sie sie anschließend in Ihrem Presenter auch angemessen behandeln. Diese Methode wird oft als sogenannter Preflight-Request verwendet, den der Browser automatisch vor dem eigentlichen Request sendet, wenn festgestellt werden muss, ob der Request nach der CORS-Richtlinie (Cross-Origin Resource Sharing) zulässig ist. Aktivieren Sie die Methode, ohne die richtige Antwort zu implementieren, kann das zu Inkonsistenzen und potenziellen Sicherheitsproblemen führen.

Veraltete Aktionen kennzeichnen

Das Attribut #[Deprecated] kennzeichnet Aktionen, Signale oder ganze Presenter als veraltet und für eine künftige Entfernung vorgesehen. Beim Erzeugen von Links auf veraltete Teile der Anwendung wirft Nette eine Warnung, um die Entwickler darauf aufmerksam zu machen.

Das Attribut lässt sich entweder auf die gesamte Presenter-Klasse oder auf einzelne Methoden action<Action>(), render<View>() und handle<Signal>() anwenden.

Weiterführende Lektüre

Version: 4.x