Wie funktionieren Anwendungen?

Sie lesen gerade das grundlegende Dokument der Nette-Dokumentation. Sie werden das gesamte Funktionsprinzip von Webanwendungen kennenlernen. Schön von A bis Z, von dem Moment der Entstehung bis zum letzten Atemzug des PHP-Skripts. Nach dem Lesen werden Sie wissen:

  • wie das Ganze funktioniert
  • was Bootstrap, Presenter und DI-Container sind
  • wie die Verzeichnisstruktur aussieht

Verzeichnisstruktur

Öffnen Sie das Beispiel-Skelett einer Webanwendung namens WebProject und beim Lesen können Sie sich die Dateien ansehen, von denen die Rede ist.

Die Verzeichnisstruktur sieht ungefähr so aus:

web-project/
├── app/                      ← Verzeichnis mit der Anwendung
│   ├── Core/                 ← grundlegende Klassen, die für den Betrieb notwendig sind
│   │   └── RouterFactory.php ← Konfiguration der URL-Adressen
│   ├── Presentation/         ← Presenter, Templates & Co.
│   │   ├── @layout.latte     ← Layout-Template
│   │   └── Home/             ← Verzeichnis des Home-Presenters
│   │       ├── HomePresenter.php ← Klasse des Home-Presenters
│   │       └── default.latte ← Template der default-Aktion
│   └── Bootstrap.php         ← Startklasse Bootstrap
├── bin/                      ← Skripte, die von der Kommandozeile ausgeführt werden
├── config/                   ← Konfigurationsdateien
│   ├── common.neon
│   └── services.neon
├── log/                      ← protokollierte Fehler
├── temp/                     ← temporäre Dateien, Cache, …
├── vendor/                   ← Bibliotheken, die mit Composer installiert wurden
│   ├── ...
│   └── autoload.php          ← Autoloading aller installierten Pakete
├── www/                      ← öffentliches Verzeichnis oder Document-Root des Projekts
│   ├── .htaccess             ← mod_rewrite-Regeln
│   └── index.php             ← initiale Datei, mit der die Anwendung gestartet wird
└── .htaccess                 ← verbietet den Zugriff auf alle Verzeichnisse außer www

Die Verzeichnisstruktur können Sie beliebig ändern, Ordner umbenennen oder verschieben, sie ist völlig flexibel. Nette verfügt zudem über eine intelligente Autodetektion und erkennt automatisch den Speicherort der Anwendung einschließlich ihrer URL-Basis.

Bei etwas größeren Anwendungen können wir die Ordner mit Presentern und Templates in Unterverzeichnisse aufteilen und Klassen in Namespaces, die wir Module nennen.

Das Verzeichnis www/ stellt das sogenannte öffentliche Verzeichnis oder Document-Root des Projekts dar. Sie können es umbenennen, ohne etwas Weiteres auf Anwendungsseite einstellen zu müssen. Es ist nur notwendig, das Hosting zu konfigurieren, damit der Document-Root auf dieses Verzeichnis zeigt.

WebProject können Sie sich auch direkt inklusive Nette herunterladen, und zwar mittels Composer:

composer create-project nette/web-project

Unter Linux oder macOS setzen Sie für die Verzeichnisse log/ und temp/ Schreibrechte.

Die Anwendung WebProject ist startbereit, es muss überhaupt nichts konfiguriert werden und Sie können sie direkt im Browser anzeigen, indem Sie auf den Ordner www/ zugreifen.

HTTP-Anfrage

Alles beginnt in dem Moment, in dem der Benutzer im Browser eine Seite öffnet. Also wenn der Browser beim Server mit einer HTTP-Anfrage anklopft. Die Anfrage zielt auf eine einzige PHP-Datei, die sich im öffentlichen Verzeichnis www/ befindet, und das ist index.php. Nehmen wir an, es handelt sich um eine Anfrage an die Adresse https://example.com/product/123. Dank geeigneter Serverkonfiguration wird auch diese URL auf die Datei index.php abgebildet und diese wird ausgeführt.

Ihre Aufgabe ist es:

  1. die Umgebung zu initialisieren
  2. die Factory zu erhalten
  3. die Nette-Anwendung zu starten, die die Anfrage bearbeitet

Welche Factory denn? Wir stellen doch keine Traktoren her, sondern Webseiten! Bleiben Sie dran, das wird gleich erklärt.

Mit „Initialisierung der Umgebung“ meinen wir zum Beispiel, dass Tracy aktiviert wird, ein großartiges Werkzeug zur Protokollierung oder Visualisierung von Fehlern. Auf dem Produktionsserver protokolliert es Fehler, auf dem Entwicklungsserver zeigt es sie direkt an. Zur Initialisierung gehört also auch die Entscheidung, ob die Website im Produktions- oder Entwicklungsmodus läuft. Dazu verwendet Nette eine intelligente Autodetektion: Wenn Sie die Website auf localhost starten, läuft sie im Entwicklungsmodus. Sie müssen also nichts konfigurieren und die Anwendung ist direkt bereit sowohl für die Entwicklung als auch für den Live-Einsatz. Diese Schritte werden durchgeführt und sind im Kapitel über die Bootstrap-Klasse ausführlich beschrieben.

Der dritte Punkt (ja, den zweiten haben wir übersprungen, aber wir kommen darauf zurück) ist der Start der Anwendung. Die Bearbeitung von HTTP-Anfragen übernimmt in Nette die Klasse Nette\Application\Application (weiter Application), wenn wir also sagen, die Anwendung starten, meinen wir konkret den Aufruf der Methode mit dem treffenden Namen run() auf einem Objekt dieser Klasse.

Nette ist ein Mentor, der Sie dazu anleitet, saubere Anwendungen nach bewährten Methoden zu schreiben. Und eine der absolut bewährtesten heißt Dependency Injection, abgekürzt DI. An dieser Stelle möchten wir Sie nicht mit der Erklärung von DI belasten, dafür gibt es ein eigenes Kapitel, wesentlich ist die Konsequenz, dass uns Schlüsselobjekte üblicherweise von einer Objekt-Factory erstellt werden, die DI-Container (abgekürzt DIC) genannt wird. Ja, das ist die Factory, von der vorhin die Rede war. Und sie stellt uns auch das Application-Objekt her, deshalb benötigen wir zuerst den Container. Wir erhalten ihn mittels der Klasse Configurator und lassen ihn das Application-Objekt erstellen, rufen darauf die Methode run() auf und damit startet die Nette-Anwendung. Genau das geschieht in der Datei index.php.

Nette Application

Die Klasse Application hat eine einzige Aufgabe: auf eine HTTP-Anfrage zu antworten.

Anwendungen, die in Nette geschrieben sind, gliedern sich in viele sogenannte Presenter (in anderen Frameworks können Sie auf den Begriff Controller stoßen, es handelt sich um dasselbe), das sind Klassen, von denen jede eine bestimmte Webseite repräsentiert: z.B. die Homepage; ein Produkt im E-Shop; ein Anmeldeformular; ein Sitemap-Feed usw. Eine Anwendung kann von einem bis zu Tausenden von Presentern haben.

Die Application beginnt damit, den sogenannten Router zu bitten, zu entscheiden, welchem der Presenter die aktuelle Anfrage zur Bearbeitung übergeben werden soll. Der Router entscheidet, wessen Verantwortung das ist. Er schaut sich die Eingabe-URL https://example.com/product/123 an und entscheidet auf Basis seiner Konfiguration, dass dies die Arbeit z.B. für den Presenter Product ist, von dem er als Aktion die Anzeige (show) des Produkts mit id: 123 verlangen wird. Das Paar Presenter + Aktion wird üblicherweise durch einen Doppelpunkt getrennt als Product:show geschrieben.

Also hat der Router die URL in das Paar Presenter:action + Parameter transformiert, in unserem Fall Product:show + id: 123. Wie ein solcher Router aussieht, können Sie sich in der Datei app/Core/RouterFactory.php ansehen und wir beschreiben ihn detailliert im Kapitel Routing.

Gehen wir weiter. Die Application kennt nun den Namen des Presenters und kann weitermachen. Indem sie ein Objekt der Klasse ProductPresenter erstellt, was der Code des Presenters Product ist. Genauer gesagt, bittet sie den DI-Container, den Presenter zu erstellen, denn für das Erstellen ist er da.

Der Presenter kann etwa so aussehen:

class ProductPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ProductRepository $repository,
	) {
	}

	public function renderShow(int $id): void
	{
		// Wir holen Daten aus dem Modell und übergeben sie an das Template
		$this->template->product = $this->repository->getProduct($id);
	}
}

Die Bearbeitung der Anfrage übernimmt der Presenter. Und die Aufgabe lautet klar: führe die Aktion show mit id: 123 aus. Was in der Sprache der Presenter bedeutet, dass die Methode renderShow() aufgerufen wird und im Parameter $id den Wert 123 erhält.

Ein Presenter kann mehrere Aktionen bedienen, also mehrere Methoden render<Aktion>() haben. Wir empfehlen jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen.

Also, die Methode renderShow(123) wurde aufgerufen, deren Code zwar ein fiktives Beispiel ist, aber Sie können daran sehen, wie Daten an das Template übergeben werden, nämlich durch Schreiben in $this->template.

Anschließend gibt der Presenter eine Antwort zurück. Das kann eine HTML-Seite, ein Bild, ein XML-Dokument, das Senden einer Datei von der Festplatte, JSON oder auch eine Weiterleitung auf eine andere Seite sein. Wichtig ist, dass wenn wir nicht explizit sagen, wie geantwortet werden soll (was der Fall bei ProductPresenter ist), die Antwort das Rendern eines Templates mit einer HTML-Seite sein wird. Warum? Weil wir in 99 % der Fälle ein Template rendern wollen, daher nimmt der Presenter dieses Verhalten als Standard an und möchte uns die Arbeit erleichtern. Das ist der Sinn von Nette.

Wir müssen nicht einmal angeben, welches Template gerendert werden soll, den Pfad dazu leitet er selbst ab. Im Falle der Aktion show versucht er einfach, das Template show.latte im Verzeichnis der Klasse ProductPresenter zu laden. Ebenso versucht er, das Layout in der Datei @layout.latte zu finden (mehr dazu unter Template-Suche).

Und anschließend rendert er die Templates. Damit ist die Aufgabe des Presenters und der gesamten Anwendung erfüllt und das Werk vollendet. Wenn das Template nicht existiert, wird eine Seite mit dem Fehler 404 zurückgegeben. Mehr über Presenter erfahren Sie auf der Seite Presenter.

Zur Sicherheit versuchen wir, den gesamten Prozess mit einer etwas anderen URL zu rekapitulieren:

  1. Die URL lautet https://example.com
  2. Wir booten die Anwendung, der DI-Container wird erstellt und Application::run() wird gestartet
  3. Der Router dekodiert die URL als Paar Home:default
  4. Ein Objekt der Klasse HomePresenter wird erstellt
  5. Die Methode renderDefault() wird aufgerufen (falls sie existiert)
  6. Das Template z.B. default.latte mit dem Layout z.B. @layout.latte wird gerendert

Vielleicht sind Sie jetzt auf viele neue Begriffe gestoßen, aber wir glauben, dass sie Sinn ergeben. Die Entwicklung von Anwendungen in Nette ist unglaublich entspannt.

Templates

Wo wir schon bei Templates sind, in Nette wird das Template-System Latte verwendet. Daher auch die Endungen .latte bei den Templates. Latte wird zum einen verwendet, weil es das sicherste Template-System für PHP ist, und zum anderen auch das intuitivste System. Sie müssen nicht viel Neues lernen, Sie kommen mit PHP-Kenntnissen und ein paar Tags aus. Alles erfahren Sie in der Dokumentation.

Im Template werden Links erstellt zu anderen Presentern & Aktionen wie folgt:

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

Einfach statt der realen URL schreiben Sie das bekannte Paar Presenter:action und geben eventuelle Parameter an. Der Trick liegt im n:href, das besagt, dass dieses Attribut von Nette verarbeitet wird. Und es generiert:

<a href="/product/456">Produktdetail</a>

Die Generierung der URL übernimmt der bereits erwähnte Router. Denn Router in Nette sind außergewöhnlich, da sie nicht nur Transformationen von URL zum Paar Presenter:Aktion durchführen können, sondern auch umgekehrt, also aus dem Namen des Presenters + Aktion + Parametern eine URL generieren. Dadurch können Sie in Nette die Formen der URLs in der gesamten fertigen Anwendung vollständig ändern, ohne ein einziges Zeichen im Template oder Presenter zu ändern. Nur durch die Anpassung des Routers. Dadurch funktioniert auch die sogenannte Kanonisierung, was eine weitere einzigartige Eigenschaft von Nette ist, die zu besserem SEO (Optimierung der Auffindbarkeit im Internet) beiträgt, indem sie automatisch die Existenz von doppeltem Inhalt unter verschiedenen URLs verhindert. Viele Programmierer finden das erstaunlich.

Interaktive Komponenten

Über Presenter müssen wir Ihnen noch eine Sache verraten: Sie haben ein eingebautes Komponentensystem. Etwas Ähnliches kennen Ältere vielleicht aus Delphi oder ASP.NET Web Forms, auf etwas entfernt Ähnlichem bauen React oder Vue.js auf. In der Welt der PHP-Frameworks ist dies eine absolut einzigartige Angelegenheit.

Komponenten sind eigenständige wiederverwendbare Einheiten, die wir in Seiten (also Presenter) einfügen. Das können Formulare, Datagrids, Menüs, Abstimmungsumfragen sein, eigentlich alles, was sinnvoll wiederverwendet werden kann. Wir können eigene Komponenten erstellen oder einige aus dem riesigen Angebot an Open-Source-Komponenten verwenden.

Komponenten beeinflussen grundlegend den Ansatz zur Anwendungsentwicklung. Sie eröffnen Ihnen neue Möglichkeiten, Seiten aus vorgefertigten Einheiten zusammenzusetzen. Und außerdem haben sie etwas mit Hollywood gemeinsam.

DI-Container und Konfiguration

Der DI-Container oder die Objekt-Factory ist das Herz der gesamten Anwendung.

Keine Sorge, es ist keine magische Blackbox, wie es vielleicht aus den vorherigen Zeilen scheinen mag. Eigentlich ist es eine ziemlich langweilige PHP-Klasse, die Nette generiert und im Cache-Verzeichnis speichert. Sie hat viele Methoden namens createServiceAbcd() und jede von ihnen kann ein bestimmtes Objekt erstellen und zurückgeben. Ja, es gibt auch eine Methode createServiceApplication(), die Nette\Application\Application erstellt, das wir in der Datei index.php zum Starten der Anwendung benötigten. Und es gibt Methoden, die einzelne Presenter erstellen. Und so weiter.

Objekte, die der DI-Container erstellt, werden aus irgendeinem Grund Dienste genannt.

Was an dieser Klasse wirklich besonders ist, ist, dass nicht Sie sie programmieren, sondern das Framework. Es generiert tatsächlich den PHP-Code und speichert ihn auf der Festplatte. Sie geben nur Anweisungen, welche Objekte der Container erstellen können soll und wie genau. Und diese Anweisungen sind in Konfigurationsdateien geschrieben, für die das Format NEON verwendet wird und die daher auch die Erweiterung .neon haben.

Konfigurationsdateien dienen rein dazu, den DI-Container zu instruieren. Wenn ich also zum Beispiel im Abschnitt session die Option expiration: 14 days angebe, ruft der DI-Container beim Erstellen des Objekts Nette\Http\Session, das die Session repräsentiert, dessen Methode setExpiration('14 days') auf und damit wird die Konfiguration Realität.

Es gibt für Sie ein ganzes Kapitel, das beschreibt, was alles konfiguriert werden kann und wie man eigene Dienste definiert.

Sobald Sie etwas tiefer in die Erstellung von Diensten eintauchen, werden Sie auf das Wort Autowiring stoßen. Das ist ein Feature, das Ihnen das Leben unglaublich vereinfacht. Es kann Objekte automatisch dorthin übergeben, wo Sie sie benötigen (z.B. in den Konstruktoren Ihrer Klassen), ohne dass Sie etwas tun müssen. Sie werden feststellen, dass der DI-Container in Nette ein kleines Wunder ist.

Wohin als nächstes?

Wir sind die Grundprinzipien von Anwendungen in Nette durchgegangen. Bisher sehr oberflächlich, aber bald werden Sie in die Tiefe vordringen und mit der Zeit wunderbare Webanwendungen erstellen. Wohin soll es als nächstes gehen? Haben Sie schon das Tutorial Meine erste Anwendung schreiben ausprobiert?

Neben dem oben Beschriebenen verfügt Nette über ein ganzes Arsenal an nützlichen Klassen, eine Datenbankschicht, usw. Versuchen Sie doch mal, einfach so durch die Dokumentation zu klicken. Oder den Blog. Sie werden viele interessante Dinge entdecken.

Möge Ihnen das Framework viel Freude bereiten 💙

Version: 4.0