Generierte Fabriken

Nette DI kann automatisch Fabrikcode basierend auf der Schnittstelle generieren, was Ihnen das Schreiben von Code erspart.

Eine Fabrik ist eine Klasse, die Objekte erstellt und konfiguriert. Sie gibt daher auch deren Abhängigkeiten an sie weiter. Bitte nicht mit dem Entwurfsmuster Fabrikmethode verwechseln, das eine spezielle Art der Verwendung von Fabriken beschreibt und nicht mit diesem Thema zusammenhängt.

Wir haben im Einführungskapitel gezeigt, wie eine solche Fabrik aussieht:

class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}

Nette DI kann automatisch Factory-Code generieren. Alles, was Sie tun müssen, ist, eine Schnittstelle zu erstellen, und Nette DI wird eine Implementierung generieren. Die Schnittstelle muss genau eine Methode namens create haben und einen Rückgabetyp deklarieren:

interface ArticleFactory
{
	function create(): Article;
}

Die Fabrik ArticleFactory hat also eine Methode create, die Objekte Article erzeugt. Die Klasse Article könnte z.B. wie folgt aussehen:

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}
}

Fügen Sie die Fabrik in die Konfigurationsdatei ein:

services:
	- ArticleFactory

Nette DI wird die entsprechende Fabrikimplementierung erzeugen.

Im Code, der die Fabrik verwendet, fordern wir das Objekt also über die Schnittstelle an, und Nette DI verwendet die generierte Implementierung:

class UserController
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function foo()
	{
		// Die Fabrik soll ein Objekt erstellen
		$article = $this->articleFactory->create();
	}
}

Parametrisierte Fabrik

Die Factory-Methode create kann Parameter akzeptieren, die sie dann an den Konstruktor weitergibt. Fügen wir zum Beispiel der Klasse Article eine Artikelautor-ID hinzu:

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
		private int $authorId,
	) {
	}
}

Wir fügen auch den Parameter zur Fabrik hinzu:

interface ArticleFactory
{
	function create(int $authorId): Article;
}

Da der Parameter im Konstruktor und der Parameter in der Fabrik den gleichen Namen haben, werden sie von Nette DI automatisch übergeben.

Erweiterte Definition

Die Definition kann auch in mehrzeiliger Form mit der Taste implement geschrieben werden:

services:
	articleFactory:
		implement: ArticleFactory

Beim Schreiben in dieser längeren Form ist es möglich, zusätzliche Argumente für den Konstruktor im Schlüssel arguments und zusätzliche Konfigurationen mit setup anzugeben, genau wie bei normalen Diensten.

Beispiel: Wenn die Methode create() den Parameter $authorId nicht akzeptiert, könnte man in der Konfiguration einen festen Wert angeben, der an den Konstruktor Article übergeben wird:

services:
	articleFactory:
		implement: ArticleFactory
		arguments:
			authorId: 123

Oder umgekehrt, wenn create() den Parameter $authorId akzeptiert, dieser aber nicht Teil des Konstruktors ist, sondern von der Methode Article::setAuthorId() übergeben wird, würden wir in Abschnitt setup auf ihn verweisen:

services:
	articleFactory:
		implement: ArticleFactory
		setup:
			- setAuthorId($authorId)

Accessor

Neben Fabriken kann Nette auch so genannte Accessoren erzeugen. Ein Accessor ist ein Objekt mit der Methode get(), die einen bestimmten Dienst aus dem DI-Container zurückgibt. Mehrere get() Aufrufe geben immer dieselbe Instanz zurück.

Accessoren bringen Lazy-Loading in Abhängigkeiten. Nehmen wir an, eine Klasse protokolliert Fehler in einer speziellen Datenbank. Wenn die Datenbankverbindung als Abhängigkeit in ihrem Konstruktor übergeben würde, müsste die Verbindung immer erstellt werden, obwohl sie nur selten verwendet würde, wenn ein Fehler auftritt, so dass die Verbindung meist ungenutzt bliebe. Stattdessen kann die Klasse einen Accessor übergeben, und wenn ihre Methode get() aufgerufen wird, wird erst dann das Datenbankobjekt erstellt:

Wie erstellt man einen Accessor? Schreiben Sie nur eine Schnittstelle und Nette DI wird die Implementierung generieren. Die Schnittstelle muss genau eine Methode namens get haben und den Rückgabetyp deklarieren:

interface PDOAccessor
{
	function get(): PDO;
}

Fügen Sie den Accessor in die Konfigurationsdatei ein, zusammen mit der Definition des Dienstes, den der Accessor zurückgibt:

services:
	- PDOAccessor
	- PDO(%dsn%, %user%, %password%)

Der Accessor gibt einen Dienst des Typs PDO zurück, und da es nur einen solchen Dienst in der Konfiguration gibt, gibt der Accessor ihn zurück. Bei mehreren konfigurierten Diensten dieses Typs können Sie angeben, welcher Dienst zurückgegeben werden soll, indem Sie seinen Namen verwenden, zum Beispiel - PDOAccessor(@db1).

Multifactory/Accessor

Bisher konnten die Fabriken und Accessoren nur ein einziges Objekt erstellen oder zurückgeben. Es kann auch eine Multifactory in Kombination mit einem Accessor erstellt werden. Die Schnittstelle einer solchen Multifactory-Klasse kann aus mehreren Methoden bestehen, die create<name>() und get<name>()bestehen, zum Beispiel:

interface MultiFactory
{
	function createArticle(): Article;
	function getDb(): PDO;
}

Anstatt mehrere generierte Fabriken und Accessoren zu übergeben, können Sie nur eine komplexe Multifabrik übergeben.

Alternativ können Sie auch get() mit einem Parameter anstelle von mehreren Methoden verwenden:

interface MultiFactoryAlt
{
	function get($name): PDO;
}

In diesem Fall bewirkt MultiFactory::getArticle() dasselbe wie MultiFactoryAlt::get('article'). Die alternative Syntax hat jedoch einige Nachteile. Es ist nicht klar, welche $name Werte unterstützt werden, und der Rückgabetyp kann nicht in der Schnittstelle angegeben werden, wenn mehrere verschiedene $name Werte verwendet werden.

Definition mit einer Liste

Auf diese Weise können Sie in der Konfiguration eine Mehrfachfabrik definieren:

services:
	- MultiFactory(
		article: Article                      # defines createArticle()
		db: PDO(%dsn%, %user%, %password%)    # defines getDb()
	)

Oder wir können in der Fabrikdefinition auf bestehende Dienste mit einer Referenz verweisen:

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # defines createArticle()
		db: @\PDO            # defines getDb()
	)

Definition mit Tags

Eine weitere Möglichkeit, eine Multifabrik zu definieren, ist die Verwendung von Tags:

services:
	- App\Core\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.explorer
	)
Version: 3.x