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