Bootstrap

Bootstrap to kod startowy, który inicjalizuje środowisko, tworzy kontener dependency injection (DI) i uruchamia aplikację. Powiemy sobie:

  • jak konfigurować za pomocą plików NEON
  • jak rozróżnić tryb produkcyjny i deweloperski
  • jak utworzyć kontener DI

Aplikacje, czy to webowe, czy skrypty uruchamiane z wiersza poleceń, rozpoczynają swoje działanie od pewnej formy inicjalizacji środowiska. W dawnych czasach odpowiadał za to plik o nazwie np. include.inc.php, który był dołączany przez plik początkowy. W nowoczesnych aplikacjach Nette zastąpiła go klasa Bootstrap, którą jako część aplikacji znajdziesz w pliku app/Bootstrap.php. Może wyglądać na przykład tak:

use Nette\Bootstrap\Configurator;

class Bootstrap
{
	private Configurator $configurator;
	private string $rootDir;

	public function __construct()
	{
		$this->rootDir = dirname(__DIR__);
		// Konfigurator jest odpowiedzialny za ustawienie środowiska aplikacji i usług.
		$this->configurator = new Configurator;
		// Ustawia katalog dla plików tymczasowych generowanych przez Nette (np. skompilowane szablony)
		$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 jest sprytne i tryb deweloperski włącza się automatycznie,
		// lub możesz go włączyć dla konkretnego adresu IP, odkomentowując poniższą linię:
		// $this->configurator->setDebugMode('secret@23.75.345.200');

		// Aktywuje Tracy: ostateczny "szwajcarski scyzoryk" do debugowania.
		$this->configurator->enableTracy($this->rootDir . '/log');

		// RobotLoader: automatycznie ładuje wszystkie klasy w wybranym katalogu
		$this->configurator->createRobotLoader()
			->addDirectory(__DIR__)
			->register();
	}

	private function setupContainer(): void
	{
		// Ładuje pliki konfiguracyjne
		$this->configurator->addConfig($this->rootDir . '/config/common.neon');
	}
}

index.php

Plikiem początkowym w przypadku aplikacji webowych jest index.php, który znajduje się w katalogu publicznym www/. Ten plik zleca klasie Bootstrap inicjalizację środowiska i utworzenie kontenera DI. Następnie pobiera z niego usługę Application, która uruchamia aplikację webową:

$bootstrap = new App\Bootstrap;
// Inicjalizacja środowiska + utworzenie kontenera DI
$container = $bootstrap->bootWebApplication();
// Kontener DI tworzy obiekt Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
// Uruchomienie aplikacji Nette i przetworzenie przychodzącego żądania
$application->run();

Jak widać, w ustawieniu środowiska i tworzeniu kontenera dependency injection (DI) pomaga klasa Nette\Bootstrap\Configurator, którą teraz bliżej przedstawimy.

Tryb deweloperski vs produkcyjny

Nette zachowuje się różnie w zależności od tego, czy działa na serwerze deweloperskim czy produkcyjnym:

🛠️ Tryb deweloperski (Development)
Wyświetla pasek debugowania Tracy z użytecznymi informacjami (zapytania SQL, czas wykonania, użyta pamięć)
W przypadku błędu wyświetla szczegółową stronę błędu z wywołaniami funkcji i zawartością zmiennych
Automatycznie odświeża cache przy zmianie szablonów Latte, modyfikacji plików konfiguracyjnych itp.
🚀 Tryb produkcyjny (Production)
Nie wyświetla żadnych informacji debugowania, wszystkie błędy zapisuje do logu
W przypadku błędu wyświetla ErrorPresenter lub ogólną stronę “Server Error”
Cache nigdy nie jest automatycznie odświeżany!
Zoptymalizowany pod kątem szybkości i bezpieczeństwa

Wybór trybu odbywa się przez autodetekcję, więc zazwyczaj nie trzeba niczego konfigurować ani ręcznie przełączać:

  • tryb deweloperski: na localhost (adres IP 127.0.0.1 lub ::1) jeśli nie ma proxy (tj. jego nagłówka HTTP)
  • tryb produkcyjny: wszędzie indziej

Jeśli chcemy włączyć tryb deweloperski również w innych przypadkach, na przykład dla programistów łączących się z konkretnego adresu IP, użyjemy setDebugMode():

$this->configurator->setDebugMode('23.75.345.200'); // można podać również tablicę adresów IP

Zdecydowanie zalecamy łączenie adresu IP z ciasteczkiem (cookie). W ciasteczku nette-debug zapisujemy tajny token, np. secret1234, i w ten sposób aktywujemy tryb deweloperski dla programistów łączących się z konkretnego adresu IP i jednocześnie posiadających w ciasteczku wspomniany token:

$this->configurator->setDebugMode('secret1234@23.75.345.200');

Tryb deweloperski możemy również całkowicie wyłączyć, nawet dla localhost:

$this->configurator->setDebugMode(false);

Uwaga, wartość true włącza tryb deweloperski na stałe, co nigdy nie powinno mieć miejsca na serwerze produkcyjnym.

Narzędzie debugujące Tracy

Dla łatwego debugowania włączymy jeszcze świetne narzędzie Tracy. W trybie deweloperskim wizualizuje błędy, a w trybie produkcyjnym loguje błędy do podanego katalogu:

$this->configurator->enableTracy($this->rootDir . '/log');

Pliki tymczasowe

Nette wykorzystuje cache dla kontenera DI, RobotLoadera, szablonów itp. Dlatego konieczne jest ustawienie ścieżki do katalogu, w którym będzie przechowywany cache:

$this->configurator->setTempDirectory($this->rootDir . '/temp');

Na Linuksie lub macOS ustaw katalogom log/ i temp/ uprawnienia do zapisu.

RobotLoader

Zazwyczaj będziemy chcieli automatycznie ładować klasy za pomocą RobotLoadera, musimy go więc uruchomić i pozwolić mu ładować klasy z katalogu, w którym znajduje się Bootstrap.php (tj. __DIR__), oraz wszystkich podkatalogów:

$this->configurator->createRobotLoader()
	->addDirectory(__DIR__)
	->register();

Alternatywnym podejściem jest ładowanie klas wyłącznie przez Composer przy zachowaniu PSR-4.

Strefa czasowa

Za pomocą konfiguratora można ustawić domyślną strefę czasową.

$this->configurator->setTimeZone('Europe/Prague');

Konfiguracja kontenera DI

Częścią procesu startowego jest utworzenie kontenera DI, czyli fabryki obiektów, która jest sercem całej aplikacji. Jest to właściwie klasa PHP, którą generuje Nette i zapisuje w katalogu z cache. Fabryka tworzy kluczowe obiekty aplikacji, a za pomocą plików konfiguracyjnych instruujemy ją, jak ma je tworzyć i ustawiać, co wpływa na zachowanie całej aplikacji.

Pliki konfiguracyjne zazwyczaj zapisuje się w formacie NEON. W osobnym rozdziale dowiesz się, co można skonfigurować.

W trybie deweloperskim kontener automatycznie aktualizuje się przy każdej zmianie kodu lub plików konfiguracyjnych. W trybie produkcyjnym generowany jest tylko raz, a zmiany nie są sprawdzane w celu maksymalizacji wydajności.

Pliki konfiguracyjne ładujemy za pomocą addConfig():

$this->configurator->addConfig($this->rootDir . '/config/common.neon');

Jeśli chcemy dodać więcej plików konfiguracyjnych, możemy wywołać funkcję addConfig() wielokrotnie.

$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');
}

Nazwa cli.php nie jest pomyłką, konfiguracja może być zapisana również w pliku PHP, który zwraca ją jako tablicę.

Możemy również dodać inne pliki konfiguracyjne w sekcji includes.

Jeśli w plikach konfiguracyjnych pojawią się elementy o tych samych kluczach, zostaną one nadpisane lub w przypadku tablic połączone. Później dołączony plik ma wyższy priorytet niż poprzedni. Plik, w którym znajduje się sekcja includes, ma wyższy priorytet niż pliki w nim zawarte.

Parametry statyczne

Parametry używane w plikach konfiguracyjnych możemy zdefiniować w sekcji parameters, a także przekazywać (lub nadpisywać) metodą addStaticParameters() (ma alias addParameters()). Ważne jest, że różne wartości parametrów spowodują wygenerowanie kolejnych kontenerów DI, czyli kolejnych klas.

$this->configurator->addStaticParameters([
	'projectId' => 23,
]);

Do parametru projectId można odwołać się w konfiguracji za pomocą zwykłego zapisu %projectId%.

Parametry dynamiczne

Do kontenera możemy dodać również parametry dynamiczne, których różne wartości, w przeciwieństwie do parametrów statycznych, nie spowodują generowania nowych kontenerów DI.

$this->configurator->addDynamicParameters([
	'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);

W prosty sposób możemy dodać np. zmienne środowiskowe, do których można się następnie odwołać w konfiguracji za pomocą zapisu %env.variable%.

$this->configurator->addDynamicParameters([
	'env' => getenv(),
]);

Parametry domyślne

W plikach konfiguracyjnych można używać następujących parametrów statycznych:

  • %appDir% to ścieżka absolutna do katalogu z plikiem Bootstrap.php
  • %wwwDir% to ścieżka absolutna do katalogu z plikiem wejściowym index.php
  • %tempDir% to ścieżka absolutna do katalogu dla plików tymczasowych
  • %vendorDir% to ścieżka absolutna do katalogu, w którym Composer instaluje biblioteki
  • %rootDir% to ścieżka absolutna do katalogu głównego projektu
  • %debugMode% wskazuje, czy aplikacja jest w trybie debugowania
  • %consoleMode% wskazuje, czy żądanie przyszło przez wiersz poleceń

Usługi importowane

Teraz zagłębiamy się bardziej. Chociaż celem kontenera DI jest tworzenie obiektów, wyjątkowo może zaistnieć potrzeba wstawienia istniejącego obiektu do kontenera. Robimy to, definiując usługę z flagą imported: true.

services:
	myservice:
		type: App\Model\MyCustomService
		imported: true

A w bootstrapie wstawiamy obiekt do kontenera:

$this->configurator->addServices([
	'myservice' => new App\Model\MyCustomService('foobar'),
]);

Odmienne środowisko

Nie bój się modyfikować klasy Bootstrap według własnych potrzeb. Do metody bootWebApplication() możesz dodać parametry do rozróżniania projektów webowych. Możemy też dodać inne metody, na przykład bootTestEnvironment(), która inicjalizuje środowisko dla testów jednostkowych, bootConsoleApplication() dla skryptów wywoływanych z wiersza poleceń itp.

public function bootTestEnvironment(): Nette\DI\Container
{
	Tester\Environment::setup(); // inicjalizacja Nette Testera
	$this->setupContainer();
	return $this->configurator->createContainer();
}

public function bootConsoleApplication(): Nette\DI\Container
{
	$this->configurator->setDebugMode(false);
	$this->initializeEnvironment();
	$this->setupContainer();
	return $this->configurator->createContainer();
}
wersja: 4.0