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