Bootstrapping
*************
<div class=perex>
Bootstrapping - это процесс инициализации окружения приложения, создания контейнера внедрения зависимостей (DI) и запуска приложения. Мы обсудим:
- как класс Bootstrap инициализирует окружение
- как приложения настраиваются файлами NEON
- как различать производственный режим и режим разработки
- как создать и настроить DI-контейнер
</div>
Приложения, будь то веб-приложения или скрипты, запускаемые из командной строки, начинают своё выполнение с той или иной инициализации окружения. В былые времена за это отвечал файл с именем вроде `include.inc.php`, подключаемый начальным файлом. В современных приложениях Nette его заменил класс `Bootstrap`, который как часть приложения находится в файле `app/Bootstrap.php`. Он может выглядеть, например, так:
```php
namespace App;
use Nette;
use Nette\Bootstrap\Configurator;
class Bootstrap
{
private Configurator $configurator;
private string $rootDir;
public function __construct()
{
$this->rootDir = dirname(__DIR__);
// Configurator отвечает за настройку окружения приложения и сервисов.
$this->configurator = new Configurator;
// Задаём каталог для временных файлов, порождаемых Nette (например, скомпилированных шаблонов)
$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 умён, и режим разработки включается автоматически,
// либо вы можете включить его для конкретного IP-адреса, раскомментировав следующую строку:
// $this->configurator->setDebugMode('secret@23.75.345.200');
// Включает Tracy - лучший "швейцарский нож" для отладки.
$this->configurator->enableTracy($this->rootDir . '/log');
// RobotLoader: автоматически загружает все классы в выбранном каталоге
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
}
private function setupContainer(): void
{
// Загружаем конфигурационные файлы
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
```
index.php
=========
У веб-приложений начальным файлом служит `index.php`, лежащий в [публичном каталоге |directory-structure#Публичный каталог www/] `www/`. Он поручает классу Bootstrap инициализировать окружение и создать DI-контейнер. Затем он получает из контейнера сервис `Application`, который и запускает веб-приложение:
```php
$bootstrap = new App\Bootstrap;
// Инициализируем окружение и создаём DI-контейнер
$container = $bootstrap->bootWebApplication();
// DI-контейнер создаёт объект Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
// Запускаем приложение Nette и обрабатываем входящий запрос
$application->run();
```
.[note]
Объект `$application` в ходе обработки запроса испускает [события |nette:glossary#События]: `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` и `onError` (при необработанном исключении). Вы можете привязать к ним обработчики, что удобно для ведения лога или мониторинга всего приложения.
Как видите, настроить окружение и создать контейнер внедрения зависимостей (DI) помогает класс [api:Nette\Bootstrap\Configurator]. Сейчас мы познакомим вас с ним подробнее.
Режим разработки и производственный режим
=========================================
Nette ведёт себя по-разному в зависимости от того, работает он на сервере разработки или на производственном:
🛠️ Режим разработки:
- Показывает панель отладки Tracy с полезными сведениями (SQL-запросы, время выполнения, использованная память)
- При ошибке показывает подробную страницу ошибки с вызовами функций и содержимым переменных
- Автоматически обновляет кеш при изменении шаблонов Latte, конфигурационных файлов и прочего
🚀 Производственный режим:
- Не показывает никаких отладочных сведений, все ошибки записываются в лог
- При ошибке показывает ErrorPresenter или общую страницу "Server Error"
- Кеш никогда не обновляется автоматически!
- Оптимизирован ради скорости и безопасности
Режим выбирается автоопределением, поэтому обычно ничего настраивать и переключать вручную не нужно:
- режим разработки: на localhost (IP-адрес `127.0.0.1` или `::1`), если нет прокси (то есть его HTTP-заголовок не обнаружен)
- производственный режим: везде остальном
Если мы хотим включить режим разработки и в других случаях, например для программистов, заходящих с определённого IP-адреса, мы используем `setDebugMode()`:
```php
$this->configurator->setDebugMode('23.75.345.200'); // можно передать и массив IP-адресов
```
Мы настоятельно рекомендуем сочетать IP-адрес с cookie. Сохраните в cookie `nette-debug` секретный токен, например `secret1234`, и тем самым включите режим разработки для программистов, заходящих с определённого IP-адреса и имеющих в cookie этот токен:
```php
$this->configurator->setDebugMode('secret1234@23.75.345.200');
```
Мы можем и полностью отключить режим разработки, даже на localhost:
```php
$this->configurator->setDebugMode(false);
```
Учтите, что значение `true` принудительно включает режим разработки, чего на производственном сервере быть **никогда** не должно.
Автоопределением внутренне занимается статический метод `Configurator::detectDebugMode()`, который вы можете вызвать и сами, например чтобы определить режим разработки вне конфигуратора. Он принимает необязательный белый список IP-адресов или имён компьютеров и возвращает, должен ли текущий запрос выполняться в режиме разработки:
```php
$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');
```
Инструмент отладки Tracy
========================
Ради удобной отладки мы включим прекрасный инструмент [Tracy |tracy:]. В режиме разработки он наглядно показывает ошибки, а в производственном записывает их в указанный каталог:
```php
$this->configurator->enableTracy($this->rootDir . '/log');
```
Временные файлы
===============
Nette использует кеш для DI-контейнера, RobotLoader, шаблонов и прочего. Поэтому нужно задать путь к каталогу, где будет храниться кеш:
```php
$this->configurator->setTempDirectory($this->rootDir . '/temp');
```
В Linux или macOS задайте каталогам `log/` и `temp/` [права на запись |nette:troubleshooting#Задание прав на каталоги].
RobotLoader
===========
Обычно мы хотим автоматически загружать классы с помощью [RobotLoader |robot-loader:], поэтому нам нужно его запустить и дать ему загружать классы из каталога, где лежит `Bootstrap.php` (то есть `__DIR__`), и из всех его подкаталогов:
```php
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
```
Альтернативный подход - загружать классы исключительно через [Composer |best-practices:composer] по PSR-4.
Часовой пояс
============
Часовой пояс по умолчанию можно задать через конфигуратор.
```php
$this->configurator->setTimeZone('Europe/Prague');
```
Конфигурация DI-контейнера
==========================
Частью процесса запуска является создание DI-контейнера, то есть фабрики объектов, которая служит сердцем всего приложения. На деле это PHP-класс, порождённый Nette и сохранённый в каталоге кеша. Фабрика создаёт ключевые объекты приложения, а конфигурационными файлами мы указываем ей, как их создавать и настраивать, и тем самым влияем на поведение всего приложения.
Конфигурационные файлы обычно пишутся в [формате NEON |neon:format]. В отдельной главе вы можете прочитать, [что можно настраивать |nette:configuring].
.[tip]
В режиме разработки контейнер автоматически обновляется при изменении кода или конфигурационных файлов. В производственном режиме он порождается только один раз, а изменения не проверяются ради максимальной производительности.
Метод `createContainer()` собирает контейнер и возвращает его экземпляр, а метод `loadContainer()` возвращает только имя порождённого класса контейнера, который вы затем можете создать сами. Это полезно в продвинутых сценариях.
Конфигурационные файлы загружаются методом `addConfig()`:
```php
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
```
Если мы хотим добавить больше конфигурационных файлов, мы можем вызвать функцию `addConfig()` несколько раз.
```php
$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');
}
```
Имя `cli.php` - не опечатка: конфигурацию можно записать и в PHP-файле, который возвращает её массивом.
Другие конфигурационные файлы мы можем добавить и в [секции `includes` |dependency-injection:configuration#Подключение файлов].
Если в конфигурационных файлах встречаются элементы с одинаковыми ключами, они будут перезаписаны или, в случае [массивов, объединены |dependency-injection:configuration#Слияние]. Файл, подключённый позже, имеет более высокий приоритет, чем предыдущий. Файл, в котором указана секция `includes`, имеет более высокий приоритет, чем подключённые в нём файлы.
Статические параметры
---------------------
Параметры, используемые в конфигурационных файлах, можно определить [в секции `parameters` |dependency-injection:configuration#Параметры], а также передать (или переопределить) методом `addStaticParameters()` (его более старый, ныне устаревший псевдоним - `addParameters()`). Важно, что разные значения параметров вызовут порождение дополнительных DI-контейнеров, то есть дополнительных классов.
```php
$this->configurator->addStaticParameters([
'projectId' => 23,
]);
```
На параметр `projectId` можно сослаться в конфигурации обычной записью `%projectId%`.
Динамические параметры
----------------------
Мы можем добавить в контейнер и динамические параметры, разные значения которых, в отличие от статических, не вызовут порождения новых DI-контейнеров.
```php
$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
```
Так мы легко добавим, например, переменные окружения, на которые затем можно сослаться в конфигурации записью `%env.variable%`.
```php
$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
```
Параметры по умолчанию
----------------------
В конфигурационных файлах вы можете использовать эти параметры:
- `%appDir%` - абсолютный путь к каталогу с файлом `Bootstrap.php`
- `%wwwDir%` - абсолютный путь к каталогу с начальным файлом `index.php`
- `%tempDir%` - абсолютный путь к каталогу временных файлов
- `%vendorDir%` - абсолютный путь к каталогу, куда Composer устанавливает библиотеки
- `%rootDir%` - абсолютный путь к корневому каталогу проекта
- `%baseUrl%` - абсолютный URL корневого каталога (динамический параметр, вычисляемый во время выполнения)
- `%debugMode%` - находится ли приложение в режиме отладки
- `%consoleMode%` - пришёл ли запрос из командной строки
Импортированные сервисы
-----------------------
Теперь копнём глубже. Хотя назначение DI-контейнера - создавать объекты, изредка может понадобиться вставить в контейнер уже существующий объект. Мы делаем это, определив сервис с флагом `imported: true`.
```neon
services:
myservice:
type: App\Model\MyCustomService
imported: true
```
А в bootstrap вставляем объект в контейнер:
```php
$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
```
Разные окружения
================
Смело меняйте класс `Bootstrap` под свои нужды. Вы можете добавить в метод `bootWebApplication()` параметры, чтобы различать веб-проекты. Или можно добавить другие методы, например `bootTestEnvironment()`, инициализирующий окружение для модульных тестов, `bootConsoleApplication()` для скриптов, вызываемых из командной строки, и так далее.
```php
public function bootTestEnvironment(): Nette\DI\Container
{
Tester\Environment::setup(); // инициализация 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();
}
```
Bootstrapping
Bootstrapping – это процесс инициализации окружения приложения, создания
контейнера внедрения зависимостей (DI) и запуска приложения. Мы
обсудим:
- как класс Bootstrap инициализирует окружение
- как приложения настраиваются файлами NEON
- как различать производственный режим и режим разработки
- как создать и настроить DI-контейнер
Приложения, будь то веб-приложения или скрипты, запускаемые из
командной строки, начинают своё выполнение с той или иной
инициализации окружения. В былые времена за это отвечал файл с именем
вроде include.inc.php, подключаемый начальным файлом. В современных
приложениях Nette его заменил класс Bootstrap, который как часть
приложения находится в файле app/Bootstrap.php. Он может выглядеть,
например, так:
namespace App;
use Nette;
use Nette\Bootstrap\Configurator;
class Bootstrap
{
private Configurator $configurator;
private string $rootDir;
public function __construct()
{
$this->rootDir = dirname(__DIR__);
// Configurator отвечает за настройку окружения приложения и сервисов.
$this->configurator = new Configurator;
// Задаём каталог для временных файлов, порождаемых Nette (например, скомпилированных шаблонов)
$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 умён, и режим разработки включается автоматически,
// либо вы можете включить его для конкретного IP-адреса, раскомментировав следующую строку:
// $this->configurator->setDebugMode('secret@23.75.345.200');
// Включает Tracy - лучший "швейцарский нож" для отладки.
$this->configurator->enableTracy($this->rootDir . '/log');
// RobotLoader: автоматически загружает все классы в выбранном каталоге
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
}
private function setupContainer(): void
{
// Загружаем конфигурационные файлы
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
index.php
У веб-приложений начальным файлом служит index.php, лежащий в публичном каталоге
www/. Он поручает классу Bootstrap инициализировать окружение и
создать DI-контейнер. Затем он получает из контейнера сервис
Application, который и запускает веб-приложение:
$bootstrap = new App\Bootstrap;
// Инициализируем окружение и создаём DI-контейнер
$container = $bootstrap->bootWebApplication();
// DI-контейнер создаёт объект Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
// Запускаем приложение Nette и обрабатываем входящий запрос
$application->run();
Объект $application в ходе обработки запроса испускает события: onStartup, onRequest,
onPresenter, onResponse, onShutdown и onError (при
необработанном исключении). Вы можете привязать к ним обработчики, что
удобно для ведения лога или мониторинга всего приложения.
Как видите, настроить окружение и создать контейнер внедрения
зависимостей (DI) помогает класс Nette\Bootstrap\Configurator. Сейчас
мы познакомим вас с ним подробнее.
Режим разработки и производственный
режим
Nette ведёт себя по-разному в зависимости от того, работает он на
сервере разработки или на производственном:
- 🛠️ Режим разработки
- Показывает панель отладки Tracy с полезными сведениями (SQL-запросы,
время выполнения, использованная память)
- При ошибке показывает подробную страницу ошибки с вызовами функций
и содержимым переменных
- Автоматически обновляет кеш при изменении шаблонов Latte,
конфигурационных файлов и прочего
- 🚀 Производственный режим
- Не показывает никаких отладочных сведений, все ошибки записываются
в лог
- При ошибке показывает ErrorPresenter или общую страницу “Server Error”
- Кеш никогда не обновляется автоматически!
- Оптимизирован ради скорости и безопасности
Режим выбирается автоопределением, поэтому обычно ничего
настраивать и переключать вручную не нужно:
- режим разработки: на localhost (IP-адрес
127.0.0.1 или ::1), если
нет прокси (то есть его HTTP-заголовок не обнаружен)
- производственный режим: везде остальном
Если мы хотим включить режим разработки и в других случаях, например
для программистов, заходящих с определённого IP-адреса, мы используем
setDebugMode():
$this->configurator->setDebugMode('23.75.345.200'); // можно передать и массив IP-адресов
Мы настоятельно рекомендуем сочетать IP-адрес с cookie. Сохраните в cookie
nette-debug секретный токен, например secret1234, и тем самым
включите режим разработки для программистов, заходящих с
определённого IP-адреса и имеющих в cookie этот токен:
$this->configurator->setDebugMode('secret1234@23.75.345.200');
Мы можем и полностью отключить режим разработки, даже на localhost:
$this->configurator->setDebugMode(false);
Учтите, что значение true принудительно включает режим
разработки, чего на производственном сервере быть никогда не
должно.
Автоопределением внутренне занимается статический метод
Configurator::detectDebugMode(), который вы можете вызвать и сами, например
чтобы определить режим разработки вне конфигуратора. Он принимает
необязательный белый список IP-адресов или имён компьютеров и
возвращает, должен ли текущий запрос выполняться в режиме
разработки:
$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');
Инструмент отладки Tracy
Ради удобной отладки мы включим прекрасный инструмент Tracy. В режиме разработки он наглядно показывает
ошибки, а в производственном записывает их в указанный каталог:
$this->configurator->enableTracy($this->rootDir . '/log');
Временные файлы
Nette использует кеш для DI-контейнера, RobotLoader, шаблонов и прочего.
Поэтому нужно задать путь к каталогу, где будет храниться кеш:
$this->configurator->setTempDirectory($this->rootDir . '/temp');
В Linux или macOS задайте каталогам log/ и temp/ права на запись.
RobotLoader
Обычно мы хотим автоматически загружать классы с помощью RobotLoader, поэтому нам нужно его запустить и
дать ему загружать классы из каталога, где лежит Bootstrap.php (то есть
__DIR__), и из всех его подкаталогов:
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
Альтернативный подход – загружать классы исключительно через Composer по PSR-4.
Часовой пояс
Часовой пояс по умолчанию можно задать через конфигуратор.
$this->configurator->setTimeZone('Europe/Prague');
Конфигурация DI-контейнера
Частью процесса запуска является создание DI-контейнера, то есть
фабрики объектов, которая служит сердцем всего приложения. На деле это
PHP-класс, порождённый Nette и сохранённый в каталоге кеша. Фабрика создаёт
ключевые объекты приложения, а конфигурационными файлами мы указываем
ей, как их создавать и настраивать, и тем самым влияем на поведение
всего приложения.
Конфигурационные файлы обычно пишутся в формате NEON. В отдельной главе вы можете
прочитать, что можно настраивать.
В режиме разработки контейнер автоматически обновляется при
изменении кода или конфигурационных файлов. В производственном режиме
он порождается только один раз, а изменения не проверяются ради
максимальной производительности.
Метод createContainer() собирает контейнер и возвращает его
экземпляр, а метод loadContainer() возвращает только имя порождённого
класса контейнера, который вы затем можете создать сами. Это полезно в
продвинутых сценариях.
Конфигурационные файлы загружаются методом addConfig():
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
Если мы хотим добавить больше конфигурационных файлов, мы можем
вызвать функцию addConfig() несколько раз.
$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');
}
Имя cli.php – не опечатка: конфигурацию можно записать и в
PHP-файле, который возвращает её массивом.
Другие конфигурационные файлы мы можем добавить и в секции
includes.
Если в конфигурационных файлах встречаются элементы с одинаковыми
ключами, они будут перезаписаны или, в случае массивов, объединены.
Файл, подключённый позже, имеет более высокий приоритет, чем
предыдущий. Файл, в котором указана секция includes, имеет более
высокий приоритет, чем подключённые в нём файлы.
Статические параметры
Параметры, используемые в конфигурационных файлах, можно определить
в секции parameters,
а также передать (или переопределить) методом addStaticParameters() (его
более старый, ныне устаревший псевдоним – addParameters()). Важно, что
разные значения параметров вызовут порождение дополнительных
DI-контейнеров, то есть дополнительных классов.
$this->configurator->addStaticParameters([
'projectId' => 23,
]);
На параметр projectId можно сослаться в конфигурации обычной
записью %projectId%.
Динамические параметры
Мы можем добавить в контейнер и динамические параметры, разные
значения которых, в отличие от статических, не вызовут порождения
новых DI-контейнеров.
$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
Так мы легко добавим, например, переменные окружения, на которые
затем можно сослаться в конфигурации записью %env.variable%.
$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
Параметры по умолчанию
В конфигурационных файлах вы можете использовать эти параметры:
%appDir% – абсолютный путь к каталогу с файлом Bootstrap.php
%wwwDir% – абсолютный путь к каталогу с начальным
файлом index.php
%tempDir% – абсолютный путь к каталогу временных файлов
%vendorDir% – абсолютный путь к каталогу, куда Composer устанавливает
библиотеки
%rootDir% – абсолютный путь к корневому каталогу проекта
%baseUrl% – абсолютный URL корневого каталога (динамический
параметр, вычисляемый во время выполнения)
%debugMode% – находится ли приложение в режиме отладки
%consoleMode% – пришёл ли запрос из командной строки
Импортированные сервисы
Теперь копнём глубже. Хотя назначение DI-контейнера – создавать
объекты, изредка может понадобиться вставить в контейнер уже
существующий объект. Мы делаем это, определив сервис с флагом
imported: true.
services:
myservice:
type: App\Model\MyCustomService
imported: true
А в bootstrap вставляем объект в контейнер:
$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
Разные окружения
Смело меняйте класс Bootstrap под свои нужды. Вы можете добавить в
метод bootWebApplication() параметры, чтобы различать веб-проекты. Или
можно добавить другие методы, например bootTestEnvironment(),
инициализирующий окружение для модульных тестов, bootConsoleApplication()
для скриптов, вызываемых из командной строки, и так далее.
public function bootTestEnvironment(): Nette\DI\Container
{
Tester\Environment::setup(); // инициализация 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();
}