Дефиниране на сървиси
Конфигурацията е мястото, където учим 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 е техника, която отлага създаването на сървис до момента, в който той действително е необходим. В глобалната конфигурация може да се активиране на lazy създаване за всички сървиси наведнъж. За отделни сървиси след това можете да презапишете това поведение:
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