Определение сервисов
Конфигурация — это место, где мы учим DI-контейнер, как собирать отдельные сервисы и как связывать их с другими зависимостями. Nette предоставляет очень понятный и элегантный способ достижения этой цели.
Секция services в конфигурационном файле формата NEON — это место,
где мы определяем собственные сервисы и их конфигурации. Посмотрим на
простой пример определения сервиса с именем database, который
представляет экземпляр класса PDO:
services:
	database: PDO('sqlite::memory:')
Указанная конфигурация приведет к следующему фабричному методу в DI-контейнере:
public function createServiceDatabase(): PDO
{
	return new PDO('sqlite::memory:');
}
Имена сервисов позволяют нам ссылаться на них в других частях
конфигурационного файла в формате @имяСервиса. Если нет
необходимости именовать сервис, мы можем просто использовать дефис:
services:
	- PDO('sqlite::memory:')
Для получения сервиса из DI-контейнера мы можем использовать метод
getService() с именем сервиса в качестве параметра или метод
getByType() с типом сервиса:
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Создание сервиса
Обычно мы создаем сервис, просто создавая экземпляр определенного класса. Например:
services:
	database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Если нам нужно расширить конфигурацию дополнительными ключами, определение можно разбить на несколько строк:
services:
	database:
		create: PDO('sqlite::memory:')
		setup: ...
Ключ create имеет псевдоним factory, оба варианта на практике
распространены. Однако мы рекомендуем использовать create.
Аргументы конструктора или метода создания могут быть альтернативно
записаны в ключе arguments:
services:
	database:
		create: PDO
		arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
Сервисы не обязательно должны создаваться только простым созданием экземпляра класса, они также могут быть результатом вызова статических методов или методов других сервисов:
services:
	database: DatabaseFactory::create()
	router: @routerFactory::create()
Обратите внимание, что для простоты вместо -> используется
::, см. выразительные средства. Будут
сгенерированы следующие фабричные методы:
public function createServiceDatabase(): PDO
{
	return DatabaseFactory::create();
}
public function createServiceRouter(): RouteList
{
	return $this->getService('routerFactory')->create();
}
DI-контейнеру необходимо знать тип созданного сервиса. Если мы создаем сервис с помощью метода, у которого не указан тип возвращаемого значения, мы должны явно указать этот тип в конфигурации:
services:
	database:
		create: DatabaseFactory::create()
		type: PDO
Аргументы
В конструктор и методы мы передаем аргументы способом, очень похожим на сам PHP:
services:
	database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Для лучшей читаемости мы можем разбить аргументы на отдельные строки. В таком случае использование запятых необязательно:
services:
	database: PDO(
		'mysql:host=127.0.0.1;dbname=test'
		root
		secret
	)
Аргументы также можно именовать, и тогда не нужно беспокоиться об их порядке:
services:
	database: PDO(
		username: root
		password: secret
		dsn: 'mysql:host=127.0.0.1;dbname=test'
	)
Если вы хотите пропустить некоторые аргументы и использовать их значение по умолчанию или подставить сервис с помощью autowiring, используйте подчеркивание:
services:
	foo: Foo(_, %appDir%)
В качестве аргументов можно передавать сервисы, использовать параметры и многое другое, см. выразительные средства.
Setup
В секции setup мы определяем методы, которые должны вызываться
при создании сервиса.
services:
	database:
		create: PDO(%dsn%, %user%, %password%)
		setup:
			- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
В PHP это выглядело бы так:
public function createServiceDatabase(): PDO
{
	$service = new PDO('...', '...', '...');
	$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	return $service;
}
Кроме вызова методов, можно также передавать значения в свойства. Поддерживается также добавление элемента в массив, которое необходимо записывать в кавычках, чтобы не конфликтовать с синтаксисом NEON:
services:
	foo:
		create: Foo
		setup:
			- $value = 123
			- '$onClick[]' = [@bar, clickHandler]
Что в PHP-коде выглядело бы следующим образом:
public function createServiceFoo(): Foo
{
	$service = new Foo;
	$service->value = 123;
	$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
	return $service;
}
В setup можно также вызывать статические методы или методы других
сервисов. Если вам нужно передать в качестве аргумента текущий сервис,
укажите его как @self:
services:
	foo:
		create: Foo
		setup:
			- My\Helpers::initializeFoo(@self)
			- @anotherService::setFoo(@self)
Обратите внимание, что для простоты вместо -> используется
::, см. выразительные средства. Будет
сгенерирован такой фабричный метод:
public function createServiceFoo(): Foo
{
	$service = new Foo;
	My\Helpers::initializeFoo($service);
	$this->getService('anotherService')->setFoo($service);
	return $service;
}
Выразительные средства
Nette DI предоставляет нам чрезвычайно богатые выразительные средства, с помощью которых мы можем записать почти все что угодно. В конфигурационных файлах мы можем использовать параметры:
# параметр
%wwwDir%
# значение параметра под ключом
%mailer.user%
# параметр внутри строки
'%wwwDir%/images'
Далее создавать объекты, вызывать методы и функции:
# создание объекта
DateTime()
# вызов статического метода
Collator::create(%locale%)
# вызов PHP функции
::getenv(DB_USER)
Ссылаться на сервисы либо по их имени, либо по типу:
# сервис по имени
@database
# сервис по типу
@Nette\Database\Connection
Использовать синтаксис first-class callable:
# создание callback, аналог [@user, logout]
@user::logout(...)
Использовать константы:
# константа класса
FilesystemIterator::SKIP_DOTS
# глобальную константу получим PHP функцией constant()
::constant(PHP_VERSION)
Вызовы методов можно объединять в цепочку так же, как в PHP. Только для
простоты вместо -> используется :::
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')
@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
Эти выражения можно использовать где угодно, при создании сервисов, в аргументах, в секции setup или параметрах:
parameters:
	ipAddress: @http.request::getRemoteAddress()
services:
	database:
		create: DatabaseFactory::create( @anotherService::getDsn() )
		setup:
			- initialize( ::getenv('DB_USER') )
Специальные функции
В конфигурационных файлах вы можете использовать эти специальные функции:
- not()отрицание значения
- bool(),- int(),- float(),- string()преобразование типа без потерь
- typed()создает массив всех сервисов указанного типа
- tagged()создает массив всех сервисов с данным тегом
services:
	- Foo(
		id: int(::getenv('ProjectId'))
		productionMode: not(%debugMode%)
	)
В отличие от классического приведения типов в PHP, такого как
(int), преобразование без потерь вызовет исключение для
нечисловых значений.
Функция typed() создает массив всех сервисов данного типа (класс
или интерфейс). Она пропускает сервисы, у которых отключен autowiring. Можно
указать несколько типов, разделенных запятой.
services:
	- BarsDependent( typed(Bar) )
Массив сервисов определенного типа можно передавать как аргумент также автоматически с помощью autowiring.
Функция tagged() создает массив всех сервисов с определенным
тегом. Здесь также можно указать несколько тегов, разделенных
запятой.
services:
	- LoggersDependent( tagged(logger) )
Autowiring
Ключ autowired позволяет влиять на поведение autowiring для
конкретного сервиса. Для деталей см. главу об autowiring.
services:
	foo:
		create: Foo
		autowired: false     # сервис foo исключен из autowiring
Ленивые сервисы
Ленивая загрузка (Lazy loading) — это техника, которая откладывает создание сервиса до момента, когда он действительно необходим. В глобальной конфигурации можно включить ленивое создание для всех сервисов сразу. Для отдельных сервисов можно переопределить это поведение:
services:
	foo:
		create: Foo
		lazy: false
Когда сервис определен как ленивый, при его запросе из DI-контейнера мы получаем специальный объект-заместитель. Он выглядит и ведет себя так же, как реальный сервис, но фактическая инициализация (вызов конструктора и setup) происходит только при первом вызове любого его метода или свойства.
Ленивая загрузка может использоваться только для пользовательских классов, а не для внутренних классов PHP. Требуется PHP 8.4 или новее.
Теги
Теги служат для добавления дополнительной информации к сервисам. Сервису можно добавить один или несколько тегов:
services:
	foo:
		create: Foo
		tags:
			- cached
Теги также могут нести значения:
services:
	foo:
		create: Foo
		tags:
			logger: monolog.logger.event
Чтобы получить все сервисы с определенными тегами, вы можете
использовать функцию tagged():
services:
	- LoggersDependent( tagged(logger) )
В DI-контейнере вы можете получить имена всех сервисов с определенным
тегом с помощью метода findByTag():
$names = $container->findByTag('logger');
// $names - это массив, содержащий имя сервиса и значение тега
// например, ['foo' => 'monolog.logger.event', ...]
Режим Inject
С помощью флага inject: true активируется передача зависимостей
через публичные переменные с аннотацией inject и методы inject*().
services:
	articles:
		create: App\Model\Articles
		inject: true
По умолчанию inject активирован только для презентеров.
Модификация сервисов
DI-контейнер содержит множество сервисов, которые были добавлены с
помощью встроенного или пользовательского расширения.
Вы можете изменять определения этих сервисов прямо в конфигурации.
Например, вы можете изменить класс сервиса application.application,
который по умолчанию является Nette\Application\Application, на другой:
services:
	application.application:
		create: MyApplication
		alteration: true
Флаг alteration является информативным и указывает, что мы только
модифицируем существующий сервис.
Мы также можем дополнить setup:
services:
	application.application:
		create: MyApplication
		alteration: true
		setup:
			- '$onStartup[]' = [@resource, init]
При переопределении сервиса мы можем захотеть удалить исходные
аргументы, элементы setup или теги, для чего служит reset:
services:
	application.application:
		create: MyApplication
		alteration: true
		reset:
			- arguments
			- setup
			- tags
Если вы хотите удалить сервис, добавленный расширением, вы можете сделать это так:
services:
	cache.journal: false