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