Wie funktionieren Anwendungen?
Sie lesen gerade das grundlegende Kapitel der Nette-Dokumentation. Sie erfahren das komplette Prinzip, wie Webanwendungen funktionieren – von A bis Z, vom Moment der Entstehung eines Requests bis zum Ende der Ausführung des PHP-Skripts. Nach der Lektüre werden Sie wissen:
- wie das Ganze funktioniert
- was Bootstrap, Presenter und der DI-Container sind
- wie die Verzeichnisstruktur aussieht
Verzeichnisstruktur
Öffnen Sie das Beispielskelett einer Webanwendung namens WebProject. Beim Lesen können Sie in die besprochenen Dateien hineinschauen.
Die Verzeichnisstruktur sieht ungefähr so aus:
web-project/ ├── app/ ← Verzeichnis der Anwendung │ ├── Core/ ← für den Betrieb notwendige Basisklassen │ │ └── RouterFactory.php ← Konfiguration der URL-Adressen │ ├── Presentation/ ← Presenter, Templates & Co. │ │ ├── @layout.latte ← Template des Layouts │ │ └── Home/ ← Verzeichnis des Home-Presenters │ │ ├── HomePresenter.php ← Klasse des Home-Presenters │ │ └── default.latte ← Template für die Aktion default │ └── Bootstrap.php ← Startklasse Bootstrap ├── assets/ ← Ressourcen (SCSS, TypeScript, Quellbilder) ├── bin/ ← von der Kommandozeile ausgeführte Skripte ├── config/ ← Konfigurationsdateien │ ├── common.neon │ └── services.neon ├── log/ ← protokollierte Fehler ├── temp/ ← temporäre Dateien, Cache, … ├── vendor/ ← von Composer installierte Bibliotheken │ ├── ... │ └── autoload.php ← Autoloading aller installierten Pakete ├── www/ ← öffentliches Verzeichnis, Document-Root des Projekts │ ├── assets/ ← kompilierte statische Dateien (CSS, JS, Bilder, ...) │ ├── .htaccess ← Regeln für mod_rewrite │ └── index.php ← Startdatei, die die Anwendung startet └── .htaccess ← verbietet den Zugriff auf alle Verzeichnisse außer www
Sie können die Verzeichnisstruktur beliebig ändern, Ordner umbenennen oder verschieben; sie ist völlig flexibel. Nette verfügt außerdem über eine intelligente Autodetection und erkennt den Ort der Anwendung samt ihrer URL-Basis automatisch.
Bei etwas größeren Anwendungen können wir die Ordner mit Presentern und Templates in Unterverzeichnisse gliedern und die Klassen in Namespaces zusammenfassen, die wir Module nennen.
Das Verzeichnis www/ stellt das öffentliche Verzeichnis bzw. den Document-Root des Projekts dar. Sie können es
umbenennen, ohne auf der Seite der Anwendung sonst etwas konfigurieren zu müssen. Sie müssen lediglich das Hosting so
einstellen, dass der Document-Root auf dieses Verzeichnis zeigt.
WebProject können Sie samt Nette auch direkt mit Composer herunterladen:
composer create-project nette/web-project
Setzen Sie unter Linux oder macOS für die Verzeichnisse log/ und temp/ Schreibrechte.
Die Anwendung WebProject ist startbereit; es ist überhaupt nichts zu konfigurieren, und Sie können sie direkt im Browser
betrachten, indem Sie den Ordner www/ aufrufen.
HTTP-Request
Alles beginnt damit, dass ein Benutzer im Browser eine Seite öffnet. Der Browser sendet einen HTTP-Request an den Server.
Dieser Request zielt auf eine einzige PHP-Datei im öffentlichen Verzeichnis www/, nämlich index.php.
Nehmen wir an, der Request gilt der Adresse https://example.com/product/123. Dank einer passenden Serverkonfiguration
wird auch diese URL auf die Datei index.php abgebildet, die dann ausgeführt wird.
Ihre Aufgabe ist es:
- die Umgebung zu initialisieren
- die Factory zu beschaffen
- die Nette-Anwendung zu starten, die den Request bearbeitet
Welche Factory? Wir stellen doch keine Traktoren her, wir bauen Websites! Nur Geduld, das wird gleich erklärt.
Mit „Initialisierung der Umgebung“ meinen wir zum Beispiel die Aktivierung von Tracy, einem großartigen Werkzeug zum Protokollieren und Visualisieren von Fehlern. Auf dem Produktionsserver protokolliert es Fehler, in der Entwicklungsumgebung zeigt es sie direkt an. Zur Initialisierung gehört daher auch die Entscheidung, ob die Website im Produktions- oder im Entwicklungsmodus läuft. Nette nutzt dafür eine intelligente Autodetection: Wenn Sie die Website auf localhost betreiben, läuft sie im Entwicklungsmodus. Sie müssen nichts konfigurieren, und die Anwendung ist sofort sowohl für die Entwicklung als auch für den Live-Betrieb bereit. Diese Schritte werden im Kapitel über die Klasse Bootstrap ausgeführt und 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-Requests ist in Nette Aufgabe der Klasse Nette\Application\Application (im Folgenden
Application). Wenn wir also sagen „die Anwendung starten“, meinen wir konkret den Aufruf der treffend benannten
Methode run() auf einem Objekt dieser Klasse.
Nette ist ein Mentor, der Sie dazu anleitet, saubere Anwendungen nach bewährten Methodiken zu schreiben. Eine der
etabliertesten davon heißt Dependency Injection, kurz DI. Wir wollen Sie an dieser Stelle nicht mit der Erklärung von DI
belasten, dafür gibt es ein eigenes Kapitel. Die wesentliche Konsequenz ist,
dass Schlüsselobjekte üblicherweise von einer Objekt-Factory erzeugt werden, die man DI-Container (kurz DIC) nennt. Ja,
das ist die Factory, von der eben die Rede war. Sie erzeugt für uns auch das Objekt Application, weshalb wir zuerst
den Container brauchen. Wir beschaffen ihn mit der Klasse Configurator, lassen ihn das Objekt
Application erzeugen, rufen darauf die Methode run() auf, und damit startet die Nette-Anwendung. Genau
das passiert in der Datei index.php.
Nette Application
Die Klasse Application hat eine einzige Aufgabe: auf den HTTP-Request zu antworten.
In Nette geschriebene Anwendungen sind in viele sogenannte Presenter unterteilt (in anderen Frameworks begegnet Ihnen vielleicht der Begriff „Controller“, was im Wesentlichen dasselbe ist). Das sind Klassen, von denen jede eine konkrete Seite der Website repräsentiert: z. B. die Startseite, ein Produkt im E-Shop, ein Anmeldeformular, einen Sitemap-Feed usw. Eine Anwendung kann von einem bis zu Tausenden von Presentern haben.
Die Application beginnt damit, dass sie den sogenannten Router fragt, welcher Presenter den aktuellen Request
bearbeiten soll. Der Router entscheidet über die Zuständigkeit. Er schaut sich die Eingabe-URL
https://example.com/product/123 an und entscheidet anhand seiner Konfiguration, dass diese Aufgabe zum Beispiel dem
Presenter Product gehört, der die Aktion show für das Produkt mit id: 123
ausführen soll. Es ist eine gute Gewohnheit, das Paar Presenter + Aktion durch einen Doppelpunkt getrennt zu schreiben, also
Product:show.
Der Router hat die URL also in das Paar Presenter:Aktion + Parameter umgewandelt, in unserem Fall
Product:show + id: 123. Wie ein solcher Router aussieht, sehen Sie in der Datei
app/Core/RouterFactory.php, und wir beschreiben ihn ausführlich im Kapitel Routing.
Gehen wir weiter. Die Application kennt nun den Namen des Presenters und kann fortfahren. Sie erzeugt dazu eine
Instanz der Klasse ProductPresenter, die den Code des Presenters Product enthält. Genauer gesagt bittet
sie den DI-Container, den Presenter zu erzeugen, denn das Erzeugen von Objekten ist dessen Aufgabe.
Der Presenter könnte so aussehen:
class ProductPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ProductRepository $repository,
) {
}
public function renderShow(int $id): void
{
// Daten aus dem Model holen und an das Template übergeben
$this->template->product = $this->repository->getProduct($id);
}
}
Der Presenter übernimmt die Bearbeitung des Requests. Die Aufgabe ist klar: die Aktion show mit
id: 123 ausführen. In der Terminologie der Presenter bedeutet das, dass die Methode renderShow()
aufgerufen wird, die im Parameter $id den Wert 123 erhält.
Ein Presenter kann mehrere Aktionen bearbeiten, also mehrere Methoden render<Aktion>() haben. Wir empfehlen
jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen.
Es wurde also die Methode renderShow(123) aufgerufen. Ihr Code ist ein erfundenes Beispiel, zeigt aber, 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 sein, ein Bild, ein XML-Dokument, das Senden
einer Datei von der Festplatte, JSON oder auch eine Weiterleitung auf eine andere Seite. Wichtig ist: Wenn wir nicht ausdrücklich
sagen, wie geantwortet werden soll (was bei ProductPresenter der Fall ist), lautet die Antwort, ein Template in eine
HTML-Seite zu rendern. Warum? Weil wir in 99 % der Fälle ein Template rendern wollen. Deshalb macht der Presenter dieses
Verhalten zum Standardverhalten und erleichtert uns die Arbeit. Das ist das Wesen von Nette.
Wir müssen nicht einmal angeben, welches Template gerendert werden soll; den Pfad leitet das Framework selbst ab. Im Fall der
Aktion show versucht es einfach, das Template show.latte in dem Verzeichnis zu laden, in dem auch die
Klasse ProductPresenter liegt. Ebenso versucht es, das Layout in der Datei @layout.latte zu finden (mehr
zur Suche nach Templates).
Danach werden die Templates gerendert. Damit ist die Aufgabe des Presenters und der gesamten Anwendung erfüllt. Existiert das Template nicht, wird eine Fehlerseite 404 zurückgegeben. Mehr über Presenter erfahren Sie auf der Seite Presenter.
Zur Sicherheit fassen wir den gesamten Ablauf noch einmal mit einer etwas anderen URL zusammen:
- Die URL ist
https://example.com - Die Anwendung startet, der DI-Container wird erzeugt und
Application::run()wird ausgeführt. - Der Router dekodiert die URL in das Paar
Home:default. - Es wird eine Instanz der Klasse
HomePresentererzeugt. - Die Methode
renderDefault()wird aufgerufen (sofern sie existiert). - Das Template, z. B.
default.latte, wird samt Layout, z. B.@layout.latte, gerendert.
Vielleicht sind Ihnen gerade viele neue Begriffe begegnet, aber wir glauben, dass sie Sinn ergeben. Anwendungen in Nette zu entwickeln ist bemerkenswert unkompliziert.
Templates
Da wir gerade bei Templates sind: Nette verwendet das Templating-System Latte.
Deshalb haben die Template-Dateien die Endung .latte. Latte wird vor allem deshalb verwendet, weil es das sicherste
Templating-System für PHP ist und zugleich das intuitivste. Sie müssen nicht viel Neues lernen; Kenntnisse von PHP und einigen
wenigen Tags genügen. Alles Nötige finden Sie in der Dokumentation.
Im Template erstellen Sie Links auf andere Presenter & Aktionen so:
<a n:href="Product:show $productId">Produktdetail</a>
Schreiben Sie einfach das vertraute Paar Presenter:Aktion statt der tatsächlichen URL und ergänzen Sie
eventuelle Parameter. Der Trick steckt in n:href, das Nette sagt, dieses Attribut zu verarbeiten. Es
erzeugt dann:
<a href="/product/456">Produktdetail</a>
Um die Erzeugung der URL kümmert sich der bereits erwähnte Router. Router sind in Nette außergewöhnlich, weil sie nicht nur
die Umwandlung einer URL in das Paar Presenter:Aktion beherrschen, sondern auch den umgekehrten Weg: aus dem Namen
des Presenters, der Aktion und den Parametern eine URL zu erzeugen. Dadurch können Sie in einer fertigen Anwendung in Nette das
URL-Format vollständig ändern, ohne in den Templates oder Presentern ein einziges Zeichen anzufassen – Sie ändern lediglich
den Router. Das ermöglicht auch die sogenannte Kanonisierung, eine weitere einzigartige Eigenschaft von Nette, die das SEO
(Search Engine Optimization) verbessert, indem sie automatisch verhindert, dass derselbe Inhalt unter verschiedenen URLs
existiert. Viele Programmierer finden das erstaunlich.
Interaktive Komponenten
Über Presenter müssen wir Ihnen noch eines sagen: Sie haben ein eingebautes Komponentensystem. Erfahrenere erinnern sich vielleicht an etwas Ähnliches aus Delphi oder ASP.NET Web Forms; React oder Vue.js bauen auf entfernt verwandten Konzepten auf. In der Welt der PHP-Frameworks ist das eine völlig einzigartige Eigenschaft.
Komponenten sind eigenständige, wiederverwendbare Einheiten, die wir in Seiten (also in Presenter) einbetten. Das können Formulare sein, Datagrids, Menüs, Umfragen – kurz alles, was sich sinnvoll wiederverwenden lässt. Wir können eigene Komponenten erstellen oder einige aus der riesigen Auswahl an Open-Source-Komponenten nutzen.
Komponenten beeinflussen die Herangehensweise an die Anwendungsentwicklung grundlegend. Sie eröffnen neue Möglichkeiten, Seiten aus vorbereiteten Einheiten zusammenzusetzen. Und sie haben auch etwas mit Hollywood gemeinsam.
DI-Container und Konfiguration
Der DI-Container, also die Objekt-Factory, ist das Herz der gesamten Anwendung.
Keine Sorge, es ist keine magische Blackbox, wie es die vorangegangenen Zeilen vielleicht nahelegen. In Wirklichkeit ist es
eine ziemlich langweilige PHP-Klasse, die Nette erzeugt und im Cache-Verzeichnis ablegt. Sie enthält viele Methoden mit Namen wie
createServiceAbcd(), von denen jede ein bestimmtes Objekt erzeugen und zurückgeben kann. Ja, darunter ist auch die
Methode createServiceApplication__application(), die die Instanz von Nette\Application\Application
erzeugt, die wir in index.php zum Starten der Anwendung gebraucht haben. Und es gibt Methoden zum Erzeugen der
einzelnen Presenter und so weiter.
Die vom DI-Container erzeugten Objekte nennt man aus irgendeinem Grund Services.
Das wirklich Besondere an dieser Klasse ist, dass Sie sie nicht programmieren – das übernimmt das Framework. Es erzeugt
tatsächlich den PHP-Code und speichert ihn auf der Festplatte. Sie geben nur Anweisungen, welche Objekte der Container erzeugen
können soll und wie genau. Diese Anweisungen stehen in Konfigurationsdateien, die das Format NEON verwenden und daher die Endung .neon haben.
Konfigurationsdateien dienen ausschließlich dazu, den DI-Container zu instruieren. Wenn Sie also zum Beispiel im Abschnitt session die Option expiration: 14 days angeben, ruft der DI-Container beim
Erzeugen des Objekts Nette\Http\Session, das die Session repräsentiert, dessen Methode
setExpiration('14 days') auf und macht die Konfiguration damit Wirklichkeit.
Für Sie ist ein ganzes Kapitel vorbereitet, das beschreibt, was sich alles konfigurieren lässt und wie Sie eigene Services definieren.
Sobald Sie sich ein wenig in das Erzeugen von Services vertieft haben, begegnet Ihnen der Begriff Autowiring. Das ist eine Eigenschaft, die Ihnen das Leben unglaublich erleichtert. Sie kann Objekte automatisch dorthin übergeben, wo Sie sie brauchen (zum Beispiel in die Konstruktoren Ihrer Klassen), ohne dass Sie etwas tun müssen. Sie werden feststellen, dass der DI-Container in Nette ein kleines Wunder ist.
Wie geht es weiter?
Wir haben die grundlegenden Prinzipien von Anwendungen in Nette durchgenommen. Bisher war es nur ein Blick an die Oberfläche, aber Sie werden bald tiefer eintauchen und mit der Zeit großartige Webanwendungen erschaffen. Wo geht es weiter? Haben Sie schon das Tutorial Erstellen Sie Ihre erste Anwendung ausprobiert?
Über das oben Beschriebene hinaus bietet Nette ein ganzes Arsenal nützlicher Klassen, eine Datenbankschicht usw. Klicken Sie sich ruhig durch die Dokumentation. Oder besuchen Sie den Blog. Sie werden viel Interessantes entdecken.
Möge Ihnen das Framework viel Freude bereiten 💙