Прехвърляне на зависимостта
Аргументите или „зависимостите“ в терминологията на DI могат да се предават на класовете по следните основни начини:
- предаване чрез конструктор
- предаване по метод (нарича се setter)
- чрез задаване на свойство
- чрез метод, анотация или атрибут *inject
Сега ще илюстрираме различните варианти с конкретни примери.
Внедряване чрез конструктор
Зависимостите се предават като аргументи на конструктора при създаването на обекта:
Тази форма е полезна за задължителни зависимости, които са абсолютно необходими за функционирането на класа, тъй като без тях не може да се създаде инстанция.
От версия 8.0 на PHP можем да използваме по-кратка форма на запис (constructor property promotion), която е функционално еквивалентна:
От PHP 8.1 насам дадено свойство може да бъде маркирано с флага
readonly
, който декларира, че съдържанието на свойството няма да
бъде променяно:
Контейнерът DI предава зависимостите на конструктора автоматично, като използва автоматично свързване. Аргументите, които не могат да бъдат предадени по този начин (напр. низове, числа, булеви стойности), се записват в конфигурацията.
Адът на конструкторите
Терминът ад на конструкторите се отнася до ситуация, при която наследник наследява родителски клас, чийто конструктор изисква зависимости, и наследникът също изисква зависимости. То също трябва да поеме и предаде зависимостите на родителя:
Проблемът възниква, когато искаме да променим конструктора на класа
BaseClass
, например когато се добави нова зависимост. Тогава трябва
да променим и всички конструктори на децата. Което превръща подобна
модификация в ад.
Как да предотвратим това? Решението е да се даде предимство на композицията пред наследяването**.
Затова нека да проектираме кода по различен начин. Ще избягваме абстрактните
класове Base*
. Вместо MyClass
да получи някаква
функционалност, наследявайки я от BaseClass
, тя ще бъде предадена
като зависимост:
Зависимости чрез задаващи елементи
Зависимостите се предават чрез извикване на метод, който ги
съхранява в частни свойства. Обичайната конвенция за именуване на тези
методи е от вида set*()
, поради което те се наричат сетъри, но,
разбира се, могат да се наричат и по друг начин.
Този метод е полезен за незадължителни зависимости, които не са необходими за функционирането на класа, тъй като не е гарантирано, че обектът действително ще ги получи (т.е. че потребителят ще извика метода).
В същото време този метод позволява многократно извикване на setter за
промяна на зависимостта. Ако това не е желателно, добавете проверка към
метода или, от версия PHP 8.1, маркирайте свойството $cache
с флага
readonly
.
Извикването на setter се дефинира в конфигурацията на контейнера DI в раздела за настройка. И тук автоматичното предаване на зависимостите се използва чрез autowiring:
Изпълнение чрез свойства
Зависимостите се предават директно на свойството:
Този метод се счита за неприемлив, тъй като свойството трябва да бъде
декларирано като public
. Следователно нямаме контрол върху това
дали предадената зависимост действително има зададения тип (това беше
вярно преди PHP 7.4) и губим възможността да реагираме на новоназначената
зависимост със собствен код, например за да предотвратим последващи
промени. В същото време свойството става част от публичния интерфейс
на класа, което може да е нежелателно.
Настройката на променливата се дефинира в конфигурацията на контейнера DI в раздел настройка:
Инжектиране
Докато предишните три метода са общовалидни във всички обектно-ориентирани езици, инжектирането чрез метод, анотация или атрибут inject е специфично за презентаторите на Nette. Те са разгледани в отделна глава.
Кой път да избера?
- Конструкторът е подходящ за задължителни зависимости, от които класът се нуждае, за да функционира.
- сетърът, от друга страна, е подходящ за незадължителни зависимости или за зависимости, които могат да бъдат променяни.
- публичните променливи не се препоръчват