Generowane fabryki
Nette DI potrafi automatycznie wygenerować kod fabryki na podstawie interfejsów, oszczędzając Ci pisania kodu.
Fabryka to klasa odpowiedzialna za tworzenie obiektów i przekazywanie ich zależności. Nie myl tego ze wzorcem projektowym factory method, który opisuje konkretny sposób używania fabryk i nie ma z tym tematem związku.
Jak taka fabryka wygląda, 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 potrafi automatycznie wygenerować kod fabryki. Wystarczy, że utworzysz interfejs, a Nette DI wygeneruje
implementację. Interfejs musi mieć dokładnie jedną metodę o nazwie create i deklarować typ zwracany:
interface ArticleFactory
{
function create(): Article;
}
Fabryka ArticleFactory ma więc 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 odpowiadającą jej implementację fabryki.
W kodzie używającym fabryki poproś o obiekt przez jego interfejs, a Nette DI dostarczy wygenerowaną implementację:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// pozwalamy fabryce utworzyć obiekt
$article = $this->articleFactory->create();
}
}
Fabryka z parametrami
Metoda fabryczna create może przyjmować parametry, które następnie przekazuje do konstruktora. Dodajmy na
przykład do klasy Article ID autora artykułu:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
Parametr dodamy również do fabryki:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Ponieważ nazwa parametru w konstruktorze ($authorId) zgadza się z nazwą parametru w metodzie fabrycznej, Nette
DI przekazuje go automatycznie.
Definicja zaawansowana
Definicję można zapisać również w postaci wielowierszowej, używając klucza implement:
services:
articleFactory:
implement: ArticleFactory
Użycie tej dłuższej postaci pozwala podać dodatkowe argumenty konstruktora kluczem arguments i dalszą
konfigurację przez setup, podobnie jak przy zwykłych definicjach usług.
Przykład: gdyby metoda create() nie przyjmowała parametru $authorId, moglibyśmy podać w
konfiguracji stałą wartość, która zostanie przekazana do konstruktora Article:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Odwrotnie, gdyby create() przyjmowało $authorId, ale nie byłby on częścią konstruktora, tylko
przekazywany metodą w rodzaju Article::setAuthorId(), odwołalibyśmy się do parametru w sekcji
setup:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Akcesor
Poza fabrykami Nette potrafi generować również tak zwane akcesory. To obiekty z metodą get(), która zwraca
konkretną usługę z kontenera DI. Powtarzane wywołania get() zawsze zwracają tę samą instancję.
Akcesory zapewniają leniwe ładowanie zależności. Rozważ klasę, która loguje błędy do dedykowanej bazy danych. Gdyby
klasa ta otrzymywała połączenie z bazą danych przez wstrzykiwanie w konstruktorze, połączenie nawiązywałoby się zawsze,
nawet jeśli błędy występują rzadko, a połączenie przez większość czasu pozostaje niewykorzystane. Zamiast tego klasa
może otrzymać akcesor. Obiekt bazy danych (połączenie) tworzony jest dopiero wtedy, gdy metoda get() akcesora
zostanie wywołana po raz pierwszy.
Jak utworzyć akcesor? Wystarczy napisać interfejs, a Nette DI wygeneruje implementację. Interfejs musi mieć dokładnie
jedną metodę o nazwie get, która nie przyjmuje parametrów i deklaruje typ zwracany:
interface PDOAccessor
{
function get(): PDO;
}
Dodaj akcesor do pliku konfiguracyjnego wraz z definicją usługi, którą ma zwracać:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Ponieważ akcesor zwraca usługę PDO, a w konfiguracji zdefiniowana jest tylko jedna taka usługa, akcesor
zwróci właśnie ją. Jeśli usług tego typu jest więcej, podaj po nazwie, którą akcesor ma zwracać, np.
- PDOAccessor(@db1).
Multifabryka/akcesor
Jak dotąd nasze fabryki i akcesory potrafiły tworzyć albo zwracać tylko jeden typ obiektu. Możesz jednak łatwo utworzyć
multifabryki, które łączą cechy fabryk i akcesorów. Interfejs takiego komponentu może zawierać wiele metod o nazwach
create<Nazwa>() i get<Nazwa>(), na przykład:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Zamiast wstrzykiwać wiele osobnych fabryk i akcesorów, możesz więc wstrzyknąć jeden, bardziej kompleksowy komponent.
Alternatywnie zamiast wielu metod można użyć get() z parametrem:
interface MultiFactoryAlt
{
function get($name): PDO;
}
MultiFactory::getDb() robi wtedy to samo co MultiFactoryAlt::get('db'). Ten alternatywny zapis ma
jednak tę wadę, że obsługiwane wartości $name nie wynikają jawnie z sygnatury interfejsu. Ponadto nie da się
zdefiniować w interfejsie różnych typów zwracanych dla różnych wartości $name.
Zamiast get($name) interfejs może deklarować create($name), który przy każdym wywołaniu zwraca
nową instancję (podczas gdy get() zwraca współdzieloną). Interfejs może zawierać tylko jedną taką metodę
z parametrem. Jeśli typ zwracany metody jest nullable (np. ?PDO), dla nieznanego $name zwraca
null zamiast zgłaszać wyjątek.
Definicja z listą
Multifabrykę możesz zdefiniować w konfiguracji za pomocą listy, z usługami zapisanymi w linii:
services:
- MultiFactory(
article: Article() # definiuje createArticle()
db: PDO(%dsn%, %user%, %password%) # definiuje getDb()
)
Alternatywnie możesz odwołać się w definicji multifabryki do istniejących usług za pomocą referencji:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # definiuje createArticle()
db: @\PDO # definiuje getDb()
)
Definicja z tagami
Innym sposobem zdefiniowania multifabryki jest użycie tagów. Wartość tagu określa nazwę odpowiadającej metody:
services:
article:
create: Article
tags: {multi: article} # definiuje createArticle()
db:
create: PDO(%dsn%, %user%, %password%)
tags: {multi: db} # definiuje getDb()
- MultiFactory(tagged: multi)