Определения сервисов
Конфигурация – это место, где мы указываем 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