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__);
		// 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();

Як бачимо, з налаштуванням середовища та створенням DI-контейнера (впровадження залежностей) допомагає клас Nette\Bootstrap\Configurator, який ми зараз детальніше розглянемо.

Режим розробки проти робочого режиму

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

🛠️ Режим розробки (Development)
Показує панель налагодження Tracy з корисною інформацією (SQL-запити, час виконання, використана пам'ять)
У разі помилки показує детальну сторінку помилки з викликами функцій та вмістом змінних
Автоматично оновлює кеш при зміні шаблонів Latte, редагуванні конфігураційних файлів тощо.
🚀 Робочий режим (Production)
Не показує жодної налагоджувальної інформації, всі помилки записує в лог
У разі помилки показує 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 вмикає режим розробки примусово, що ніколи не повинно статися на робочому сервері.

Інструмент налагодження 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/Kyiv');

Конфігурація 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();
}