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\Router\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.explorer
	)
wersja: 3.x