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

Обзор параметров конфигурации DI-контейнера Nette.

Конфигурационный файл

DI-контейнером Nette легко управлять с помощью конфигурационных файлов. Обычно они пишутся в формате NEON. Мы рекомендуем использовать редакторы с поддержкой этого формата.

 decorator: 	Decorator
di: DI-контейнер
extensions: Установка дополнительных DI-расширений
includes: Подключение файлов
parameters: Параметры
search: Автоматическая регистрация сервисов
services: Сервисы

Чтобы записать строку, содержащую символ %, его нужно экранировать удвоением до %%.

Параметры

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

parameters:
	dsn: 'mysql:host=127.0.0.1;dbname=test'
	user: root
	password: secret

На параметр dsn мы ссылаемся где угодно в конфигурации записью %dsn%. Параметры можно использовать и внутри строк вроде '%wwwDir%/images'.

Параметрами могут быть не только строки или числа, они могут содержать и массивы:

parameters:
	mailer:
		host: smtp.example.com
		secure: ssl
		user: franta@gmail.com
	languages: [cs, en, de]

На конкретный ключ мы ссылаемся как %mailer.user%.

Если вашему коду (например, классу) нужно значение параметра, передайте его в класс. Например, в конструкторе. Никакого глобального объекта конфигурации, у которого классы могли бы запросить значения параметров, нет. Это было бы нарушением принципа внедрения зависимостей.

Сервисы

См. отдельную главу.

Decorator

Как изменить сразу несколько сервисов определённого типа? Например, как вызвать определённый метод у всех презентеров, наследующих от конкретного базового класса? Для этого и служит decorator.

decorator:
	# для всех сервисов, которые являются экземплярами этого класса или интерфейса
	App\Presentation\BasePresenter:
		setup:
			- setProjectId(10)       # вызвать этот метод
			- $absoluteUrls = true   # и задать переменную

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

decorator:
	InjectableInterface:
		tags: [mytag: 1]
		inject: true

DI

Технические настройки DI-контейнера.

di:
	# показывать DIC в панели Tracy?
	debugger: ...        # (bool) по умолчанию определяется автоматически (включено при наличии Tracy)

	# типы параметров, которые никогда не проходят autowiring
	excluded: ...        # (string[])

	# включить ленивое создание сервисов?
	lazy: ...            # (bool) по умолчанию false

	# класс, от которого наследуется DI-контейнер
	parentClass: ...     # (string) по умолчанию Nette\DI\Container

Ленивые сервисы

Установка lazy: true включает ленивое (отложенное) создание сервисов. Это значит, что сервисы на самом деле создаются не в момент запроса из DI-контейнера, а только при первом использовании. Это может ускорить старт приложения и снизить расход памяти, потому что создаются только те сервисы, которые действительно нужны для данного запроса.

Для конкретного сервиса ленивое создание можно настроить отдельно.

Ленивые объекты можно использовать только для пользовательских классов, но не для внутренних классов PHP. Требуется PHP 8.4 или новее.

Экспорт метаданных

Класс DI-контейнера содержит также много метаданных. Вы можете уменьшить его размер, сократив экспорт метаданных.

di:
	export:
		# экспортировать параметры?
		parameters: false   # (bool) по умолчанию true

		# экспортировать теги и какие?
		tags:               # (string[]|bool) по умолчанию все
			- event.subscriber

		# экспортировать данные для autowiring и какие?
		types:              # (string[]|bool) по умолчанию все
			- Nette\Database\Connection
			- Symfony\Component\Console\Application

Если вы не используете $container->getParameters(), вы можете отключить экспорт параметров. Кроме того, вы можете экспортировать только те теги, которые действительно используете для получения сервисов через $container->findByTag(...). Если вы этот метод вообще не вызываете, экспорт тегов можно полностью отключить значением false.

Метаданные для autowiring можно заметно сократить, перечислив только те классы, которые вы действительно запрашиваете через $container->getByType(). Снова: если вы этот метод не вызываете (или вызываете только в файле bootstrap, например чтобы получить Nette\Application\Application), экспорт типов можно полностью отключить значением false.

Extensions

Регистрация дополнительных DI-расширений. Вот так вы добавите, например, DI-расширение Dibi\Bridges\Nette\DibiExtension3 под именем dibi:

extensions:
	dibi: Dibi\Bridges\Nette\DibiExtension3

Затем вы настраиваете его в секции dibi:

dibi:
	host: localhost

Как расширение можно добавить и класс с параметрами:

extensions:
	application: Nette\Bridges\ApplicationDI\ApplicationExtension(%debugMode%, [%appDir%], %tempDir%/cache)

Подключение файлов

Дополнительные конфигурационные файлы можно подключить в секции includes:

includes:
	- parameters.php
	- services.neon
	- presenters.neon

Имя parameters.php – не опечатка: конфигурацию можно записать и в PHP-файле, который возвращает её массивом:

<?php
return [
	'database' => [
		'main' => [
			'dsn' => 'sqlite::memory:',
		],
	],
];

Если в нескольких конфигурационных файлах встречаются элементы с одинаковыми ключами, они будут перезаписаны или, в случае массивов, объединены. Файл, подключённый позже, имеет более высокий приоритет, чем предыдущий. Файл, в котором указана секция includes, имеет более высокий приоритет, чем подключённые в нём файлы.

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

Достаточно указать, в каких каталогах (и подкаталогах) искать классы:

search:
	-	in: %appDir%/Forms
	-	in: %appDir%/Model

Если вам нужно всего одно правило поиска, список можно опустить и записать его ключи прямо под search:

search:
	in: %appDir%

Однако обычно мы не хотим добавлять совершенно все классы и интерфейсы, поэтому их можно отфильтровать:

search:
	-	in: %appDir%/Forms

		# фильтрация по имени файла (string|string[])
		files:
			- *Factory.php

		# фильтрация по имени класса (string|string[])
		classes:
			- *Factory

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

search:
	-	in: %appDir%
		extends:
			- App\*Form
		implements:
			- App\*FormInterface

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

search:
	-	in: %appDir%
		exclude:
			files: ...
			classes: ...
			extends: ...
			implements: ...

Всем автоматически зарегистрированным сервисам можно назначить теги:

search:
	-	in: %appDir%
		tags: ...

Помимо классов поиск регистрирует и интерфейсы с единственным методом create() или get() – как генерируемые фабрики или аксессоры. Классы, для которых сервис того же типа уже зарегистрирован в контейнере, пропускаются, поэтому дубликаты не возникают.

Слияние

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

config1.neon config2.neon результат
items:
	- 1
	- 2
items:
	- 3
items:
	- 1
	- 2
	- 3

Для массивов слияние можно предотвратить, добавив после имени ключа восклицательный знак:

config1.neon config2.neon результат
items:
	- 1
	- 2
items!:
	- 3
items:
	- 3
версия: 3.x