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