Bootstrap

Bootstrap — это загрузочный код, который инициализирует среду, создает контейнер внедрения зависимостей (DI) и запускает приложение. Мы обсудим:

  • как настроить приложение с помощью файлов NEON
  • как работать с режимами производства и разработки
  • как создать контейнер 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();

Как вы видите, класс Nette\Bootstrap\Configurator, который мы сейчас представим более подробно, помогает в настройке окружения и создании контейнера внедрения зависимостей (DI).

Режим разработки и режим производства

Nette ведет себя по-разному в зависимости от того, работает ли он на сервере разработки или на рабочем сервере:

🛠️ Режим разработки
Отображает панель отладки Tracy с полезной информацией (например, SQL-запросы, время выполнения, использование памяти).
При возникновении ошибки показывает подробную страницу ошибки с трассировкой вызовов функций и содержимым переменных.
Автоматически обновляет кэш при изменении шаблонов Latte, файлов конфигурации и т. д.
🚀 Производственный режим
Не отображает никакой отладочной информации; все ошибки записываются в журнал.
При возникновении ошибки показывает ErrorPresenter или общую страницу “Ошибка сервера”.
Кэш никогда не обновляется автоматически!
Оптимизирован для скорости и безопасности.

Режим определяется автоматически, поэтому в большинстве случаев нет необходимости настраивать или переключать его вручную:

  • Режим разработки: Активен на 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 жестко включает режим разработчика, чего никогда не должно происходить на рабочем сервере.

Отладочный инструмент 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();

Альтернативный способ — использовать только автозагрузку PSR-4 Composer.

Часовой пояс

Configurator позволяет указать часовой пояс для вашего приложения.

$this->configurator->setTimeZone('Europe/Prague');

Конфигурация DI-контейнера

Частью процесса загрузки является создание DI-контейнера, то есть фабрики объектов, которая является сердцем всего приложения. На самом деле это PHP-класс, созданный Nette и хранящийся в каталоге кэша. Фабрика создает ключевые объекты приложения, а конфигурационные файлы инструктируют её, как их создавать и настраивать, и таким образом мы влияем на поведение всего приложения.

Файлы конфигурации обычно записываются в формате NEON. Вы можете прочитать что можно настроить здесь.

В режиме разработки контейнер автоматически обновляется каждый раз, когда вы изменяете код или конфигурационные файлы. В производственном режиме он генерируется только один раз, а изменения файлов не проверяются для достижения максимальной производительности.

Файлы конфигурации загружаются с помощью 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();
}