Bootstrap

Bootstrap to kod startowy, który inicjalizuje środowisko, tworzy kontener wtrysku zależności (DI) i uruchamia aplikację. Powiedzmy:

  • jak skonfigurować przy użyciu plików NEON
  • jak odróżnić tryb produkcyjny od deweloperskiego
  • jak stworzyć kontener DI

Aplikacje, niezależnie od tego, czy są to aplikacje internetowe, czy skrypty wiersza poleceń, rozpoczynają swój czas działania od pewnej formy inicjalizacji środowiska. W dawnych czasach robił to plik o nazwie na przykład include.inc.php, który inkubował początkowy plik. W nowoczesnych aplikacjach Nette zostało to zastąpione klasą Bootstrap, która jako część aplikacji znajduje się w pliku app/Bootstrap.php Może to wyglądać tak:

use Nette\Bootstrap\Configurator;

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

	public function __construct()
	{
		$this->rootDir = dirname(__DIR__);
		// Konfigurator jest odpowiedzialny za konfigurację środowiska aplikacji i usług.
		$this->configurator = new Configurator;
		// Ustawienie katalogu dla plików tymczasowych generowanych przez Nette (np. skompilowanych szablonów).
		$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 inteligentny i tryb deweloperski włącza się automatycznie,
		// lub można go włączyć dla określonego adresu IP, odkomentowując następującą linię:
		// $this->configurator->setDebugMode('secret@23.75.345.200');

		// Włącza Tracy: najlepsze narzędzie do debugowania.
		$this->configurator->enableTracy($this->rootDir . '/log');

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

	private function setupContainer(): void
	{
		// Ładowanie plików konfiguracyjnych
		$this->configurator->addConfig($this->rootDir . '/config/common.neon');
	}
}

index.php

Początkowym plikiem dla aplikacji internetowych jest index.php, znajdujący się w publicznym katalogu www/. Używa on klasy Bootstrap do zainicjowania środowiska i utworzenia kontenera DI. Następnie uzyskuje usługę Application z kontenera, który uruchamia aplikację internetową:

$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);
// Uruchom aplikację Nette i obsłuż przychodzące żądanie
$application->run();

Jak widać, klasa Nette\Bootstrap\Configurator pomaga w konfiguracji środowiska i tworzeniu kontenera dependency injection (DI), któremu teraz przyjrzymy się bliżej.

Tryb deweloperski a produkcyjny

Nette rozróżnia dwa podstawowe tryby, w których realizowane jest żądanie: deweloperski i produkcyjny. Tryb deweloperski ma na celu maksymalną wygodę dla programisty, wyświetlana jest Tracy, pamięć podręczna jest automatycznie aktualizowana, gdy zmieniają się szablony lub konfiguracje kontenerów DI itp. Produkcja skupia się na wydajności i rześkim wdrożeniu, Tracy tylko loguje błędy, a zmiany w szablonach i innych plikach nie są testowane.

Wybór trybu odbywa się poprzez autodetekcję, więc zazwyczaj nie ma potrzeby konfigurowania czy ręcznego przełączania czegokolwiek. Trybem deweloperskim jest sytuacja, kiedy aplikacja jest uruchomiona na localhoście (czyli na adresie IP 127.0.0.1 lub ::1) i nie ma proxy (czyli jego nagłówka HTTP). W przeciwnym razie działa w trybie produkcyjnym.

Jeśli chcemy włączyć tryb deweloperski w innych przypadkach, takich jak programiści uzyskujący dostęp z określonego adresu IP, używamy setDebugMode():

$this->configurator->setDebugMode('23.75.345.200'); // można również określić pole adresu IP

Zdecydowanie zalecamy połączenie adresu IP z plikiem cookie. W pliku cookie nette-debug przechowujemy tajny token, np. secret1234, i w ten sposób umożliwiamy tryb deweloperski dla programistów uzyskujących dostęp z określonego adresu IP i posiadających token w pliku cookie:

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

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

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

Uwaga, wartość true domyślnie włącza tryb deweloperski, co nigdy nie może mieć miejsca na serwerze produkcyjnym.

Narzędzie do debugowania Tracy

Aby ułatwić debugowanie, włączmy wspaniałe narzędzie Tracy. Wizualizuje błędy w trybie deweloperskim i loguje błędy w trybie produkcyjnym do podanego katalogu:

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

Pliki tymczasowe

Nette używa buforowania dla kontenera DI, RobotLoader, szablonów itp. Dlatego musisz ustawić ścieżkę do katalogu, w którym będzie przechowywany cache:

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

W systemach Linux lub macOS ustaw katalogi log/ i temp/ na uprawnienia do zapisu.

RobotLoader

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

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

Alternatywnym podejściem jest pozwolić mu załadować klasy tylko poprzez Composer, jednocześnie podążając za PSR-4.

Strefa czasowa

Domyślną strefę czasową można ustawić za pośrednictwem konfiguratora.

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

Konfiguracja kontenera DI

Częścią procesu uruchamiania jest stworzenie kontenera DI, czyli fabryki obiektów, która jest sercem aplikacji. Jest to właściwie klasa PHP, która jest generowana przez Nette i przechowywana w katalogu cache. Fabryka produkuje kluczowe obiekty aplikacji, a my za pomocą plików konfiguracyjnych instruujemy ją, jak ma je tworzyć i ustawiać, wpływając tym samym na zachowanie całej aplikacji.

Pliki konfiguracyjne są zazwyczaj zapisane w formacie NEON. Zobacz osobny rozdział, aby dowiedzieć się, co można skonfigurować.

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

Pliki konfiguracyjne są ładowane 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 literówką, konfiguracja może być również zapisana 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 takich samych kluczach, zostaną one nadpisane, lub scalone w przypadku pól. Plik dodany później ma wyższy priorytet niż poprzedni. Plik, w którym wymieniona jest sekcja includes ma wyższy priorytet niż pliki w niej zawarte.

Parametry statyczne

Parametry wykorzystywane w plikach konfiguracyjnych można zdefiniować w sekcji parameters, a także przekazać (lub nadpisać) za pomocą metody addStaticParameters() (posiada ona alias addParameters()). Co ważne, różne wartości parametrów spowodują wygenerowanie dodatkowych kontenerów DI, czyli dodatkowych klas.

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

W plikach konfiguracyjnych możemy zapisać zwykłą notację %projectId% aby uzyskać dostęp do parametru o nazwie projectId.

Parametry dynamiczne

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

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

Możemy po prostu dodać np. zmienne środowiskowe, do których następnie możemy się odwołać w konfiguracji pisząc %env.variable%.

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

Parametry domyślne

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

  • %appDir% jest bezwzględną ścieżką do katalogu zawierającego plik Bootstrap.php
  • %wwwDir% jest bezwzględną ścieżką do katalogu zawierającego plik wejściowy index.php
  • %tempDir% jest bezwzględną ścieżką do katalogu plików tymczasowych
  • %vendorDir% to bezwzględna ścieżka do katalogu, w którym Composer instaluje biblioteki
  • %rootDir% to bezwzględna ścieżka do katalogu głównego projektu
  • %debugMode% wskazuje, czy aplikacja jest w trybie debugowania
  • %consoleMode% wskazuje, czy żądanie przyszło z linii poleceń

Usługi importowane

Teraz wchodzimy głębiej. Chociaż punktem kontenera DI jest wytwarzanie obiektów, w rzadkich przypadkach może zaistnieć potrzeba wstawienia do kontenera istniejącego obiektu. Robimy to poprzez zdefiniowanie usługi 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'),
]);

Różne środowiska

Nie wahaj się dostosować klasy Bootstrap do swoich potrzeb. Możesz dodać parametry do metody bootWebApplication(), aby rozróżnić projekty internetowe. Alternatywnie można dodać inne metody, takie jak bootTestEnvironment() do inicjalizacji środowiska dla testów jednostkowych, bootConsoleApplication() dla skryptów wywoływanych z wiersza poleceń itp.

public function bootTestEnvironment(): Nette\DI\Container
{
	Tester\Environment::setup(); // Inicjalizacja testera sieci
	$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