Nette Documentation Preview

syntax
Bootstrapping
*************

<div class=perex>

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

- как класс Bootstrap инициализирует окружение
- как приложения настраиваются файлами NEON
- как различать производственный режим и режим разработки
- как создать и настроить DI-контейнер

</div>


Приложения, будь то веб-приложения или скрипты, запускаемые из командной строки, начинают своё выполнение с той или иной инициализации окружения. В былые времена за это отвечал файл с именем вроде `include.inc.php`, подключаемый начальным файлом. В современных приложениях Nette его заменил класс `Bootstrap`, который как часть приложения находится в файле `app/Bootstrap.php`. Он может выглядеть, например, так:

```php
namespace App;

use Nette;
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`, лежащий в [публичном каталоге |directory-structure#Публичный каталог www/] `www/`. Он поручает классу Bootstrap инициализировать окружение и создать DI-контейнер. Затем он получает из контейнера сервис `Application`, который и запускает веб-приложение:

```php
$bootstrap = new App\Bootstrap;
// Инициализируем окружение и создаём DI-контейнер
$container = $bootstrap->bootWebApplication();
// DI-контейнер создаёт объект Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
// Запускаем приложение Nette и обрабатываем входящий запрос
$application->run();
```

.[note]
Объект `$application` в ходе обработки запроса испускает [события |nette:glossary#События]: `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` и `onError` (при необработанном исключении). Вы можете привязать к ним обработчики, что удобно для ведения лога или мониторинга всего приложения.

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


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

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

🛠️  Режим разработки:
	- Показывает панель отладки Tracy с полезными сведениями (SQL-запросы, время выполнения, использованная память)
	- При ошибке показывает подробную страницу ошибки с вызовами функций и содержимым переменных
	- Автоматически обновляет кеш при изменении шаблонов Latte, конфигурационных файлов и прочего


🚀  Производственный режим:
	- Не показывает никаких отладочных сведений, все ошибки записываются в лог
	- При ошибке показывает ErrorPresenter или общую страницу "Server Error"
	- Кеш никогда не обновляется автоматически!
	- Оптимизирован ради скорости и безопасности


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

- режим разработки: на localhost (IP-адрес `127.0.0.1` или `::1`), если нет прокси (то есть его HTTP-заголовок не обнаружен)
- производственный режим: везде остальном

Если мы хотим включить режим разработки и в других случаях, например для программистов, заходящих с определённого IP-адреса, мы используем `setDebugMode()`:

```php
$this->configurator->setDebugMode('23.75.345.200'); // можно передать и массив IP-адресов
```

Мы настоятельно рекомендуем сочетать IP-адрес с cookie. Сохраните в cookie `nette-debug` секретный токен, например `secret1234`, и тем самым включите режим разработки для программистов, заходящих с определённого IP-адреса и имеющих в cookie этот токен:

```php
$this->configurator->setDebugMode('secret1234@23.75.345.200');
```

Мы можем и полностью отключить режим разработки, даже на localhost:

```php
$this->configurator->setDebugMode(false);
```

Учтите, что значение `true` принудительно включает режим разработки, чего на производственном сервере быть **никогда** не должно.

Автоопределением внутренне занимается статический метод `Configurator::detectDebugMode()`, который вы можете вызвать и сами, например чтобы определить режим разработки вне конфигуратора. Он принимает необязательный белый список IP-адресов или имён компьютеров и возвращает, должен ли текущий запрос выполняться в режиме разработки:

```php
$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');
```


Инструмент отладки Tracy
========================

Ради удобной отладки мы включим прекрасный инструмент [Tracy |tracy:]. В режиме разработки он наглядно показывает ошибки, а в производственном записывает их в указанный каталог:

```php
$this->configurator->enableTracy($this->rootDir . '/log');
```


Временные файлы
===============

Nette использует кеш для DI-контейнера, RobotLoader, шаблонов и прочего. Поэтому нужно задать путь к каталогу, где будет храниться кеш:

```php
$this->configurator->setTempDirectory($this->rootDir . '/temp');
```

В Linux или macOS задайте каталогам `log/` и `temp/` [права на запись |nette:troubleshooting#Задание прав на каталоги].


RobotLoader
===========

Обычно мы хотим автоматически загружать классы с помощью [RobotLoader |robot-loader:], поэтому нам нужно его запустить и дать ему загружать классы из каталога, где лежит `Bootstrap.php` (то есть `__DIR__`), и из всех его подкаталогов:

```php
$this->configurator->createRobotLoader()
	->addDirectory(__DIR__)
	->register();
```

Альтернативный подход - загружать классы исключительно через [Composer |best-practices:composer] по PSR-4.


Часовой пояс
============

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

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


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

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

Конфигурационные файлы обычно пишутся в [формате NEON |neon:format]. В отдельной главе вы можете прочитать, [что можно настраивать |nette:configuring].

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

Метод `createContainer()` собирает контейнер и возвращает его экземпляр, а метод `loadContainer()` возвращает только имя порождённого класса контейнера, который вы затем можете создать сами. Это полезно в продвинутых сценариях.

Конфигурационные файлы загружаются методом `addConfig()`:

```php
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
```

Если мы хотим добавить больше конфигурационных файлов, мы можем вызвать функцию `addConfig()` несколько раз.

```php
$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` |dependency-injection:configuration#Подключение файлов].

Если в конфигурационных файлах встречаются элементы с одинаковыми ключами, они будут перезаписаны или, в случае [массивов, объединены |dependency-injection:configuration#Слияние]. Файл, подключённый позже, имеет более высокий приоритет, чем предыдущий. Файл, в котором указана секция `includes`, имеет более высокий приоритет, чем подключённые в нём файлы.


Статические параметры
---------------------

Параметры, используемые в конфигурационных файлах, можно определить [в секции `parameters` |dependency-injection:configuration#Параметры], а также передать (или переопределить) методом `addStaticParameters()` (его более старый, ныне устаревший псевдоним - `addParameters()`). Важно, что разные значения параметров вызовут порождение дополнительных DI-контейнеров, то есть дополнительных классов.

```php
$this->configurator->addStaticParameters([
	'projectId' => 23,
]);
```

На параметр `projectId` можно сослаться в конфигурации обычной записью `%projectId%`.


Динамические параметры
----------------------

Мы можем добавить в контейнер и динамические параметры, разные значения которых, в отличие от статических, не вызовут порождения новых DI-контейнеров.

```php
$this->configurator->addDynamicParameters([
	'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
```

Так мы легко добавим, например, переменные окружения, на которые затем можно сослаться в конфигурации записью `%env.variable%`.

```php
$this->configurator->addDynamicParameters([
	'env' => getenv(),
]);
```


Параметры по умолчанию
----------------------

В конфигурационных файлах вы можете использовать эти параметры:

- `%appDir%` - абсолютный путь к каталогу с файлом `Bootstrap.php`
- `%wwwDir%` - абсолютный путь к каталогу с начальным файлом `index.php`
- `%tempDir%` - абсолютный путь к каталогу временных файлов
- `%vendorDir%` - абсолютный путь к каталогу, куда Composer устанавливает библиотеки
- `%rootDir%` - абсолютный путь к корневому каталогу проекта
- `%baseUrl%` - абсолютный URL корневого каталога (динамический параметр, вычисляемый во время выполнения)
- `%debugMode%` - находится ли приложение в режиме отладки
- `%consoleMode%` - пришёл ли запрос из командной строки


Импортированные сервисы
-----------------------

Теперь копнём глубже. Хотя назначение DI-контейнера - создавать объекты, изредка может понадобиться вставить в контейнер уже существующий объект. Мы делаем это, определив сервис с флагом `imported: true`.

```neon
services:
	myservice:
		type: App\Model\MyCustomService
		imported: true
```

А в bootstrap вставляем объект в контейнер:

```php
$this->configurator->addServices([
	'myservice' => new App\Model\MyCustomService('foobar'),
]);
```


Разные окружения
================

Смело меняйте класс `Bootstrap` под свои нужды. Вы можете добавить в метод `bootWebApplication()` параметры, чтобы различать веб-проекты. Или можно добавить другие методы, например `bootTestEnvironment()`, инициализирующий окружение для модульных тестов, `bootConsoleApplication()` для скриптов, вызываемых из командной строки, и так далее.

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

Bootstrapping

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

  • как класс Bootstrap инициализирует окружение
  • как приложения настраиваются файлами NEON
  • как различать производственный режим и режим разработки
  • как создать и настроить DI-контейнер

Приложения, будь то веб-приложения или скрипты, запускаемые из командной строки, начинают своё выполнение с той или иной инициализации окружения. В былые времена за это отвечал файл с именем вроде include.inc.php, подключаемый начальным файлом. В современных приложениях Nette его заменил класс Bootstrap, который как часть приложения находится в файле app/Bootstrap.php. Он может выглядеть, например, так:

namespace App;

use Nette;
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();

Объект $application в ходе обработки запроса испускает события: onStartup, onRequest, onPresenter, onResponse, onShutdown и onError (при необработанном исключении). Вы можете привязать к ним обработчики, что удобно для ведения лога или мониторинга всего приложения.

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

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

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

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

Автоопределением внутренне занимается статический метод Configurator::detectDebugMode(), который вы можете вызвать и сами, например чтобы определить режим разработки вне конфигуратора. Он принимает необязательный белый список IP-адресов или имён компьютеров и возвращает, должен ли текущий запрос выполняться в режиме разработки:

$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');

Инструмент отладки 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. В отдельной главе вы можете прочитать, что можно настраивать.

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

Метод createContainer() собирает контейнер и возвращает его экземпляр, а метод loadContainer() возвращает только имя порождённого класса контейнера, который вы затем можете создать сами. Это полезно в продвинутых сценариях.

Конфигурационные файлы загружаются методом 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% – абсолютный путь к корневому каталогу проекта
  • %baseUrl% – абсолютный URL корневого каталога (динамический параметр, вычисляемый во время выполнения)
  • %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();
}