Generierte Fabriken
Nette DI kann automatisch Code für Fabriken basierend auf Schnittstellen generieren, was Ihnen das Schreiben von Code erspart.
Eine Fabrik ist eine Klasse, die Objekte herstellt und konfiguriert. Sie übergibt ihnen also auch ihre Abhängigkeiten. Bitte verwechseln Sie dies nicht mit dem Entwurfsmuster Factory Method, das eine spezifische Art der Verwendung von Fabriken beschreibt und mit diesem Thema nichts zu tun hat.
Wie eine solche Fabrik aussieht, haben wir im Einführungskapitel gezeigt:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Nette DI kann den Code von Fabriken automatisch generieren. Alles, was Sie tun müssen, ist, eine Schnittstelle zu erstellen,
und Nette DI generiert die Implementierung. 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 Article
-Objekte erstellt.
Die Klasse Article
könnte beispielsweise so aussehen:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Wir fügen die Fabrik zur Konfigurationsdatei hinzu:
services:
- ArticleFactory
Nette DI generiert die entsprechende Implementierung der Fabrik.
Im Code, der die Fabrik verwendet, fordern wir das Objekt über die Schnittstelle an, und Nette DI verwendet die generierte Implementierung:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// lassen wir die Fabrik das Objekt erstellen
$article = $this->articleFactory->create();
}
}
Parametrisierte Fabrik
Die Fabrikmethode create
kann Parameter annehmen, die sie dann an den Konstruktor weitergibt. Ergänzen wir
beispielsweise die Klasse Article
um die ID des Artikelautors:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
Wir fügen den Parameter auch zur Fabrik hinzu:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Da der Parameter im Konstruktor und der Parameter in der Fabrik denselben Namen haben, übergibt Nette DI sie vollautomatisch.
Erweiterte Definition
Die Definition kann auch in mehrzeiliger Form unter Verwendung des Schlüssels implement
geschrieben werden:
services:
articleFactory:
implement: ArticleFactory
Bei dieser längeren Schreibweise können zusätzliche Argumente für den Konstruktor im Schlüssel arguments
und
zusätzliche Konfigurationen mittels setup
angegeben werden, genau wie bei regulären Diensten.
Beispiel: Wenn die Methode create()
den Parameter $authorId
nicht akzeptieren würde, könnten wir
einen festen Wert in der Konfiguration angeben, der an den Konstruktor von Article
übergeben würde:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Oder umgekehrt, wenn create()
den Parameter $authorId
akzeptieren würde, aber er nicht Teil des
Konstruktors wäre und über die Methode Article::setAuthorId()
übergeben würde, würden wir im Abschnitt
setup
darauf verweisen:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
Nette kann neben Fabriken auch sogenannte Accessoren generieren. Dies sind Objekte mit einer get()
-Methode, die
einen bestimmten Dienst aus dem DI-Container zurückgibt. Wiederholte Aufrufe von get()
geben immer dieselbe Instanz
zurück.
Accessoren ermöglichen Lazy-Loading für Abhängigkeiten. Nehmen wir an, wir haben eine Klasse, die Fehler in eine spezielle
Datenbank schreibt. Wenn diese Klasse die Datenbankverbindung als Abhängigkeit im Konstruktor übergeben bekäme, müsste die
Verbindung immer erstellt werden, obwohl in der Praxis ein Fehler nur selten auftritt und die Verbindung daher meist ungenutzt
bliebe. Stattdessen übergibt sich die Klasse einen Accessor, und erst wenn dessen get()
aufgerufen wird, wird das
Datenbankobjekt erstellt:
Wie erstellt man einen Accessor? Schreiben Sie einfach eine Schnittstelle, und Nette DI generiert die Implementierung. Die
Schnittstelle muss genau eine Methode namens get
haben und einen Rückgabetyp deklarieren:
interface PDOAccessor
{
function get(): PDO;
}
Wir fügen den Accessor zur Konfigurationsdatei hinzu, wo auch die Definition des Dienstes steht, den er zurückgeben wird:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Da der Accessor einen Dienst vom Typ PDO
zurückgibt und in der Konfiguration nur ein solcher Dienst vorhanden
ist, wird er genau diesen zurückgeben. Wenn es mehrere Dienste dieses Typs gäbe, würden wir den zurückgegebenen Dienst anhand
seines Namens bestimmen, z. B. - PDOAccessor(@db1)
.
Mehrfachfabrik/-accessor
Unsere Fabriken und Accessoren konnten bisher immer nur ein Objekt herstellen oder zurückgeben. Es ist jedoch sehr einfach,
auch Mehrfachfabriken in Kombination mit Accessoren zu erstellen. Die Schnittstelle einer solchen Klasse enthält eine beliebige
Anzahl von Methoden mit den Namen create<name>()
und get<name>()
, z. B.:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Anstatt also mehrere generierte Fabriken und Accessoren zu übergeben, übergeben wir eine komplexere Fabrik, die mehr kann.
Alternativ kann anstelle mehrerer Methoden get()
mit einem Parameter verwendet werden:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Dann gilt, dass MultiFactory::getArticle()
dasselbe tut wie MultiFactoryAlt::get('article')
. Die
alternative Schreibweise hat jedoch den Nachteil, dass nicht ersichtlich ist, welche Werte für $name
unterstützt
werden, und logischerweise können in der Schnittstelle auch keine unterschiedlichen Rückgabewerte für verschiedene
$name
unterschieden werden.
Definition durch Liste
Auf diese Weise kann eine Mehrfachfabrik in der Konfiguration definiert werden:
services:
- MultiFactory(
article: Article # definiert createArticle()
db: PDO(%dsn%, %user%, %password%) # definiert getDb()
)
Oder wir können uns in der Fabrikdefinition mittels Referenz auf bestehende Dienste beziehen:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # definiert createArticle()
db: @\PDO # definiert getDb()
)
Definition mittels Tags
Die zweite Möglichkeit ist, zur Definition Tags zu verwenden:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)