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
)