Определения на услуги

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