Визначення сервісів
Конфігурація – це місце, де ми вказуємо 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
lazy: false
Коли сервіс визначено як лінивий, запит до нього з контейнера DI повертатиме спеціальний проксі-об'єкт. Цей проксі виглядає і поводиться як справжній сервіс, але справжня ініціалізація (виклик конструктора і налаштування) відбудеться лише при першому виклику будь-якого з його методів або властивостей.
Ліниве завантаження можна використовувати лише для класів, визначених користувачем, але не для внутрішніх класів 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: 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