Визначення сервісів

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