Bootstrap

Bootstrap е зареждащ код, който инициализира средата, създава dependency injection (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();

Както се вижда, с настройката на средата и създаването на dependency injection (DI) контейнер помага класът Nette\Bootstrap\Configurator, който сега ще разгледаме по-подробно.

Режим за разработка срещу продукционен режим

Nette се държи различно в зависимост от това дали работи на сървър за разработка или на продукционен сървър:

🛠️ Режим за разработка (Development)
Показва Tracy debugbar с полезна информация (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 адрес с бисквитка. В бисквитката nette-debug ще запазим таен токен, напр. secret1234, и по този начин ще активираме режима за разработка за програмисти, достъпващи от конкретен IP адрес и същевременно имащи споменатия токен в бисквитката:

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