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 plikiemBootstrap.php
%wwwDir%
to ścieżka absolutna do katalogu z plikiem wejściowymindex.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();
}