Bootstrapping
Bootstrapping ist der Prozess der Initialisierung der Anwendungsumgebung, der Erzeugung des Dependency-Injection-Containers (DI) und des Startens der Anwendung. Wir besprechen:
- wie die Klasse Bootstrap die Umgebung initialisiert
- wie Anwendungen mit NEON-Dateien konfiguriert werden
- wie man Produktions- und Entwicklungsmodus unterscheidet
- wie man den DI-Container erzeugt und konfiguriert
Anwendungen, seien es Web-Anwendungen oder von der Kommandozeile ausgeführte Skripte, beginnen ihre Ausführung mit einer Form
der Initialisierung der Umgebung. Früher kümmerte sich darum eine Datei mit einem Namen wie include.inc.php, die
von der Startdatei eingebunden wurde. In modernen Nette-Anwendungen wurde sie durch die Klasse Bootstrap ersetzt, die
als Teil der Anwendung in der Datei app/Bootstrap.php zu finden ist. Sie kann zum Beispiel so aussehen:
namespace App;
use Nette;
use Nette\Bootstrap\Configurator;
class Bootstrap
{
private Configurator $configurator;
private string $rootDir;
public function __construct()
{
$this->rootDir = dirname(__DIR__);
// Der Configurator ist für das Einrichten der Anwendungsumgebung und der Services zuständig.
$this->configurator = new Configurator;
// Verzeichnis für von Nette erzeugte temporäre Dateien festlegen (z. B. kompilierte Templates)
$this->configurator->setTempDirectory($this->rootDir . '/temp');
}
public function bootWebApplication(): Nette\DI\Container
{
$this->initializeEnvironment();
$this->setupContainer();
return $this->configurator->createContainer();
}
private function initializeEnvironment(): void
{
// Nette ist schlau, und der Entwicklungsmodus schaltet sich automatisch ein,
// oder Sie aktivieren ihn für eine bestimmte IP-Adresse, indem Sie die folgende Zeile einkommentieren:
// $this->configurator->setDebugMode('secret@23.75.345.200');
// Aktiviert Tracy: das ultimative "Schweizer Taschenmesser" zum Debuggen.
$this->configurator->enableTracy($this->rootDir . '/log');
// RobotLoader: lädt automatisch alle Klassen im gewählten Verzeichnis
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
}
private function setupContainer(): void
{
// Konfigurationsdateien laden
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
index.php
Bei Webanwendungen ist die Startdatei index.php, die im öffentlichen Verzeichnis www/ liegt. Sie lässt
die Klasse Bootstrap die Umgebung initialisieren und den DI-Container erzeugen. Anschließend holt sie sich aus dem Container den
Service Application, der die Webanwendung startet:
$bootstrap = new App\Bootstrap;
// Umgebung initialisieren + DI-Container erzeugen
$container = $bootstrap->bootWebApplication();
// Der DI-Container erzeugt ein Objekt Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
// Die Nette-Anwendung starten und den eingehenden Request verarbeiten
$application->run();
Das Objekt $application löst während der Verarbeitung des Requests Events aus – onStartup, onRequest,
onPresenter, onResponse, onShutdown und onError (bei einer unbehandelten
Exception). Sie können daran Handler hängen, was sich für Logging oder anwendungsweites Monitoring anbietet.
Wie Sie sehen, hilft die Klasse Nette\Bootstrap\Configurator beim Einrichten der Umgebung und beim Erzeugen des Dependency-Injection-Containers (DI). Stellen wir sie nun genauer vor.
Entwicklungs- vs. Produktionsmodus
Nette verhält sich unterschiedlich, je nachdem, ob es auf einem Entwicklungs- oder einem Produktionsserver läuft:
- 🛠️ Entwicklungsmodus
- Zeigt die Tracy Debug Bar mit nützlichen Informationen (SQL-Queries, Ausführungszeit, verbrauchter Speicher)
- Zeigt bei einem Fehler eine detaillierte Fehlerseite mit Funktionsaufrufen und Variableninhalten
- Aktualisiert den Cache automatisch, wenn Latte-Templates, Konfigurationsdateien usw. geändert werden
- 🚀 Produktionsmodus
- Zeigt keinerlei Debugging-Informationen an, alle Fehler werden ins Log geschrieben
- Zeigt bei einem Fehler den ErrorPresenter oder eine allgemeine Seite „Server Error“
- Der Cache wird niemals automatisch aktualisiert!
- Optimiert auf Geschwindigkeit und Sicherheit
Die Wahl des Modus erfolgt per Autodetection, üblicherweise ist also nichts zu konfigurieren und der Modus nicht manuell umzuschalten:
- Entwicklungsmodus: auf localhost (IP-Adresse
127.0.0.1oder::1), sofern kein Proxy vorhanden ist (also dessen HTTP-Header nicht erkannt wird) - Produktionsmodus: überall sonst
Wollen wir den Entwicklungsmodus auch in anderen Fällen einschalten, etwa für Programmierer, die von einer bestimmten
IP-Adresse zugreifen, verwenden wir setDebugMode():
$this->configurator->setDebugMode('23.75.345.200'); // es lässt sich auch ein Array von IP-Adressen angeben
Wir empfehlen dringend, die IP-Adresse mit einem Cookie zu kombinieren. Legen Sie im Cookie nette-debug ein
geheimes Token ab, z. B. secret1234, und aktivieren Sie so den Entwicklungsmodus für Programmierer, die von einer
bestimmten IP-Adresse zugreifen und zugleich das genannte Token im Cookie haben:
$this->configurator->setDebugMode('secret1234@23.75.345.200');
Den Entwicklungsmodus können wir auch vollständig abschalten, sogar für localhost:
$this->configurator->setDebugMode(false);
Beachten Sie, dass der Wert true den Entwicklungsmodus erzwingt, was auf einem Produktionsserver niemals
passieren darf.
Die Autodetection erledigt intern die statische Methode Configurator::detectDebugMode(), die Sie auch selbst
aufrufen können, etwa um den Entwicklungsmodus außerhalb des Configurators zu erkennen. Sie nimmt optional eine Whitelist von
IP-Adressen oder Rechnernamen entgegen und gibt zurück, ob der aktuelle Request im Entwicklungsmodus laufen soll:
$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');
Debugging-Tool Tracy
Für bequemes Debuggen aktivieren wir das ausgezeichnete Werkzeug Tracy. Im Entwicklungsmodus visualisiert es Fehler, im Produktionsmodus protokolliert es sie in das angegebene Verzeichnis:
$this->configurator->enableTracy($this->rootDir . '/log');
Temporäre Dateien
Nette verwendet Cache für den DI-Container, RobotLoader, Templates usw. Deshalb muss der Pfad zu dem Verzeichnis gesetzt werden, in dem der Cache abgelegt wird:
$this->configurator->setTempDirectory($this->rootDir . '/temp');
Setzen Sie unter Linux oder macOS für die Verzeichnisse log/ und temp/ Schreibrechte.
RobotLoader
Üblicherweise wollen wir Klassen automatisch mit RobotLoader laden. Wir müssen ihn also
starten und Klassen aus dem Verzeichnis laden lassen, in dem Bootstrap.php liegt (also __DIR__), samt
allen Unterverzeichnissen:
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
Ein alternativer Weg ist, Klassen ausschließlich über Composer nach PSR-4 zu laden.
Zeitzone
Über den Configurator lässt sich die Standard-Zeitzone einstellen.
$this->configurator->setTimeZone('Europe/Prague');
Konfiguration des DI-Containers
Teil des Startvorgangs ist die Erzeugung des DI-Containers, also der Objekt-Factory, die das Herz der gesamten Anwendung ist. Es handelt sich tatsächlich um eine von Nette erzeugte und im Cache-Verzeichnis abgelegte PHP-Klasse. Die Factory stellt die Schlüsselobjekte der Anwendung her, und mit Konfigurationsdateien weisen wir sie an, wie sie diese erzeugen und einrichten soll – und beeinflussen damit das Verhalten der gesamten Anwendung.
Konfigurationsdateien werden üblicherweise im NEON-Format geschrieben. In einem eigenen Kapitel lesen Sie, was sich alles konfigurieren lässt.
Im Entwicklungsmodus wird der Container bei jeder Änderung des Codes oder der Konfigurationsdateien automatisch aktualisiert. Im Produktionsmodus wird er nur einmal erzeugt, und Änderungen werden nicht geprüft, um die Leistung zu maximieren.
Während createContainer() den Container baut und seine Instanz zurückgibt, liefert die Methode
loadContainer() nur den Namen der generierten Container-Klasse, die Sie dann selbst instanziieren können. Das ist in
fortgeschrittenen Szenarien nützlich.
Konfigurationsdateien werden mit addConfig() geladen:
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
Wollen wir weitere Konfigurationsdateien hinzufügen, können wir die Funktion addConfig() mehrfach aufrufen.
$configDir = $this->rootDir . '/config';
$this->configurator->addConfig($configDir . '/common.neon');
$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
$this->configurator->addConfig($configDir . '/cli.php');
}
Der Name cli.php ist kein Tippfehler; die Konfiguration lässt sich auch in einer PHP-Datei schreiben, die sie als
Array zurückgibt.
Weitere Konfigurationsdateien können wir auch im
Abschnitt includes hinzufügen.
Erscheinen in den Konfigurationsdateien Einträge mit denselben Schlüsseln, werden sie überschrieben, im Fall von Arrays zusammengeführt. Eine später eingebundene Datei hat
höhere Priorität als die vorherige. Die Datei, in der der Abschnitt includes steht, hat höhere Priorität als die
darin eingebundenen Dateien.
Statische Parameter
Parameter, die in Konfigurationsdateien verwendet werden, lassen sich im Abschnitt parameters definieren und außerdem mit
der Methode addStaticParameters() übergeben (oder überschreiben), deren älterer, inzwischen veralteter Alias
addParameters() lautet. Wichtig ist, dass unterschiedliche Parameterwerte die Erzeugung weiterer DI-Container, also
weiterer Klassen, bewirken.
$this->configurator->addStaticParameters([
'projectId' => 23,
]);
Auf den Parameter projectId lässt sich in der Konfiguration mit der üblichen Schreibweise
%projectId% verweisen.
Dynamische Parameter
Dem Container können wir auch dynamische Parameter hinzufügen, deren unterschiedliche Werte im Gegensatz zu statischen Parametern keine Erzeugung neuer DI-Container bewirken.
$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
So lassen sich zum Beispiel bequem Umgebungsvariablen ergänzen, auf die man dann in der Konfiguration mit der Schreibweise
%env.variable% verweisen kann.
$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
Standardparameter
In den Konfigurationsdateien können Sie diese Parameter verwenden:
%appDir%ist der absolute Pfad zum Verzeichnis mit der DateiBootstrap.php%wwwDir%ist der absolute Pfad zum Verzeichnis mit der Startdateiindex.php%tempDir%ist der absolute Pfad zum Verzeichnis für temporäre Dateien%vendorDir%ist der absolute Pfad zum Verzeichnis, in das Composer die Bibliotheken installiert%rootDir%ist der absolute Pfad zum Wurzelverzeichnis des Projekts%baseUrl%ist die absolute URL zum Wurzelverzeichnis (ein dynamischer Parameter, der zur Laufzeit aufgelöst wird)%debugMode%gibt an, ob die Anwendung im Debug-Modus läuft%consoleMode%gibt an, ob der Request über die Kommandozeile kam
Importierte Services
Jetzt gehen wir tiefer. Obwohl der Zweck des DI-Containers darin besteht, Objekte zu erzeugen, kann gelegentlich der Bedarf
entstehen, ein bereits existierendes Objekt in den Container einzufügen. Wir tun das, indem wir den Service mit dem Flag
imported: true definieren.
services:
myservice:
type: App\Model\MyCustomService
imported: true
Und im Bootstrap fügen wir das Objekt in den Container ein:
$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
Verschiedene Umgebungen
Passen Sie die Klasse Bootstrap ruhig Ihren Bedürfnissen an. Sie können der Methode
bootWebApplication() Parameter hinzufügen, um zwischen Webprojekten zu unterscheiden. Oder wir ergänzen weitere
Methoden, etwa bootTestEnvironment(), die die Umgebung für Unit-Tests initialisiert,
bootConsoleApplication() für von der Kommandozeile aufgerufene Skripte usw.
public function bootTestEnvironment(): Nette\DI\Container
{
Tester\Environment::setup(); // Initialisierung von Nette Tester
$this->setupContainer();
return $this->configurator->createContainer();
}
public function bootConsoleApplication(): Nette\DI\Container
{
$this->configurator->setDebugMode(false);
$this->initializeEnvironment();
$this->setupContainer();
return $this->configurator->createContainer();
}