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 zachowuje się różnie w zależności od tego, czy działa na serwerze deweloperskim czy produkcyjnym:
- 🛠️ Tryb deweloperski
- Wyświetla pasek debugowania Tracy z przydatnymi informacjami (np. zapytania SQL, czas wykonania, użycie pamięci).
- Wyświetla szczegółową stronę błędu ze śladami wywołań funkcji i zawartością zmiennych w przypadku wystąpienia błędu.
- Automatycznie odświeża pamięć podręczną, gdy modyfikowane są szablony Latte, pliki konfiguracyjne itp.
- Tryb produkcyjny
- Nie wyświetla żadnych informacji debugowania; wszystkie błędy są rejestrowane.
- Wyświetla stronę
ErrorPresenter
lub ogólną stronę “Błąd serwera”, gdy wystąpi błąd. - Pamięć podręczna nigdy nie jest automatycznie odświeżana!
- Zoptymalizowany pod kątem szybkości i bezpieczeństwa.
Tryb jest określany automatycznie, więc w większości przypadków nie ma potrzeby konfigurowania lub przełączania go ręcznie:
- Tryb deweloperski: Aktywny na localhost (adres IP
127.0.0.1
lub::1
), chyba że używany jest serwer proxy (tj. na podstawie jego nagłówków HTTP). - Tryb produkcyjny: Aktywny wszędzie indziej.
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 plikBootstrap.php
%wwwDir%
jest bezwzględną ścieżką do katalogu zawierającego plik wejściowyindex.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();
}