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

Конфигурация – это место, где мы указываем 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
		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