Nette Documentation Preview

syntax
Определения сервисов
********************

.[perex]
Конфигурация - это место, где мы указываем DI-контейнеру, как собирать отдельные сервисы и как связывать их с другими зависимостями. Nette предоставляет очень понятный и элегантный способ достижения этой цели.

Секция `services` в конфигурационном файле NEON - это место, где мы определяем наши пользовательские сервисы и их конфигурации. Рассмотрим простой пример определения сервиса с именем `database`, который представляет собой экземпляр класса `PDO`:

```neon
services:
	database: PDO('sqlite::memory:')
```

Эта конфигурация приводит к появлению следующего фабричного метода в [контейнере DI |container]:

```php
public function createServiceDatabase(): PDO
{
	return new PDO('sqlite::memory:');
}
```

Имена сервисов позволяют ссылаться на них в других частях конфигурационного файла, используя формат `@serviceName`. Если нет необходимости присваивать сервису имя, можно просто использовать пулевую точку:

```neon
services:
	- PDO('sqlite::memory:')
```

Для получения сервиса из DI-контейнера можно использовать метод `getService()` с именем сервиса в качестве параметра или метод `getByType()` с типом сервиса:

```php
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
```


Создание сервиса .[#toc-service-creation]
=========================================

Чаще всего мы создаем сервис, просто инстанцируя определенный класс. Например:

```neon
services:
	database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
```

Если нам необходимо расширить конфигурацию дополнительными ключами, то определение может быть разложено на несколько строк:

```neon
services:
	database:
		create: PDO('sqlite::memory:')
		setup: ...
```

Ключ `create` имеет псевдоним `factory`, оба варианта распространены на практике. Однако мы рекомендуем использовать `create`.

В качестве альтернативы аргументы конструктора или метод создания могут быть записаны в ключе `arguments`:

```neon
services:
	database:
		create: PDO
		arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
```

Сервисы не обязательно должны создаваться только простым инстанцированием класса; они также могут возникать в результате вызова статических методов или методов других сервисов:

```neon
services:
	database: DatabaseFactory::create()
	router: @routerFactory::create()
```

Заметим, что для простоты вместо `->`, мы используем `::`, см. [выражения |#expression means]. Эти фабричные методы генерируются:

```php
public function createServiceDatabase(): PDO
{
	return DatabaseFactory::create();
}

public function createServiceRouter(): RouteList
{
	return $this->getService('routerFactory')->create();
}
```

DI-контейнер должен знать тип создаваемого сервиса. Если мы создаем сервис с помощью метода, который не имеет заданного возвращаемого типа, то мы должны явно указать этот тип в конфигурации:

```neon
services:
	database:
		create: DatabaseFactory::create()
		type: PDO
```


Аргументы .[#toc-arguments]
===========================

Передача аргументов конструкторам и методам осуществляется аналогично обычному PHP:

```neon
services:
	database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
```

Для лучшей читабельности мы можем перечислять аргументы в отдельных строках. В этом формате использование запятых необязательно:

```neon
services:
	database: PDO(
		'mysql:host=127.0.0.1;dbname=test'
		root
		secret
	)
```

Можно также назвать аргументы, и тогда можно не заботиться об их порядке:

```neon
services:
	database: PDO(
		username: root
		password: secret
		dsn: 'mysql:host=127.0.0.1;dbname=test'
	)
```

Если вы хотите опустить некоторые аргументы и использовать их значения по умолчанию или вставить функцию с помощью [автоподключения |autowiring], используйте символ подчеркивания:

```neon
services:
	foo: Foo(_, %appDir%)
```

Аргументами могут быть сервисы, параметры и многое другое, см. [средства выражения |#expression means].


Настройка .[#toc-setup]
=======================

В разделе `setup` мы определяем методы, которые должны вызываться при создании сервиса.

```neon
services:
	database:
		create: PDO(%dsn%, %user%, %password%)
		setup:
			- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
```

На языке PHP это выглядит следующим образом:

```php
public function createServiceDatabase(): PDO
{
	$service = new PDO('...', '...', '...');
	$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	return $service;
}
```

Помимо вызовов методов, можно передавать значения в свойства. Добавление элемента в массив также поддерживается, но его необходимо заключать в кавычки, чтобы избежать столкновения с синтаксисом NEON:

```neon
services:
	foo:
		create: Foo
		setup:
			- $value = 123
			- '$onClick[]' = [@bar, clickHandler]
```

В PHP это будет выглядеть так:

```php
public function createServiceFoo(): Foo
{
	$service = new Foo;
	$service->value = 123;
	$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
	return $service;
}
```

В настройке можно также вызывать статические методы или методы других сервисов. Если необходимо передать текущий сервис в качестве аргумента, используйте `@self`:

```neon
services:
	foo:
		create: Foo
		setup:
			- My\Helpers::initializeFoo(@self)
			- @anotherService::setFoo(@self)
```

Обратите внимание, что для простоты вместо `->` мы используем `::`, см. [средства выражения |#expression means]. В результате формируется следующий фабричный метод:

```php
public function createServiceFoo(): Foo
{
	$service = new Foo;
	My\Helpers::initializeFoo($service);
	$this->getService('anotherService')->setFoo($service);
	return $service;
}
```


Средства выражения .[#toc-expression-means]
===========================================

Nette DI предоставляет нам исключительно богатые возможности выражения, позволяющие сформулировать практически все, что угодно. В конфигурационных файлах мы можем использовать [параметры |configuration#parameters]:

```neon
# параметр
%wwwDir%

# значение под ключом параметра
%mailer.user%

# параметр в строке
'%wwwDir%/images'
```

Мы также можем создавать объекты, вызывать методы и функции:

```neon
# создать объект
DateTime()

# вызов статического метода
Collator::create(%locale%)

# вызвать функцию PHP
::getenv(DB_USER)
```

Ссылайтесь на сервисы либо по их имени, либо по типу:

```neon
# услуга по названию
@database

# услуга по типу
@Nette\Database\Connection
```

Используйте синтаксис первоклассных вызываемых элементов: .{data-version:3.2.0}

```neon
# creating a callback, equivalent to [@user, logout]
@user::logout(...)
```

Использовать константы:

```neon
# константа класса
FilesystemIterator::SKIP_DOTS

# глобальная константа, получаемая с помощью PHP-функции constant()
::constant(PHP_VERSION)
```

Вызовы методов, как и в PHP, можно объединять в цепочки. Для простоты вместо `->` мы используем `::`:

```neon
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')

@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
```

Эти выражения можно использовать в любом месте при [создании сервисов |#Service Creation], в [аргументах |#Arguments], в секции [настройки |#setup] или [параметрах |configuration#parameters]:

```neon
parameters:
	ipAddress: @http.request::getRemoteAddress()

services:
	database:
		create: DatabaseFactory::create( @anotherService::getDsn() )
		setup:
			- initialize( ::getenv('DB_USER') )
```


Специальные функции .[#toc-special-functions]
---------------------------------------------

В конфигурационных файлах можно использовать эти специальные функции:

- `not()` для отрицания значений
- `bool()`, `int()`, `float()`, `string()` для приведения типов без потерь
- `typed()` для создания массива всех сервисов заданного типа
- `tagged()` для создания массива всех сервисов с заданным тегом

```neon
services:
	- Foo(
		id: int(::getenv('ProjectId'))
		productionMode: not(%debugMode%)
	)
```

По сравнению с обычным приведением типов в PHP, например, `(int)`, при приведении типов без потерь будет возникать исключение для нечисловых значений.

Функция `typed()` создает массив всех сервисов определенного типа (класса или интерфейса). При этом исключаются сервисы с выключенным автоподключением. Можно указать несколько типов, разделяя их запятыми.

```neon
services:
	- BarsDependent( typed(Bar) )
```

Также можно автоматически передать массив сервисов определенного типа в качестве аргумента при использовании [автоподключения |autowiring#Collection of Services].

Функция `tagged()` создает массив всех сервисов с указанным тегом. Можно перечислить несколько тегов, разделяя их запятыми.

```neon
services:
	- LoggersDependent( tagged(logger) )
```


Автоэлектрика .[#toc-autowiring]
================================

Ключ `autowired` позволяет модифицировать поведение автоподключения для конкретного сервиса. Более подробная информация приведена в [главе, посвященной автоподключению |autowiring].

```neon
services:
	foo:
		create: Foo
		autowired: false     # служба foo исключена из автоподключения
```


Теги .[#toc-tags]
=================

Теги используются для добавления дополнительной информации к сервисам. Вы можете назначить сервису один или несколько тегов:

```neon
services:
	foo:
		create: Foo
		tags:
			- cached
```

Теги также могут иметь значения:

```neon
services:
	foo:
		create: Foo
		tags:
			logger: monolog.logger.event
```

Чтобы получить все услуги с определенными тегами, можно воспользоваться функцией `tagged()`:

```neon
services:
	- LoggersDependent( tagged(logger) )
```

В контейнере DI можно получить имена всех сервисов с определенным тегом с помощью метода `findByTag()`:

```php
$names = $container->findByTag('logger');
// $names - массив, содержащий имя сервиса и значение тега
// например, ['foo' => 'monolog.logger.event', ...].
```


Режим инжекции .[#toc-inject-mode]
==================================

Использование флага `inject: true` активизирует передачу зависимостей через публичные переменные с помощью аннотации [inject |best-practices:inject-method-attribute#Inject Attributes] и методов [inject*() |best-practices:inject-method-attribute#inject Methods].

```neon
services:
	articles:
		create: App\Model\Articles
		inject: true
```

По умолчанию `inject` активизируется только для ведущих.


Модификации сервиса .[#toc-service-modifications]
=================================================

Контейнер DI содержит множество сервисов, добавленных как встроенными, так и [пользовательскими расширениями |#extensions]. Определения этих сервисов можно изменять непосредственно в конфигурации. Например, можно изменить класс сервиса `application.application`, который условно называется `Nette\Application\Application`, на другой:

```neon
services:
	application.application:
		create: MyApplication
		alteration: true
```

Флаг `alteration` является информативным и указывает на то, что мы просто модифицируем существующий сервис.

Мы также можем дополнить настройку:

```neon
services:
	application.application:
		create: MyApplication
		alteration: true
		setup:
			- '$onStartup[]' = [@resource, init]
```

При перезаписи сервиса может потребоваться удалить исходные аргументы, элементы настройки или теги, и здесь пригодится `reset`:

```neon
services:
	application.application:
		create: MyApplication
		alteration: true
		reset:
			- arguments
			- setup
			- tags
```

Если необходимо удалить сервис, добавленный расширением, это можно сделать следующим образом:

```neon
services:
	cache.journal: false
```

Определения сервисов

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