Wytworzone fabryki
Nette DI może automatycznie generować kod fabryczny na podstawie interfejsu, oszczędzając na pisaniu kodu.
Fabryka to klasa, która tworzy i konfiguruje obiekty. W związku z tym przekazuje im również ich zależności. Proszę nie mylić z wzorcem projektowym factory method, który opisuje specyficzny sposób używania fabryk i nie jest związany z tym tematem.
Jak wygląda taka fabryka, pokazaliśmy w rozdziale wprowadzającym:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Nette DI może automatycznie wygenerować kod fabryczny. Wystarczy, że stworzysz interfejs, a Nette DI wygeneruje jego
implementację. Interfejs musi mieć dokładnie jedną metodę o nazwie create
i deklarować typ zwrotny:
interface ArticleFactory
{
function create(): Article;
}
Tak więc fabryka ArticleFactory
ma metodę create
, która tworzy obiekty Article
. Klasa
Article
może wyglądać na przykład tak:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Dodaj fabrykę do pliku konfiguracyjnego:
services:
- ArticleFactory
Nette DI wygeneruje odpowiednią implementację fabryczną.
W kodzie, który korzysta z fabryki, żądamy obiektu przez interfejs, a Nette DI używa wygenerowanej implementacji:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// pozwól fabryce stworzyć obiekt
$article = $this->articleFactory->create();
}
}
Fabryka z parametrami
Metoda fabryczna create
może otrzymywać parametry, które następnie przekazuje do konstruktora. Na przykład
uzupełnijmy klasę Article
o ID autora artykułu:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
Dodamy również parametr do fabryki:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Ponieważ parametr w konstruktorze i parametr w fabryce mają tę samą nazwę, Nette DI przekazuje je automatycznie.
Zaawansowana definicja
Definicja może być również zapisana w formie wieloliniowej za pomocą klawisza implement
:
services:
articleFactory:
implement: ArticleFactory
Pisząc w ten dłuższy sposób, można podać dodatkowe argumenty do konstruktora w kluczu arguments
oraz
dodatkową konfigurację za pomocą setup
, tak jak w przypadku zwykłych serwisów.
Przykład: gdyby metoda create()
nie akceptowała parametru $authorId
, moglibyśmy w konfiguracji
określić stałą wartość, która zostałaby przekazana do konstruktora Article
:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Lub odwrotnie, gdyby create()
zaakceptował parametr $authorId
, ale nie był on częścią
konstruktora i został przekazany przez metodę Article::setAuthorId()
, odwołalibyśmy się do niego w sekcji
setup
:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Pomocnik
Nette oprócz fabryk może generować accessory. Są to obiekty z metodą get()
, która zwraca określoną
usługę z kontenera DI. Wielokrotne wywołanie get()
wciąż zwraca tę samą instancję.
Accessory zapewniają leniwe ładowanie zależności. Rozważmy klasę, która zapisuje błędy do specjalnej bazy danych.
Gdyby ta klasa miała połączenie z bazą danych przekazywane jako zależność przez konstruktor, połączenie zawsze
musiałoby zostać nawiązane, choć w praktyce rzadko występowałby błąd, a więc przez większość czasu połączenie
pozostawałoby nieużywane. Więc zamiast tego klasa przekazuje accessor i tylko wtedy, gdy jego get()
jest
wywoływany, obiekt bazy danych zostaje utworzony:
Jak stworzyć accessora? Wystarczy napisać interfejs, a Nette DI wygeneruje jego implementację. Interfejs musi mieć
dokładnie jedną metodę o nazwie get
i deklarować typ zwrotny:
interface PDOAccessor
{
function get(): PDO;
}
Dodaj accessor do pliku konfiguracyjnego, który określa również usługę, którą zwróci:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Ponieważ accessor zwraca usługę typu PDO
i w konfiguracji jest tylko jedna taka usługa, zwróci tę usługę.
Jeśli istnieje więcej usług tego typu, określ usługę, która ma być zwrócona przez nazwę, np.
- PDOAccessor(@db1)
.
Wiele fabryk/akcesoriów
Dotychczas nasze fabryki i accessory były w stanie wyprodukować lub zwrócić tylko jeden obiekt. Jednak bardzo łatwo jest
stworzyć wiele fabryk połączonych z accessorami. Interfejs takiej klasy będzie zawierał dowolną liczbę metod o nazwach
create<name>()
a get<name>()
, np:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Więc zamiast przekazywać kilka wygenerowanych fabryk i accessorów, przekazujemy jedną bardziej złożoną fabrykę, która może zrobić więcej.
Alternatywnie można użyć get()
z parametrem zamiast wielu metod:
interface MultiFactoryAlt
{
function get($name): PDO;
}
W tym przypadku MultiFactory::getArticle()
robi to samo, co MultiFactoryAlt::get('article')
.
Alternatywna składnia ma jednak kilka wad. Nie jest jasne, które wartości $name
są obsługiwane, a typ zwracany
nie może być określony w interfejsie, gdy używanych jest wiele różnych wartości $name
.
Definicja według listy
W ten sposób można zdefiniować wiele fabryk w konfiguracji:
services:
- MultiFactory(
article: Article # defines createArticle()
db: PDO(%dsn%, %user%, %password%) # defines getDb()
)
Lub w definicji fabryki możemy odwołać się do istniejących usług za pomocą referencji:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # defines createArticle()
db: @\PDO # defines getDb()
)
Definicja według znaczników
Druga opcja to użycie tagów do definicji:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)