Определения сервисов
Конфигурация — это место, где мы размещаем определения
пользовательских сервисов. Делается это в секции services
.
Например, вот как мы создаем сервис с именем database
, который
будет экземпляром класса PDO
:
services:
database: PDO('sqlite::memory:')
Именование служб используется для того, чтобы мы могли ссылаться на них. Если на сервис не ссылаются, нет необходимости давать ему имя. Поэтому вместо имени мы просто используем двоеточие:
services:
- PDO('sqlite::memory:') # анонимный сервис
Однострочная запись может быть разбита на несколько строк, чтобы
можно было добавить дополнительные ключи, например setup.
Псевдоним для ключа create:
– factory:
.
services:
database:
create: PDO('sqlite::memory:')
setup: ...
Затем мы получаем сервис из контейнера 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)
Что создаст фабричный метод в DI-контейнере:
public function createServiceDatabase(): PDO
{
return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret');
}
Альтернативно, ключ arguments
может быть использован для передачи
аргументов:
services:
database:
create: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
Статический метод также может создать сервис:
services:
database: My\Database::create(root, secret)
Это эквивалентно коду PHP:
public function createServiceDatabase(): PDO
{
return My\Database::create('root', 'secret');
}
Предполагается, что статический метод My\Database::create()
имеет
определенное возвращаемое значение, которое должен знать контейнер DI.
Если у него его нет, мы записываем тип в конфигурацию:
services:
database:
create: My\Database::create(root, secret)
type: PDO
Nette DI предоставляет вам чрезвычайно мощные средства выражения,
позволяющие написать практически всё, что угодно. Например, чтобы обратиться к другой службе и вызвать её метод. Для
простоты вместо ->
используется ::
.
services:
routerFactory: App\Router\Factory
router: @routerFactory::create()
Это эквивалентно коду PHP:
public function createServiceRouterFactory(): App\Router\Factory
{
return new App\Router\Factory;
}
public function createServiceRouter(): Router
{
return $this->getService('routerFactory')->create();
}
Вызовы методов можно объединять в цепочки, как в PHP:
services:
foo: FooFactory::build()::get()
Это эквивалентно коду PHP:
public function createServiceFoo()
{
return FooFactory::build()->get();
}
Аргументы
Именованные параметры также могут использоваться для передачи аргументов:
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test' # позиционный
username: root # именованный
password: secret # именованный
)
Использование запятых необязательно при разбиении аргументов на несколько строк.
Конечно, мы также можем использовать другие сервисы или параметры в качестве аргументов:
services:
- Foo(@anotherService, %appDir%)
Соответствует коду PHP:
public function createService01(): Foo
{
return new Foo($this->getService('anotherService'), '...');
}
Если первый аргумент – автомонтируемый, а вы хотите
указать второй, опустите первый с помощью символа _
, например
Foo(_, %appDir%)
. Или, что ещё лучше, передавайте только второй аргумент
в качестве именованного параметра, например: Foo(path: %appDir%)
.
Nette DI и формат NEON дают вам чрезвычайно мощные выразительные средства, позволяющие написать практически всё, что угодно. Таким образом, аргументом может быть вновь созданный объект, вы можете вызывать статические методы, методы других сервисов или даже глобальные функции, используя специальную нотацию:
services:
analyser: My\Analyser(
FilesystemIterator(%appDir%) # создаем объект
DateTime::createFromFormat('Y-m-d') # вызываем статический метод
@anotherService # передаём другой сервис
@http.request::getRemoteAddress() # вызываем метод другого сервиса
::getenv(NetteMode) # вызываем глобальную функцию
)
Соответствует коду PHP:
public function createServiceAnalyser(): My\Analyser
{
return new My\Analyser(
new FilesystemIterator('...'),
DateTime::createFromFormat('Y-m-d'),
$this->getService('anotherService'),
$this->getService('http.request')->getRemoteAddress(),
getenv('NetteMode')
);
}
Специальные функции
Вы также можете использовать специальные функции в аргументах для приведения или отрицания значений:
not(%arg%)
отрицаниеbool(%arg%)
приведение к bool без потерьint(%arg%)
приведение к int без потерьfloat(%arg%)
приведение к плавающему состоянию без потерьstring(%arg%)
приведение к строке без потерь
services:
- Foo(
id: int(::getenv('ProjectId'))
productionMode: not(%debugMode%)
)
Переписывание без потерь отличается от обычного переписывания в PHP,
например используя (int)
, в том, что оно выбрасывает исключение
для нечисловых значений.
В качестве аргументов можно передавать несколько сервисов. Массив
всех сервисов определенного типа (т. е. класса или интерфейса)
создается функцией typed()
. Функция будет опускать сервисы, у
которых отключено автоподключение, при этом можно указать несколько
типов, разделенных запятой.
services:
- BarsDependent( typed(Bar) )
Вы также можете передать массив сервисов автоматически, используя автосвязывание.
Массив всех сервисов с определенным тегом создается
функцией tagged()
. Можно указать несколько тегов, разделенных
запятой.
services:
- LoggersDependent( tagged(logger) )
Ссылки на сервисы
Ссылки на отдельные сервисы используются с помощью символа @
и
имени, например @database
:
services:
- create: Foo(@database)
setup:
- setCacheStorage(@cache.storage)
Соответствует коду PHP:
public function createService01(): Foo
{
$service = new Foo($this->getService('database'));
$service->setCacheStorage($this->getService('cache.storage'));
return $service;
}
Даже на анонимные сервисы можно ссылаться с помощью обратного вызова, просто укажите их тип (класс или интерфейс) вместо имени. Однако обычно в этом нет необходимости из-за автосвязывания.
services:
- create: Foo(@Nette\Database\Connection) # или @\PDO
setup:
- setCacheStorage(@cache.storage)
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;
}
Однако статические методы или методы других сервисов также могут
быть вызваны в настройке. Мы передаем им фактический сервис как
@self
:
services:
foo:
create: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Соответствует коду PHP:
public function createServiceFoo(): Foo
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
Автосвязывание
Ключ 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
является информативным и говорит о том, что мы
просто изменяем существующий сервис.
Мы также можем добавить 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