Nette DI Container

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

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

parameters:
	db:
		dsn: 'mysql:'
		user: root
		password: '***'

services:
	- Nette\Database\Connection(%db.dsn%, %db.user%, %db.password%)
	- ArticleFactory
	- EditController

Запись очень краткая.

Все зависимости, объявленные в конструкторах классов ArticleFactory и EditController, Nette DI обнаруживает и передаёт автоматически благодаря так называемому autowiring, поэтому в конфигурационном файле указывать ничего не нужно. Так что даже при изменении параметров менять в конфигурации ничего не придётся. Во время разработки Nette автоматически перегенерирует контейнер. Вы можете сосредоточиться исключительно на разработке приложения.

Если мы хотим передавать зависимости через сеттеры, для этого служит секция setup.

Nette DI порождает PHP-код контейнера напрямую. Результатом становится файл .php, который вы можете открыть и изучить. Благодаря этому вы видите в точности, как работает контейнер. Вы можете и отлаживать его в своей IDE, проходя по шагам. И, что важнее всего, порождённый PHP-код исключительно быстр.

Nette DI умеет порождать и код фабрики на основе заданного интерфейса. Поэтому вместо класса ArticleFactory нам достаточно создать в приложении только интерфейс:

interface ArticleFactory
{
	function create(): Article;
}

Полный пример вы найдёте на GitHub.

Самостоятельное использование

Встроить библиотеку Nette DI в приложение очень просто. Сначала установим её через Composer (потому что скачивать zip-файлы давно вышло из моды):

composer require nette/di

Следующий код использует Compiler, чтобы создать экземпляр DI-контейнера по конфигурации, хранящейся в файле config.neon:

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function ($compiler) {
	$compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;

Контейнер порождается только один раз, его код записывается в кеш (каталог __DIR__ . '/temp'), а при последующих запросах только оттуда загружается.

Сам по себе Compiler включает в конфигурации только секции services и parameters. Чтобы использовать остальные, такие как search, decorator, di или inject, сначала зарегистрируйте их расширения. А чтобы можно было регистрировать расширения из секции extensions конфигурации, добавьте ExtensionsExtension:

$compiler->addExtension('search', new Nette\DI\Extensions\SearchExtension($tempDir));
$compiler->addExtension('extensions', new Nette\DI\Extensions\ExtensionsExtension);

Configurator, используемый в полноценных приложениях Nette, регистрирует всё это автоматически.

Если вы держите несколько разных контейнеров в одном каталоге кеша, различайте их ключом, передаваемым вторым аргументом в load(); он становится частью имени порождаемого класса:

$class = $loader->load(
	fn($compiler) => $compiler->loadConfig(__DIR__ . '/config.neon'),
	'my-key',
);

Для создания и получения сервисов служат методы getService() или getByType(). Вот так мы создаём объект EditController:

$controller = $container->getByType(EditController::class);
$controller->someMethod();

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

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true);

Работа с контейнером

Помимо getService() и getByType(), объект контейнера предлагает ещё несколько полезных методов:

  • getByType(string $type, bool $throw = true): ?object возвращает сервис заданного типа. Если передать вторым аргументом false, при отсутствии такого сервиса вместо исключения возвращается null.
  • hasService(string $name): bool и isCreated(string $name): bool сообщают, определён ли сервис и был ли он уже создан.
  • getParameters(): array возвращает все параметры контейнера, getParameter($key) возвращает один из них.
  • createInstance(string $class, array $args = []): object создаёт новый экземпляр заданного класса и передаёт зависимости его конструктора через autowiring.
  • callMethod(callable $function, array $args = []): mixed вызывает заданный callable и передаёт его аргументы через autowiring.
  • callInjects(object $service): void вызывает у заданного объекта все методы inject*() и передаёт им зависимости.

Конструктор контейнера принимает также массив параметров, дополняющих те, что заданы в конфигурации:

$container = new $class(['host' => 'localhost']);

Использование с Nette Framework

Как мы показали, использование Nette DI не ограничено приложениями на Nette Framework: встроить его можно куда угодно всего тремя строками кода. Однако если вы разрабатываете приложения на Nette Framework, конфигурацией и созданием контейнера занимается Bootstrap.

версия: 3.x