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

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

Секція services у конфігураційному файлі формату NEON – це місце, де ми визначаємо власні сервіси та їхню конфігурацію. Розглянемо простий приклад визначення сервісу під назвою database, який представляє екземпляр класу PDO:

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

Наведена конфігурація призведе до створення наступного фабричного методу в DI-контейнері:

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

Назви сервісів дозволяють нам посилатися на них в інших частинах конфігураційного файлу у форматі @назваСервісу. Якщо немає потреби називати сервіс, ми можемо просто використати маркер списку:

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

Якщо ви хочете пропустити деякі аргументи та використати їхнє значення за замовчуванням або підставити сервіс за допомогою autowiring, використовуйте підкреслення:

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

Як аргументи можна передавати сервіси, використовувати параметри та багато іншого, див. #виразні засоби.

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

Однак у setup можна викликати також статичні методи або методи інших сервісів. Якщо вам потрібно передати поточний сервіс як аргумент, вкажіть його як @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

Використовувати синтаксис first-class callable:

# створення callback, аналог [@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()

Ці вирази можна використовувати будь-де, при створенні сервісів, в аргументах, у секції #setup або параметрах:

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

services:
	- BarsDependent( typed(Bar) )

Масив сервісів певного типу ви також можете передавати як аргумент автоматично за допомогою autowiring.

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

services:
	- LoggersDependent( tagged(logger) )

Autowiring

Ключ autowired дозволяє впливати на поведінку autowiring для конкретного сервісу. Детальніше див. розділ про autowiring.

services:
	foo:
		create: Foo
		autowired: false     # сервіс foo виключено з autowiring

Lazy-сервіси

Lazy loading (ліниве завантаження) – це техніка, яка відкладає створення сервісу до моменту, коли він дійсно потрібен. У глобальній конфігурації можна увімкнути ліниве створення для всіх сервісів одночасно. Для окремих сервісів ви можете змінити цю поведінку:

services:
	foo:
		create: Foo
		lazy: false

Коли сервіс визначено як lazy, при його запиті з DI-контейнера ми отримуємо спеціальний об'єкт-заступник. Він виглядає і поводиться так само, як реальний сервіс, але фактична ініціалізація (виклик конструктора та setup) відбувається лише при першому виклику будь-якого його методу або властивості.

Lazy loading можна використовувати лише для користувацьких класів, а не для внутрішніх класів 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

За допомогою прапорця 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
версія: 3.x