Определения сервисов

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

Секция services в конфигурационном файле NEON – это место, где мы определяем наши пользовательские сервисы и их конфигурации. Рассмотрим простой пример определения сервиса с именем database, который представляет собой экземпляр класса PDO:

services:
	database: PDO('sqlite::memory:')

Эта конфигурация приводит к появлению следующего фабричного метода в контейнере DI:

public function createServiceDatabase(): PDO
{
	return new PDO('sqlite::memory:');
}

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

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'
	)

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

services:
	foo: Foo(_, %appDir%)

Аргументами могут быть сервисы, параметры и многое другое, см. средства выражения.

Настройка

В разделе 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;
}

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

Используйте синтаксис первоклассных вызываемых элементов:

# creating a callback, equivalent to [@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()

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

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() создает массив всех сервисов определенного типа (класса или интерфейса). При этом исключаются сервисы с выключенным автоподключением. Можно указать несколько типов, разделяя их запятыми.

services:
	- BarsDependent( typed(Bar) )

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

Функция tagged() создает массив всех сервисов с указанным тегом. Можно перечислить несколько тегов, разделяя их запятыми.

services:
	- LoggersDependent( tagged(logger) )

Автоэлектрика

Ключ autowired позволяет модифицировать поведение автоподключения для конкретного сервиса. Более подробная информация приведена в главе, посвященной автоподключению.

services:
	foo:
		create: Foo
		autowired: false     # служба foo исключена из автоподключения

Ленивые услуги

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

services:
	foo:
		create: Foo
		lazy: false

Когда сервис определен как ленивый, запрос к нему из DI-контейнера будет возвращать специальный прокси-объект. Этот прокси выглядит и ведет себя как настоящий сервис, но настоящая инициализация (вызов конструктора и настройка) будет происходить только при первом вызове любого из его методов или свойств.

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

Мы также можем дополнить настройку:

services:
	application.application:
		create: MyApplication
		alteration: true
		setup:
			- '$onStartup[]' = [@resource, init]

При перезаписи сервиса может потребоваться удалить исходные аргументы, элементы настройки или теги, и здесь пригодится reset:

services:
	application.application:
		create: MyApplication
		alteration: true
		reset:
			- arguments
			- setup
			- tags

Если необходимо удалить сервис, добавленный расширением, это можно сделать следующим образом:

services:
	cache.journal: false
версия: 3.x