Nette Documentation Preview

syntax
Загрузка
********

<div class=perex>

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

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

</div>


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

```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`, который находится в [публичном каталоге |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();
```

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


Режим разработки vs режим production
====================================

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

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


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


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

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

Если мы хотим включить режим разработки и в других случаях, например, для программистов, обращающихся с конкретного 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` включает режим разработки принудительно, что никогда не должно происходить на production-сервере.


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

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

```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]
В режиме разработки контейнер автоматически обновляется при каждом изменении кода или конфигурационных файлов. В режиме production он генерируется только один раз, и изменения не проверяются для максимальной производительности.

Конфигурационные файлы загружаем с помощью `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%` — абсолютный путь к корневому каталогу проекта
- `%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();
}
```

Загрузка

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

  • как класс Bootstrap инициализирует среду
  • как приложения настраиваются с помощью 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();

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

Режим разработки vs режим production

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

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

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

  • режим разработки: на localhost (IP-адрес 127.0.0.1 или ::1), если нет прокси (т. е. его HTTP-заголовка)
  • режим production: везде в остальных случаях

Если мы хотим включить режим разработки и в других случаях, например, для программистов, обращающихся с конкретного 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 включает режим разработки принудительно, что никогда не должно происходить на production-сервере.

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

Для легкой отладки мы также включим отличный инструмент Tracy. В режиме разработки он визуализирует ошибки, а в режиме production записывает ошибки в указанный каталог:

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

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

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