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

Конфигурация — это место, где мы размещаем определения пользовательских сервисов. Делается это в секции services.

Например, вот как мы создаем сервис с именем database, который будет экземпляром класса PDO:

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

Именование служб используется для того, чтобы мы могли ссылаться на них. Если на сервис не ссылаются, нет необходимости давать ему имя. Поэтому вместо имени мы просто используем двоеточие:

services:
	- PDO('sqlite::memory:')  # анонимный сервис

Однострочная запись может быть разбита на несколько строк, чтобы можно было добавить дополнительные ключи, например setup. Псевдоним для ключа create: – factory:.

services:
	database:
		create: PDO('sqlite::memory:')
		setup: ...

Затем мы получаем сервис из контейнера 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)

Что создаст фабричный метод в DI-контейнере:

public function createServiceDatabase(): PDO
{
	return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret');
}

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

services:
	database:
		create: PDO
		arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]

Статический метод также может создать сервис:

services:
	database: My\Database::create(root, secret)

Это эквивалентно коду PHP:

public function createServiceDatabase(): PDO
{
	return My\Database::create('root', 'secret');
}

Предполагается, что статический метод My\Database::create() имеет определенное возвращаемое значение, которое должен знать контейнер DI. Если у него его нет, мы записываем тип в конфигурацию:

services:
	database:
		create: My\Database::create(root, secret)
		type: PDO

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

services:
	routerFactory: App\Router\Factory
	router: @routerFactory::create()

Это эквивалентно коду PHP:

public function createServiceRouterFactory(): App\Router\Factory
{
	return new App\Router\Factory;
}

public function createServiceRouter(): Router
{
	return $this->getService('routerFactory')->create();
}

Вызовы методов можно объединять в цепочки, как в PHP:

services:
	foo: FooFactory::build()::get()

Это эквивалентно коду PHP:

public function createServiceFoo()
{
	return FooFactory::build()->get();
}

Аргументы

Именованные параметры также могут использоваться для передачи аргументов:

services:
	database: PDO(
		'mysql:host=127.0.0.1;dbname=test'  # позиционный
		username: root                      # именованный
		password: secret                    # именованный
	)

Использование запятых необязательно при разбиении аргументов на несколько строк.

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

services:
	- Foo(@anotherService, %appDir%)

Соответствует коду PHP:

public function createService01(): Foo
{
	return new Foo($this->getService('anotherService'), '...');
}

Если первый аргумент – автомонтируемый, а вы хотите указать второй, опустите первый с помощью символа _, например Foo(_, %appDir%). Или, что ещё лучше, передавайте только второй аргумент в качестве именованного параметра, например: Foo(path: %appDir%).

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

services:
	analyser: My\Analyser(
		FilesystemIterator(%appDir%)         # создаем объект
		DateTime::createFromFormat('Y-m-d')  # вызываем статический метод
		@anotherService                      # передаём другой сервис
		@http.request::getRemoteAddress()    # вызываем метод другого сервиса
		::getenv(NetteMode)                  # вызываем глобальную функцию
	)

Соответствует коду PHP:

public function createServiceAnalyser(): My\Analyser
{
	return new My\Analyser(
		new FilesystemIterator('...'),
		DateTime::createFromFormat('Y-m-d'),
		$this->getService('anotherService'),
		$this->getService('http.request')->getRemoteAddress(),
		getenv('NetteMode')
	);
}

Специальные функции

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

  • not(%arg%) отрицание
  • bool(%arg%) приведение к bool без потерь
  • int(%arg%) приведение к int без потерь
  • float(%arg%) приведение к плавающему состоянию без потерь
  • string(%arg%) приведение к строке без потерь
services:
	- Foo(
		id: int(::getenv('ProjectId'))
		productionMode: not(%debugMode%)
	)

Переписывание без потерь отличается от обычного переписывания в PHP, например используя (int), в том, что оно выбрасывает исключение для нечисловых значений.

В качестве аргументов можно передавать несколько сервисов. Массив всех сервисов определенного типа (т. е. класса или интерфейса) создается функцией typed(). Функция будет опускать сервисы, у которых отключено автоподключение, при этом можно указать несколько типов, разделенных запятой.

services:
	- BarsDependent( typed(Bar) )

Вы также можете передать массив сервисов автоматически, используя автосвязывание.

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

services:
	- LoggersDependent( tagged(logger) )

Ссылки на сервисы

Ссылки на отдельные сервисы используются с помощью символа @ и имени, например @database:

services:
	- create: Foo(@database)
	  setup:
			- setCacheStorage(@cache.storage)

Соответствует коду PHP:

public function createService01(): Foo
{
	$service = new Foo($this->getService('database'));
	$service->setCacheStorage($this->getService('cache.storage'));
	return $service;
}

Даже на анонимные сервисы можно ссылаться с помощью обратного вызова, просто укажите их тип (класс или интерфейс) вместо имени. Однако обычно в этом нет необходимости из-за автосвязывания.

services:
	- create: Foo(@Nette\Database\Connection)  # или @\PDO
	  setup:
			- setCacheStorage(@cache.storage)

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;
}

Однако статические методы или методы других сервисов также могут быть вызваны в настройке. Мы передаем им фактический сервис как @self:

services:
	foo:
		create: Foo
		setup:
			- My\Helpers::initializeFoo(@self)
			- @anotherService::setFoo(@self)

Соответствует коду PHP:

public function createServiceFoo(): Foo
{
	$service = new Foo;
	My\Helpers::initializeFoo($service);
	$this->getService('anotherService')->setFoo($service);
	return $service;
}

Автосвязывание

Ключ 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 является информативным и говорит о том, что мы просто изменяем существующий сервис.

Мы также можем добавить 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