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:
- die Umgebung zu initialisieren
- die Factory zu erhalten
- 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:
- Die URL lautet
https://example.com
- Wir booten die Anwendung, der DI-Container wird erstellt und
Application::run()
wird gestartet - Der Router dekodiert die URL als Paar
Home:default
- Ein Objekt der Klasse
HomePresenter
wird erstellt - Die Methode
renderDefault()
wird aufgerufen (falls sie existiert) - 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 💙