Bootstrap
Bootstrap — это загрузочный код, который инициализирует среду, создает контейнер внедрения зависимостей (DI) и запускает приложение. Мы расскажем:
- как он настраивается с помощью файлов NEON
- как различать режим production и режим разработки
- как создать DI-контейнер
Приложения, будь то веб-приложения или скрипты, запускаемые из
командной строки, начинают свою работу с некоторой формы
инициализации среды. В давние времена за это отвечал файл с именем,
например, include.inc.php
, который включался в первоначальный файл. В
современных приложениях Nette его заменил класс Bootstrap
, который как
часть приложения находится в файле app/Bootstrap.php
. Он может
выглядеть, например, так:
use Nette\Bootstrap\Configurator;
class Bootstrap
{
private Configurator $configurator;
private string $rootDir;
public function __construct()
{
$this->rootDir = dirname(__DIR__);
// Конфигуратор отвечает за настройку среды приложения и сервисов.
$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();
Как видно, с настройкой среды и созданием контейнера внедрения зависимостей (DI) помогает класс Nette\Bootstrap\Configurator, который мы сейчас рассмотрим подробнее.
Режим разработки vs режим production
Nette ведет себя по-разному в зависимости от того, работает ли он на сервере разработки или production:
- 🛠️ Режим разработки (Development)
- Отображает панель отладки Tracy с полезной информацией (SQL-запросы, время выполнения, использованная память)
- При ошибке отображает подробную страницу ошибки с вызовами функций и содержимым переменных
- Автоматически обновляет кеш при изменении шаблонов Latte, редактировании конфигурационных файлов и т. д.
- 🚀 Режим production (Production)
- Не отображает никакой отладочной информации, все ошибки записывает в лог
- При ошибке отображает ErrorPresenter или общую страницу “Server Error”
- Кеш никогда автоматически не обновляется!
- Оптимизирован для скорости и безопасности
Выбор режима осуществляется автоопределением, поэтому обычно не требуется ничего настраивать или вручную переключать:
- режим разработки: на localhost (IP-адрес
127.0.0.1
или::1
), если нет прокси (т. е. его HTTP-заголовка) - режим production: везде в остальных случаях
Если мы хотим включить режим разработки и в других случаях, например,
для программистов, обращающихся с конкретного 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
включает режим разработки
принудительно, что никогда не должно происходить на production-сервере.
Инструмент отладки Tracy
Для легкой отладки мы также включим отличный инструмент Tracy. В режиме разработки он визуализирует ошибки, а в режиме production записывает ошибки в указанный каталог:
$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. В отдельной главе вы узнаете, что все можно настроить.
В режиме разработки контейнер автоматически обновляется при каждом изменении кода или конфигурационных файлов. В режиме production он генерируется только один раз, и изменения не проверяются для максимальной производительности.
Конфигурационные файлы загружаем с помощью 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%
— абсолютный путь к корневому каталогу проекта%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();
}