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();
}