Автокомуникация
Autowiring или autobinding е чудесна функция, която може автоматично да предава услуги на конструктора и други методи, така че да не е необходимо да ги пишем изобщо. Това ще ви спести много време.
Това ни позволява да пропуснем по-голямата част от аргументите при писането на дефиниции на услуги. Вместо:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Просто напишете:
services:
articles: Model\ArticleRepository
Автоматичното свързване се контролира от типовете, така че класът
ArticleRepository
трябва да бъде дефиниран по следния начин:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
За да се използва автоматично обвързване, контейнерът трябва да има само една услуга за всеки тип. Ако те са повече, автоматичното свързване няма да знае коя от тях да предаде и ще направи изключение:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # ВЫБРАСЫВАЕТСЯ ИСКЛЮЧЕНИЕ, mainDb и tempDb совпадают
Решението може да бъде или да се заобиколи автоматичната
комуникация, или изрично да се посочи името на услугата (т.е.
articles: Model\ArticleRepository(@mainDb)
). Въпреки това е по-удобно да деактивирате автоматичното свързване на една
услуга или да предпочетете конкретна услуга.
Деактивирано автоматично свързване
Можете да деактивирате автоматичното откриване на зависимостите на
услугите, като използвате параметъра autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # премахва tempDb от автоматичното обвързване
articles: Model\ArticleRepository # предава mainDb на конструктора
Услугата articles
не хвърля изключение, че има две свързани услуги
от тип PDO
(т.е. mainDb
и tempDb
), които могат да бъдат
предадени на конструктора, тъй като той вижда само услугата
mainDb
.
Конфигурацията за автоматично свързване работи по различен
начин в Nette, отколкото в Symfony, където опцията autowire: false
казва, че
автоматичното свързване не трябва да се използва за аргументите на
конструктора на услугата. В Nette винаги се използва автоматично
свързване, независимо дали става въпрос за аргументи на конструктора
или за друг метод. Опцията autowired: false
указва, че екземплярът на
услугата не трябва да се предава никъде с помощта на автоматично
свързване.
Предпочитаното автоматично окабеляване
Ако разполагаме с няколко услуги от един и същи вид и една от тях има
опция autowired
, тази услуга става предпочитана:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # прави го предпочитан
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
Услугата articles
няма да хвърли изключение, ако има две съвпадащи
услуги PDO
(т.е. mainDb
и tempDb
), а ще използва
предпочитаната услуга, т.е. mainDb
.
Събиране на услуги
Автоматичното свързване може също така да предава масив от услуги от
определен тип. Тъй като PHP не може да обозначава типа на елементите на
масива, в допълнение към типа array
трябва да се добави phpDoc
коментар с типа на елемента, например ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
След това контейнерът DI автоматично ще предаде масив от услуги, отговарящи на зададения тип. Това ще доведе до пропускане на услуги с деактивирано автоматично свързване.
Типът в коментара може да бъде и под формата array<int, Class>
или
list<Class>
. Ако не можете да контролирате формата на phpDoc
коментара, можете да подадете масив от услуги директно в
конфигурацията, като използвате typed()
.
Скаларни аргументи
Автоматичното свързване може да предава само обекти и масиви от обекти. Скаларните аргументи (напр. низове, числа, булеви стойности) се записват в конфигурацията. Алтернативата е да се създаде обект за настройки, който капсулира скаларна стойност (или няколко стойности) като обект, който след това може да бъде предаден отново с помощта на автоматично свързване.
class MySettings
{
public function __construct(
// readonly може да се използва от PHP 8.1
public readonly bool $value,
)
{}
}
Създавате услуга, като я добавяте в конфигурацията:
services:
- MySettings('любое значение')
След това всички класове ще го заявят чрез автоматично свързване.
Стесняване на автоматичното окабеляване
За отделни услуги автоматичното свързване може да бъде ограничено до конкретни класове или интерфейси.
Обикновено автоматичното свързване предава функция на всеки параметър на метода, на чийто тип съответства функцията. Стесняването означава, че посочваме условията, на които трябва да отговарят типовете, посочени за параметрите на метода, за да може функцията да им бъде предадена.
Нека разгледаме един пример:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Ако ги регистрираме всички като услуги, автоматичното свързване няма да е възможно:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # ИЗКЛЮЧЕНИЕ, родител и дете са едно и също
childDep: ChildDependent # предава услугата 'child' на конструктора
Услугата parentDep
хвърля изключение
Multiple services of type ParentClass found: parent, child
, тъй като и parent
, и
child
са поставени в нейния конструктор и автоматичното свързване
не може да реши коя от тях да избере.
Така за услугата child
можем да стесним нейното автоматично
свързване до ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # алтернатива: 'autowired: self'
parentDep: ParentDependent # ИЗКЛЮЧЕНИЕ, 'child' не може да бъде автоматично свързан
childDep: ChildDependent # предава услугата 'child' на конструктора
Услугата parentDep
сега се предава на конструктора на услугата
parentDep
, тъй като сега тя е единственият подходящ обект. Услугата
child
вече не е автоматично обвързана. Да, функцията child
все
още е от тип ParentClass
, но условието за стесняване, зададено за типа
на параметъра, вече не важи, т.е. вече не е вярно, че ParentClass
е
надтип на ChildClass
.
В случая на child
, autowired: ChildClass
може да се запише като
autowired: self
, тъй като self
означава текущия тип услуга.
Ключът autowired
може да включва няколко класа и интерфейса
като масив:
autowired: [BarClass, FooInterface]
Нека се опитаме да добавим интерфейси към примера:
interface FooInterface
{}
interface BarInterface
{}
class ParentClass implements FooInterface
{}
class ChildClass extends ParentClass implements BarInterface
{}
class FooDependent
{
function __construct(FooInterface $obj)
{}
}
class BarDependent
{
function __construct(BarInterface $obj)
{}
}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Ако не ограничим услугата до child
, тя ще съвпадне с
конструкторите на всички класове FooDependent
, BarDependent
,
ParentDependent
и ChildDependent
, а автоматичното свързване ще я
предаде там.
Ако обаче стесним автосвързването до ChildClass
с
autowired: ChildClass
(или self
), автосвързването ще го предаде само
на конструктора ChildDependent
, тъй като той изисква аргумент от тип
ChildClass
, а ChildClass
е тип ChildClass
. Нито един друг тип,
посочен за другите параметри, не е заместител на ChildClass
, поради
което услугата не успява.
Ако го ограничим до ParentClass
с autowired: ParentClass
, тогава
автосвързването ще го предаде отново на конструктора ChildDependent
(защото изискваният тип ChildClass
е супермножество на ParentClass
)
и на конструктора ParentDependent
, защото изискваният тип ParentClass
също съвпада.
Ако го ограничим до FooInterface
, той все още ще се свързва с
ParentDependent
(необходимият тип ParentClass
е надтип на
FooInterface
) и ChildDependent
, но освен това и с конструктора
FooDependent
, но не и с BarDependent
, тъй като BarInterface
не е
надтип на FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # предава подчинената услуга на конструктора
barDep: BarDependent # предава подчинената услуга на конструктора
parentDep: ParentDependent # Предава подчинената услуга на конструктора
childDep: ChildDependent # предава услугата на детето на конструктора