Згенеровані фабрики
Nette DI може автоматично генерувати код фабрик на основі інтерфейсу, що позбавляє вас від написання коду.
Фабрика – це клас, який створює та налаштовує об'єкти. Тому він також передає їм їхні залежності. Будь ласка, не плутайте з патерном проектування factory method, який описує специфічний спосіб використання фабрик і не має відношення до цієї теми.
Ми показали, як виглядає така фабрика у вступному розділі:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Усе, що вам потрібно зробити, це створити інтерфейс, а Nette DI згенерує
його реалізацію. Інтерфейс повинен мати рівно один метод з ім'ям
create
і оголошувати тип, що повертається:
interface ArticleFactory
{
function create(): Article;
}
Отже, фабрика ArticleFactory
має метод create
, який створює
об'єкти Article
. Клас Article
може виглядати, наприклад,
таким чином:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Додайте фабрику до файлу конфігурації:
services:
- ArticleFactory
Nette DI створить відповідну реалізацію фабрики.
Таким чином, у коді, що використовує фабрику, ми запитуємо об'єкт за інтерфейсом, а Nette DI використовує згенеровану реалізацію:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// дозволити фабриці створити об'єкт
$article = $this->articleFactory->create();
}
}
Параметризована фабрика
Метод фабрики create
може приймати параметри, які він потім
передає конструктору. Наприклад, давайте додамо ID автора статті в клас
Article
:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
Ми також додамо параметр до фабрики:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Оскільки параметр у конструкторі та параметр у фабриці мають однакове ім'я, Nette DI передасть їх автоматично.
Розширене визначення
Визначення також може бути записано в багаторядковій формі за
допомогою ключа implement
:
services:
articleFactory:
implement: ArticleFactory
При написанні таким подовженим способом можна надати додаткові
аргументи для конструктора в ключі arguments
і додаткову
конфігурацію за допомогою setup
, як і для звичайних сервісів.
Приклад: Якби метод create()
не приймав параметр $authorId
, ми
могли б вказати в конфігурації фіксоване значення, яке передавалося б
у конструктор Article
:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Або, навпаки, якби create()
приймав параметр $authorId
, але він
не був частиною конструктора і був переданий методом
Article::setAuthorId()
, ми б звернулися до нього в секції setup
:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Аксесор
Крім фабрик, Nette також може генерувати так звані аксесори. Це об'єкти з
методом get()
, який повертає конкретний сервіс з DI-контейнера.
Повторні виклики get()
як і раніше повертають один і той самий
екземпляр.
Аксесор забезпечує ледаче завантаження залежностей. Нехай у нас є
клас, який записує помилки в спеціальну базу даних. Якби в цьому класі
з'єднання з базою даних передавалося конструктором як залежність, то
з'єднання доводилося б створювати завжди, хоча на практиці помилка
виникає дуже рідко, і тому з'єднання зазвичай залишалося б
невикористаним. Замість цього клас передає метод доступу, і тільки при
виклику його get()
створюється об'єкт бази даних:
Як створити аксесор? Просто напишіть інтерфейс, і Nette DI згенерує
реалізацію. Інтерфейс повинен мати рівно один метод з ім'ям get
і
оголосити тип, що повертається:
interface PDOAccessor
{
function get(): PDO;
}
Ми додамо аксесор у файл конфігурації, який також містить визначення сервісу, який він поверне:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Оскільки метод доступу повертає службу PDO
, а в конфігурації є
тільки один такий сервіс, він поверне його. Якщо сервісів цього типу
більше, ми визначаємо сервіс, що повертається, за іменем, наприклад
- PDOAccessor(@db1)
.
Кілька фабрик/аксесорів
Досі наші фабрики та аксесори завжди могли виробляти або повертати
лише один об'єкт. Однак дуже легко створити кілька фабрик у поєднанні з
аксесорами. Інтерфейс такого класу міститиме будь-яку кількість
методів з іменами create<name>()
и get<name>()
, наприклад:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Тому замість того, щоб передавати кілька згенерованих фабрик і аксесорів, ми збираємося передати ще одну складну фабрику, яка може робити більше.
Крім того, ви можете використовувати get()
з параметром замість
декількох методів:
interface MultiFactoryAlt
{
function get($name): PDO;
}
У цьому випадку MultiFactory::getArticle()
робить те ж саме, що і
MultiFactoryAlt::get('article')
. Однак альтернативний синтаксис має кілька
недоліків. Незрозуміло, які значення $name
підтримуються, і
неможливо вказати тип повернення в інтерфейсі при використанні
декількох різних значень $name
.
Визначення списку
Цей спосіб можна використовувати для визначення декількох фабрик у конфігурації:
services:
- MultiFactory(
article: Article # defines createArticle()
db: PDO(%dsn%, %user%, %password%) # defines getDb()
)
Або у визначенні фабрики ми можемо посилатися на існуючі сервіси за допомогою посилання:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # defines createArticle()
db: @\PDO # defines getDb()
)
Визначення з використанням тегів
Другий варіант – використовувати теги для визначення:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.context
)