Nette Documentation Preview

syntax
Was ist Dependency Injection?
*****************************

.[perex]
In diesem Kapitel werden Sie mit den grundlegenden Programmierpraktiken vertraut gemacht, die Sie beim Schreiben jeder Anwendung befolgen sollten. Dies sind die Grundlagen, die zum Schreiben von sauberem, verständlichem und wartbarem Code erforderlich sind.

Wenn Sie diese Regeln lernen und befolgen, wird Nette Ihnen bei jedem Schritt zur Seite stehen. Es wird Routineaufgaben für Sie erledigen und Ihnen maximalen Komfort bieten, so dass Sie sich auf die eigentliche Logik konzentrieren können.

Die Prinzipien, die wir hier zeigen, sind ganz einfach. Sie brauchen sich um nichts zu kümmern.


Erinnern Sie sich an Ihr erstes Programm? .[#toc-remember-your-first-program]
-----------------------------------------------------------------------------

Wir wissen nicht, in welcher Sprache Sie es geschrieben haben, aber wenn es PHP war, könnte es etwa so aussehen:

```php
function summe(float $a, float $b): float
{
	return $a + $b;
}

echo summe(23, 1); // gibt 24 aus
```

Ein paar triviale Codezeilen, aber so viele wichtige Konzepte, die darin versteckt sind. Dass es Variablen gibt. Dass der Code in kleinere Einheiten unterteilt ist, die zum Beispiel Funktionen sind. Dass wir ihnen Eingabeargumente übergeben und sie Ergebnisse zurückgeben. Alles, was fehlt, sind Bedingungen und Schleifen.

Die Tatsache, dass eine Funktion Eingabedaten entgegennimmt und ein Ergebnis zurückliefert, ist ein durchaus verständliches Konzept, das auch in anderen Bereichen, z. B. der Mathematik, verwendet wird.

Eine Funktion hat ihre Signatur, die aus ihrem Namen, einer Liste von Parametern und deren Typen und schließlich dem Typ des Rückgabewerts besteht. Als Benutzer sind wir an der Signatur interessiert und müssen normalerweise nichts über die interne Implementierung wissen.

Stellen Sie sich nun vor, die Funktionssignatur sähe wie folgt aus:

```php
function summe(float $x): float
```

Ein Zusatz mit einem Parameter? Das ist seltsam... Und was ist damit?

```php
function summe(): float
```

Das ist doch wirklich seltsam, oder? Wie wird die Funktion verwendet?

```php
echo summe(); // was wird gedruckt?
```

Wenn wir uns einen solchen Code ansehen, wären wir verwirrt. Nicht nur ein Anfänger würde ihn nicht verstehen, sondern auch ein erfahrener Programmierer würde einen solchen Code nicht verstehen.

Fragen Sie sich, wie eine solche Funktion eigentlich aussehen würde? Woher würde sie die Summanden bekommen? Sie würde sie wahrscheinlich *irgendwie* selbst beschaffen, vielleicht so:

```php
function summe(): float
{
	$a = Input::get('a');
	$b = Input::get('b');
	return $a + $b;
}
```

Es stellt sich heraus, dass es versteckte Bindungen zu anderen Funktionen (oder statischen Methoden) im Körper der Funktion gibt, und um herauszufinden, woher die Summanden tatsächlich kommen, müssen wir weiter graben.


Nicht hier entlang! .[#toc-not-this-way]
----------------------------------------

Das eben gezeigte Design ist die Essenz vieler negativer Merkmale:

- die Funktionssignatur gibt vor, dass sie die Summanden nicht braucht, was uns verwirrt
- wir haben keine Ahnung, wie wir die Funktion mit zwei anderen Zahlen rechnen lassen können
- wir mussten uns den Code ansehen, um herauszufinden, woher die Summanden kamen
- wir haben versteckte Abhängigkeiten gefunden
- ein vollständiges Verständnis erfordert auch die Untersuchung dieser Abhängigkeiten

Und ist es überhaupt die Aufgabe der Additionsfunktion, Eingaben zu beschaffen? Nein, natürlich nicht.  Ihre Aufgabe ist es nur, zu addieren.


Solchen Code wollen wir nicht sehen, und wir wollen ihn schon gar nicht schreiben. Die Abhilfe ist einfach: Zurück zu den Grundlagen und einfach Parameter verwenden:


```php
function summe(float $a, float $b): float
{
	return $a + $b;
}
```


Regel Nr. 1: Lass es dir übergeben .[#toc-rule-1-let-it-be-passed-to-you]
-------------------------------------------------------------------------

Die wichtigste Regel lautet: **alle Daten, die Funktionen oder Klassen benötigen, müssen an sie übergeben werden**.

Anstatt versteckte Wege für den Zugriff auf die Daten selbst zu erfinden, übergeben Sie einfach die Parameter. So sparen Sie Zeit, die Sie sonst für das Erfinden versteckter Pfade aufwenden müssten, die Ihren Code sicherlich nicht verbessern würden.

Wenn Sie diese Regel immer und überall befolgen, sind Sie auf dem Weg zu einem Code ohne versteckte Abhängigkeiten. Zu einem Code, der nicht nur für den Autor, sondern auch für jeden, der ihn später liest, verständlich ist. Wo alles aus den Signaturen von Funktionen und Klassen verständlich ist und man nicht nach versteckten Geheimnissen in der Implementierung suchen muss.

Diese Technik wird in der Fachsprache **dependency injection** genannt. Und diese Daten werden **Abhängigkeiten** genannt. Es ist nur eine gewöhnliche Parameterübergabe, nichts weiter.

.[note]
Verwechseln Sie bitte nicht Dependency Injection, die ein Entwurfsmuster ist, mit einem "Dependency Injection Container", der ein Werkzeug ist, etwas diametral anderes. Wir werden uns später mit Containern beschäftigen.


Von Funktionen zu Klassen .[#toc-from-functions-to-classes]
-----------------------------------------------------------

Und wie hängen die Klassen zusammen? Eine Klasse ist eine komplexere Einheit als eine einfache Funktion, aber auch hier gilt Regel Nr. 1 uneingeschränkt. Es gibt einfach [mehr Möglichkeiten, Argumente |passing-dependencies] zu übergeben. Zum Beispiel, ganz ähnlich wie im Fall einer Funktion:

```php
class Mathematik
{
	public function summe(float $a, float $b): float
	{
		return $a + $b;
	}
}

$math = new Mathematik;
echo $math->summe(23, 1); // 24
```

Oder durch andere Methoden, oder direkt durch den Konstruktor:

```php
class Summe
{
	public function __construct(
		private float $a,
		private float $b,
	) {
	}

	public function calculate(): float
	{
		return $this->a + $this->b;
	}

}

$summe = new Summe(23, 1);
echo $summe->calculate(); // 24
```

Beide Beispiele stehen vollständig im Einklang mit Dependency Injection.


Beispiele aus der Praxis .[#toc-real-life-examples]
---------------------------------------------------

In der realen Welt werden Sie keine Klassen für die Addition von Zahlen schreiben. Kommen wir nun zu den praktischen Beispielen.

Nehmen wir eine Klasse `Article`, die einen Blogbeitrag darstellt:

```php
class Article
{
	public int $id;
	public string $title;
	public string $content;

	public function save(): void
	{
		// speichert den Artikel in der Datenbank
	}
}
```

und die Verwendung wird wie folgt sein:

```php
$article = new Article;
$article->title = '10 Things You Need to Know About Losing Weight';
$article->content = 'Every year millions of people in ...';
$article->save();
```

Die Methode `save()` speichert den Artikel in einer Datenbanktabelle. Die Implementierung mit [Nette Database |database:] ist ein Kinderspiel, wenn es nicht ein Problem gäbe: Woher bekommt `Article` die Datenbankverbindung, d.h. ein Objekt der Klasse `Nette\Database\Connection`?

Es scheint, dass wir viele Möglichkeiten haben. Es kann die Verbindung von einer statischen Variable irgendwoher nehmen. Oder von einer Klasse erben, die eine Datenbankverbindung bereitstellt. Oder die Vorteile eines [Singletons |global-state#Singleton] nutzen. Oder sogenannte Fassaden verwenden, die in Laravel verwendet werden:

```php
use Illuminate\Support\Facades\DB;

class Article
{
	public int $id;
	public string $title;
	public string $content;

	public function save(): void
	{
		DB::insert(
			'INSERT INTO articles (title, content) VALUES (?, ?)',
			[$this->title, $this->content],
		);
	}
}
```

Toll, wir haben das Problem gelöst.

Oder haben wir das?

Erinnern wir uns an [Regel Nr. 1: "Let It Be Passed to You |#rule #1: Let It Be Passed to You]": Alle Abhängigkeiten, die die Klasse benötigt, müssen an sie weitergegeben werden. Denn wenn wir diese Regel brechen, haben wir uns auf einen Weg zu schmutzigem Code voller versteckter Abhängigkeiten und Unverständlichkeit begeben, und das Ergebnis wird eine Anwendung sein, die mühsam zu warten und zu entwickeln sein wird.

Der Benutzer der Klasse `Article` hat keine Ahnung, wo die Methode `save()` den Artikel speichert. In einer Datenbanktabelle? In welcher, der Produktions- oder der Testtabelle? Und wie kann sie geändert werden?

Der Benutzer muss sich ansehen, wie die Methode `save()` implementiert ist, und findet die Verwendung der Methode `DB::insert()`. Er muss also weiter suchen, um herauszufinden, wie diese Methode eine Datenbankverbindung herstellt. Und versteckte Abhängigkeiten können eine ziemlich lange Kette bilden.

In sauberem und gut durchdachtem Code gibt es niemals versteckte Abhängigkeiten, Laravel-Fassaden oder statische Variablen. In sauberem und gut durchdachtem Code werden Argumente übergeben:

```php
class Article
{
	public function save(Nette\Database\Connection $db): void
	{
		$db->query('INSERT INTO articles', [
			'title' => $this->title,
			'content' => $this->content,
		]);
	}
}
```

Ein noch praktischerer Ansatz ist, wie wir später sehen werden, die Verwendung des Konstruktors:

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

	public function save(): void
	{
		$this->db->query('INSERT INTO articles', [
			'title' => $this->title,
			'content' => $this->content,
		]);
	}
}
```

.[note]
Wenn Sie ein erfahrener Programmierer sind, denken Sie vielleicht, dass `Article` überhaupt keine Methode `save()` haben sollte; es sollte eine reine Datenkomponente darstellen, und ein separates Repository sollte sich um das Speichern kümmern. Das macht Sinn. Aber das würde weit über den Rahmen dieses Themas hinausgehen, das sich mit der Injektion von Abhängigkeiten befasst, und den Versuch, einfache Beispiele zu liefern.

Wenn Sie eine Klasse schreiben, die zum Beispiel eine Datenbank für ihren Betrieb benötigt, erfinden Sie nicht, woher Sie diese bekommen, sondern lassen Sie sie übergeben. Entweder als Parameter des Konstruktors oder einer anderen Methode. Geben Sie Abhängigkeiten zu. Geben Sie sie in der API Ihrer Klasse an. Sie werden verständlichen und vorhersehbaren Code erhalten.

Und was ist mit dieser Klasse, die Fehlermeldungen protokolliert?

```php
class Logger
{
	public function log(string $message)
	{
		$file = LOG_DIR . '/log.txt';
		file_put_contents($file, $message . "\n", FILE_APPEND);
	}
}
```

Was meinen Sie, haben wir die [Regel Nr. 1: Lass es dir übergeben |#rule #1: Let It Be Passed to You]: Es wird an Sie weitergegeben?

Wir haben es nicht getan.

Die Schlüsselinformation, d.h. das Verzeichnis mit der Protokolldatei, wird von der Klasse selbst aus der Konstante *erhalten*.

Sehen Sie sich das Beispiel für die Verwendung an:

```php
$logger = new Logger;
$logger->log('The temperature is 23 °C');
$logger->log('The temperature is 10 °C');
```

Können Sie, ohne die Implementierung zu kennen, die Frage beantworten, wo die Nachrichten geschrieben werden? Würden Sie vermuten, dass das Vorhandensein der Konstante `LOG_DIR` für das Funktionieren des Programms notwendig ist? Und könnten Sie eine zweite Instanz erstellen, die an einen anderen Ort schreibt? Sicherlich nicht.

Lassen Sie uns die Klasse korrigieren:

```php
class Logger
{
	public function __construct(
		private string $file,
	) {
	}

	public function log(string $message): void
	{
		file_put_contents($this->file, $message . "\n", FILE_APPEND);
	}
}
```

Die Klasse ist jetzt viel verständlicher, konfigurierbar und daher nützlicher.

```php
$logger = new Logger('/path/to/log.txt');
$logger->log('The temperature is 15 °C');
```


Aber das ist mir egal! .[#toc-but-i-don-t-care]
-----------------------------------------------

*"Wenn ich ein Artikel-Objekt erstelle und save() aufrufe, möchte ich mich nicht mit der Datenbank befassen; ich möchte nur, dass es in der Datenbank gespeichert wird, die ich in der Konfiguration eingestellt habe."*

*"Wenn ich Logger verwende, möchte ich nur, dass die Nachricht geschrieben wird, und ich möchte mich nicht darum kümmern, wo. Es sollen die globalen Einstellungen verwendet werden."*

Dies sind berechtigte Einwände.

Betrachten wir als Beispiel eine Klasse, die Newsletter versendet und protokolliert, wie es gelaufen ist:

```php
class NewsletterDistributor
{
	public function distribute(): void
	{
		$logger = new Logger(/* ... */);
		try {
			$this->sendEmails();
			$logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}
```

Bei der verbesserten Version `Logger`, die nicht mehr die Konstante `LOG_DIR` verwendet, muss der Dateipfad im Konstruktor angegeben werden. Wie lässt sich das Problem lösen? Der Klasse `NewsletterDistributor` ist es egal, wohin die Nachrichten geschrieben werden; sie will sie einfach nur schreiben.

Die Lösung ist wieder [Regel Nr. 1: Lass sie dir übergeben |#rule #1: Let It Be Passed to You]: Übergeben Sie alle Daten, die die Klasse benötigt.

Heißt das also, dass wir den Pfad zum Protokoll über den Konstruktor übergeben, den wir dann bei der Erstellung des `Logger` Objekts verwenden?

```php
class NewsletterDistributor
{
	public function __construct(
		private string $file, // ⛔ NICHT AUF DIESE WEISE!
	) {
	}

	public function distribute(): void
	{
		$logger = new Logger($this->file);
```

Nein, nicht auf diese Weise! Der Pfad gehört nicht zu den Daten, die die Klasse `NewsletterDistributor` braucht, sondern die Klasse `Logger` braucht ihn. Verstehen Sie den Unterschied? Die Klasse `NewsletterDistributor` braucht den Logger selbst. Das ist es also, was wir übergeben:

```php
class NewsletterDistributor
{
	public function __construct(
		private Logger $logger, // ✅
	) {
	}

	public function distribute(): void
	{
		try {
			$this->sendEmails();
			$this->logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$this->logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}
```

Nun geht aus den Signaturen der Klasse `NewsletterDistributor` hervor, dass auch die Protokollierung zu ihrer Funktionalität gehört. Und die Aufgabe, den Logger gegen einen anderen auszutauschen, etwa zu Testzwecken, ist völlig trivial.
Wenn sich außerdem der Konstruktor der Klasse `Logger` ändert, hat dies keine Auswirkungen auf unsere Klasse.


Regel Nr. 2: Nimm, was dir gehört .[#toc-rule-2-take-what-s-yours]
------------------------------------------------------------------

Lassen Sie sich nicht in die Irre führen und lassen Sie sich nicht die Abhängigkeiten von Ihren Abhängigen geben. Übergeben Sie nur Ihre eigenen Abhängigkeiten.

Dadurch wird der Code, der andere Objekte verwendet, völlig unabhängig von Änderungen in deren Konstruktoren. Seine API wird wahrheitsgetreuer sein. Und vor allem wird es trivial sein, diese Abhängigkeiten durch andere zu ersetzen.


Neues Familienmitglied .[#toc-new-family-member]
------------------------------------------------

Das Entwicklungsteam beschloss, einen zweiten Logger zu erstellen, der in die Datenbank schreibt. Also erstellen wir eine `DatabaseLogger` Klasse. Wir haben also zwei Klassen, `Logger` und `DatabaseLogger`, eine schreibt in eine Datei, die andere in eine Datenbank ... kommt Ihnen die Namensgebung nicht seltsam vor?
Wäre es nicht besser, `Logger` in `FileLogger` umzubenennen? Eindeutig ja.

Aber lassen Sie uns das auf intelligente Weise tun. Wir erstellen eine Schnittstelle unter dem ursprünglichen Namen:

```php
interface Logger
{
	function log(string $message): void;
}
```

... die beide Logger implementieren werden:

```php
class FileLogger implements Logger
// ...

class DatabaseLogger implements Logger
// ...
```

Daher muss im restlichen Code, in dem der Logger verwendet wird, nichts geändert werden. Zum Beispiel wird der Konstruktor der Klasse `NewsletterDistributor` immer noch damit zufrieden sein, `Logger` als Parameter zu benötigen. Und es bleibt uns überlassen, welche Instanz wir übergeben.

**Deshalb fügen wir den Schnittstellennamen niemals das Suffix `Interface` oder das Präfix `I` hinzu.** Sonst wäre es nicht möglich, den Code so schön zu entwickeln.


Houston, wir haben ein Problem .[#toc-houston-we-have-a-problem]
----------------------------------------------------------------

Während wir mit einer einzigen Instanz des Loggers, egal ob datei- oder datenbankbasiert, in der gesamten Anwendung auskommen und sie einfach überall dort übergeben können, wo etwas protokolliert wird, verhält es sich bei der Klasse `Article` ganz anders. Wir erzeugen ihre Instanzen je nach Bedarf, sogar mehrfach. Wie geht man mit der Datenbankabhängigkeit in ihrem Konstruktor um?

Ein Beispiel kann ein Controller sein, der nach dem Absenden eines Formulars einen Artikel in der Datenbank speichern soll:

```php
class EditController extends Controller
{
	public function formSubmitted($data)
	{
		$article = new Article(/* ... */);
		$article->title = $data->title;
		$article->content = $data->content;
		$article->save();
	}
}
```

Eine mögliche Lösung liegt auf der Hand: Übergeben Sie das Datenbankobjekt an den `EditController` Konstruktor und verwenden Sie `$article = new Article($this->db)`.

Genau wie im vorherigen Fall mit `Logger` und dem Dateipfad ist dies nicht der richtige Ansatz. Die Datenbank ist keine Abhängigkeit von `EditController`, sondern von `Article`. Die Übergabe der Datenbank verstößt gegen [Regel #2: Nimm, was dir gehört |#rule #2: take what's yours]. Wenn sich der Konstruktor der Klasse `Article` ändert (ein neuer Parameter wird hinzugefügt), müssen Sie den Code überall dort ändern, wo Instanzen erzeugt werden. Ufff.

Houston, was schlagen Sie vor?


Regel Nr. 3: Überlassen Sie die Abwicklung der Fabrik .[#toc-rule-3-let-the-factory-handle-it]
----------------------------------------------------------------------------------------------

Durch die Beseitigung versteckter Abhängigkeiten und die Übergabe aller Abhängigkeiten als Argumente haben wir mehr konfigurierbare und flexible Klassen erhalten. Und deshalb brauchen wir etwas anderes, um diese flexibleren Klassen für uns zu erstellen und zu konfigurieren. Wir werden es Fabriken nennen.

Die Faustregel lautet: Wenn eine Klasse Abhängigkeiten hat, überlassen Sie die Erstellung ihrer Instanzen der Fabrik.

Fabriken sind ein intelligenter Ersatz für den `new` Operator in der Welt der Dependency Injection.

.[note]
Nicht zu verwechseln mit dem Entwurfsmuster *Fabrikmethode*, das eine spezielle Art der Verwendung von Fabriken beschreibt und nichts mit diesem Thema zu tun hat.


Fabrik .[#toc-factory]
----------------------

Eine Fabrik ist eine Methode oder Klasse, die Objekte erstellt und konfiguriert. Wir werden die Klasse, die `Article` erzeugt, `ArticleFactory` nennen, und sie könnte wie folgt aussehen:

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

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

Die Verwendung im Controller sieht folgendermaßen aus:

```php
class EditController extends Controller
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function formSubmitted($data)
	{
		// die Fabrik ein Objekt erstellen lassen
		$article = $this->articleFactory->create();
		$article->title = $data->title;
		$article->content = $data->content;
		$article->save();
	}
}
```

Wenn sich nun die Signatur des Konstruktors der Klasse `Article` ändert, ist der einzige Teil des Codes, der darauf reagieren muss, der `ArticleFactory` selbst. Alle anderen Codes, die mit `Article` Objekten arbeiten, wie z.B. `EditController`, sind davon nicht betroffen.

Sie werden sich vielleicht fragen, ob wir die Dinge tatsächlich besser gemacht haben. Die Menge des Codes hat zugenommen, und das Ganze sieht verdächtig kompliziert aus.

Keine Sorge, bald werden wir zum Nette-DI-Container kommen. Und der hat einige Tricks in petto, die das Erstellen von Anwendungen mit Dependency Injection erheblich vereinfachen werden. Zum Beispiel müssen Sie anstelle der Klasse `ArticleFactory` nur [eine einfache Schnittstelle schreiben |factory]:

```php
interface ArticleFactory
{
	function create(): Article;
}
```

Aber wir greifen uns selbst vor; bitte haben Sie noch etwas Geduld :-)


Zusammenfassung .[#toc-summary]
-------------------------------

Zu Beginn dieses Kapitels haben wir versprochen, Ihnen einen Prozess zur Entwicklung von sauberem Code zu zeigen. Alles, was es braucht, ist, dass die Klassen:

- [die Abhängigkeiten zu übergeben, die sie benötigen |#Rule #1: Let It Be Passed to You]
- [umgekehrt nicht übergeben, was sie nicht direkt brauchen |#Rule #2: Take What's Yours]
- [und dass Objekte mit Abhängigkeiten am besten in Fabriken erstellt werden |#Rule #3: Let the Factory Handle it]

Auf den ersten Blick scheinen diese drei Regeln keine weitreichenden Konsequenzen zu haben, aber sie führen zu einer radikal anderen Sichtweise des Codeentwurfs. Ist es das wert? Entwickler, die alte Gewohnheiten aufgegeben und mit der konsequenten Nutzung von Dependency Injection begonnen haben, betrachten diesen Schritt als einen entscheidenden Moment in ihrem Berufsleben. Er hat ihnen die Welt der klaren und wartbaren Anwendungen eröffnet.

Was aber, wenn der Code nicht konsequent Dependency Injection verwendet? Was ist, wenn er sich auf statische Methoden oder Singletons stützt? Verursacht das Probleme? [Ja, das tut es, und zwar ganz grundlegende |global-state].

Was ist Dependency Injection?

In diesem Kapitel werden Sie mit den grundlegenden Programmierpraktiken vertraut gemacht, die Sie beim Schreiben jeder Anwendung befolgen sollten. Dies sind die Grundlagen, die zum Schreiben von sauberem, verständlichem und wartbarem Code erforderlich sind.

Wenn Sie diese Regeln lernen und befolgen, wird Nette Ihnen bei jedem Schritt zur Seite stehen. Es wird Routineaufgaben für Sie erledigen und Ihnen maximalen Komfort bieten, so dass Sie sich auf die eigentliche Logik konzentrieren können.

Die Prinzipien, die wir hier zeigen, sind ganz einfach. Sie brauchen sich um nichts zu kümmern.

Erinnern Sie sich an Ihr erstes Programm?

Wir wissen nicht, in welcher Sprache Sie es geschrieben haben, aber wenn es PHP war, könnte es etwa so aussehen:

function summe(float $a, float $b): float
{
	return $a + $b;
}

echo summe(23, 1); // gibt 24 aus

Ein paar triviale Codezeilen, aber so viele wichtige Konzepte, die darin versteckt sind. Dass es Variablen gibt. Dass der Code in kleinere Einheiten unterteilt ist, die zum Beispiel Funktionen sind. Dass wir ihnen Eingabeargumente übergeben und sie Ergebnisse zurückgeben. Alles, was fehlt, sind Bedingungen und Schleifen.

Die Tatsache, dass eine Funktion Eingabedaten entgegennimmt und ein Ergebnis zurückliefert, ist ein durchaus verständliches Konzept, das auch in anderen Bereichen, z. B. der Mathematik, verwendet wird.

Eine Funktion hat ihre Signatur, die aus ihrem Namen, einer Liste von Parametern und deren Typen und schließlich dem Typ des Rückgabewerts besteht. Als Benutzer sind wir an der Signatur interessiert und müssen normalerweise nichts über die interne Implementierung wissen.

Stellen Sie sich nun vor, die Funktionssignatur sähe wie folgt aus:

function summe(float $x): float

Ein Zusatz mit einem Parameter? Das ist seltsam… Und was ist damit?

function summe(): float

Das ist doch wirklich seltsam, oder? Wie wird die Funktion verwendet?

echo summe(); // was wird gedruckt?

Wenn wir uns einen solchen Code ansehen, wären wir verwirrt. Nicht nur ein Anfänger würde ihn nicht verstehen, sondern auch ein erfahrener Programmierer würde einen solchen Code nicht verstehen.

Fragen Sie sich, wie eine solche Funktion eigentlich aussehen würde? Woher würde sie die Summanden bekommen? Sie würde sie wahrscheinlich irgendwie selbst beschaffen, vielleicht so:

function summe(): float
{
	$a = Input::get('a');
	$b = Input::get('b');
	return $a + $b;
}

Es stellt sich heraus, dass es versteckte Bindungen zu anderen Funktionen (oder statischen Methoden) im Körper der Funktion gibt, und um herauszufinden, woher die Summanden tatsächlich kommen, müssen wir weiter graben.

Nicht hier entlang!

Das eben gezeigte Design ist die Essenz vieler negativer Merkmale:

  • die Funktionssignatur gibt vor, dass sie die Summanden nicht braucht, was uns verwirrt
  • wir haben keine Ahnung, wie wir die Funktion mit zwei anderen Zahlen rechnen lassen können
  • wir mussten uns den Code ansehen, um herauszufinden, woher die Summanden kamen
  • wir haben versteckte Abhängigkeiten gefunden
  • ein vollständiges Verständnis erfordert auch die Untersuchung dieser Abhängigkeiten

Und ist es überhaupt die Aufgabe der Additionsfunktion, Eingaben zu beschaffen? Nein, natürlich nicht. Ihre Aufgabe ist es nur, zu addieren.

Solchen Code wollen wir nicht sehen, und wir wollen ihn schon gar nicht schreiben. Die Abhilfe ist einfach: Zurück zu den Grundlagen und einfach Parameter verwenden:

function summe(float $a, float $b): float
{
	return $a + $b;
}

Regel Nr. 1: Lass es dir übergeben

Die wichtigste Regel lautet: alle Daten, die Funktionen oder Klassen benötigen, müssen an sie übergeben werden.

Anstatt versteckte Wege für den Zugriff auf die Daten selbst zu erfinden, übergeben Sie einfach die Parameter. So sparen Sie Zeit, die Sie sonst für das Erfinden versteckter Pfade aufwenden müssten, die Ihren Code sicherlich nicht verbessern würden.

Wenn Sie diese Regel immer und überall befolgen, sind Sie auf dem Weg zu einem Code ohne versteckte Abhängigkeiten. Zu einem Code, der nicht nur für den Autor, sondern auch für jeden, der ihn später liest, verständlich ist. Wo alles aus den Signaturen von Funktionen und Klassen verständlich ist und man nicht nach versteckten Geheimnissen in der Implementierung suchen muss.

Diese Technik wird in der Fachsprache dependency injection genannt. Und diese Daten werden Abhängigkeiten genannt. Es ist nur eine gewöhnliche Parameterübergabe, nichts weiter.

Verwechseln Sie bitte nicht Dependency Injection, die ein Entwurfsmuster ist, mit einem „Dependency Injection Container“, der ein Werkzeug ist, etwas diametral anderes. Wir werden uns später mit Containern beschäftigen.

Von Funktionen zu Klassen

Und wie hängen die Klassen zusammen? Eine Klasse ist eine komplexere Einheit als eine einfache Funktion, aber auch hier gilt Regel Nr. 1 uneingeschränkt. Es gibt einfach mehr Möglichkeiten, Argumente zu übergeben. Zum Beispiel, ganz ähnlich wie im Fall einer Funktion:

class Mathematik
{
	public function summe(float $a, float $b): float
	{
		return $a + $b;
	}
}

$math = new Mathematik;
echo $math->summe(23, 1); // 24

Oder durch andere Methoden, oder direkt durch den Konstruktor:

class Summe
{
	public function __construct(
		private float $a,
		private float $b,
	) {
	}

	public function calculate(): float
	{
		return $this->a + $this->b;
	}

}

$summe = new Summe(23, 1);
echo $summe->calculate(); // 24

Beide Beispiele stehen vollständig im Einklang mit Dependency Injection.

Beispiele aus der Praxis

In der realen Welt werden Sie keine Klassen für die Addition von Zahlen schreiben. Kommen wir nun zu den praktischen Beispielen.

Nehmen wir eine Klasse Article, die einen Blogbeitrag darstellt:

class Article
{
	public int $id;
	public string $title;
	public string $content;

	public function save(): void
	{
		// speichert den Artikel in der Datenbank
	}
}

und die Verwendung wird wie folgt sein:

$article = new Article;
$article->title = '10 Things You Need to Know About Losing Weight';
$article->content = 'Every year millions of people in ...';
$article->save();

Die Methode save() speichert den Artikel in einer Datenbanktabelle. Die Implementierung mit Nette Database ist ein Kinderspiel, wenn es nicht ein Problem gäbe: Woher bekommt Article die Datenbankverbindung, d.h. ein Objekt der Klasse Nette\Database\Connection?

Es scheint, dass wir viele Möglichkeiten haben. Es kann die Verbindung von einer statischen Variable irgendwoher nehmen. Oder von einer Klasse erben, die eine Datenbankverbindung bereitstellt. Oder die Vorteile eines Singletons nutzen. Oder sogenannte Fassaden verwenden, die in Laravel verwendet werden:

use Illuminate\Support\Facades\DB;

class Article
{
	public int $id;
	public string $title;
	public string $content;

	public function save(): void
	{
		DB::insert(
			'INSERT INTO articles (title, content) VALUES (?, ?)',
			[$this->title, $this->content],
		);
	}
}

Toll, wir haben das Problem gelöst.

Oder haben wir das?

Erinnern wir uns an Regel Nr. 1: „Let It Be Passed to You“: Alle Abhängigkeiten, die die Klasse benötigt, müssen an sie weitergegeben werden. Denn wenn wir diese Regel brechen, haben wir uns auf einen Weg zu schmutzigem Code voller versteckter Abhängigkeiten und Unverständlichkeit begeben, und das Ergebnis wird eine Anwendung sein, die mühsam zu warten und zu entwickeln sein wird.

Der Benutzer der Klasse Article hat keine Ahnung, wo die Methode save() den Artikel speichert. In einer Datenbanktabelle? In welcher, der Produktions- oder der Testtabelle? Und wie kann sie geändert werden?

Der Benutzer muss sich ansehen, wie die Methode save() implementiert ist, und findet die Verwendung der Methode DB::insert(). Er muss also weiter suchen, um herauszufinden, wie diese Methode eine Datenbankverbindung herstellt. Und versteckte Abhängigkeiten können eine ziemlich lange Kette bilden.

In sauberem und gut durchdachtem Code gibt es niemals versteckte Abhängigkeiten, Laravel-Fassaden oder statische Variablen. In sauberem und gut durchdachtem Code werden Argumente übergeben:

class Article
{
	public function save(Nette\Database\Connection $db): void
	{
		$db->query('INSERT INTO articles', [
			'title' => $this->title,
			'content' => $this->content,
		]);
	}
}

Ein noch praktischerer Ansatz ist, wie wir später sehen werden, die Verwendung des Konstruktors:

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

	public function save(): void
	{
		$this->db->query('INSERT INTO articles', [
			'title' => $this->title,
			'content' => $this->content,
		]);
	}
}

Wenn Sie ein erfahrener Programmierer sind, denken Sie vielleicht, dass Article überhaupt keine Methode save() haben sollte; es sollte eine reine Datenkomponente darstellen, und ein separates Repository sollte sich um das Speichern kümmern. Das macht Sinn. Aber das würde weit über den Rahmen dieses Themas hinausgehen, das sich mit der Injektion von Abhängigkeiten befasst, und den Versuch, einfache Beispiele zu liefern.

Wenn Sie eine Klasse schreiben, die zum Beispiel eine Datenbank für ihren Betrieb benötigt, erfinden Sie nicht, woher Sie diese bekommen, sondern lassen Sie sie übergeben. Entweder als Parameter des Konstruktors oder einer anderen Methode. Geben Sie Abhängigkeiten zu. Geben Sie sie in der API Ihrer Klasse an. Sie werden verständlichen und vorhersehbaren Code erhalten.

Und was ist mit dieser Klasse, die Fehlermeldungen protokolliert?

class Logger
{
	public function log(string $message)
	{
		$file = LOG_DIR . '/log.txt';
		file_put_contents($file, $message . "\n", FILE_APPEND);
	}
}

Was meinen Sie, haben wir die Regel Nr. 1: Lass es dir übergeben: Es wird an Sie weitergegeben?

Wir haben es nicht getan.

Die Schlüsselinformation, d.h. das Verzeichnis mit der Protokolldatei, wird von der Klasse selbst aus der Konstante erhalten.

Sehen Sie sich das Beispiel für die Verwendung an:

$logger = new Logger;
$logger->log('The temperature is 23 °C');
$logger->log('The temperature is 10 °C');

Können Sie, ohne die Implementierung zu kennen, die Frage beantworten, wo die Nachrichten geschrieben werden? Würden Sie vermuten, dass das Vorhandensein der Konstante LOG_DIR für das Funktionieren des Programms notwendig ist? Und könnten Sie eine zweite Instanz erstellen, die an einen anderen Ort schreibt? Sicherlich nicht.

Lassen Sie uns die Klasse korrigieren:

class Logger
{
	public function __construct(
		private string $file,
	) {
	}

	public function log(string $message): void
	{
		file_put_contents($this->file, $message . "\n", FILE_APPEND);
	}
}

Die Klasse ist jetzt viel verständlicher, konfigurierbar und daher nützlicher.

$logger = new Logger('/path/to/log.txt');
$logger->log('The temperature is 15 °C');

Aber das ist mir egal!

„Wenn ich ein Artikel-Objekt erstelle und save() aufrufe, möchte ich mich nicht mit der Datenbank befassen; ich möchte nur, dass es in der Datenbank gespeichert wird, die ich in der Konfiguration eingestellt habe.“

„Wenn ich Logger verwende, möchte ich nur, dass die Nachricht geschrieben wird, und ich möchte mich nicht darum kümmern, wo. Es sollen die globalen Einstellungen verwendet werden.“

Dies sind berechtigte Einwände.

Betrachten wir als Beispiel eine Klasse, die Newsletter versendet und protokolliert, wie es gelaufen ist:

class NewsletterDistributor
{
	public function distribute(): void
	{
		$logger = new Logger(/* ... */);
		try {
			$this->sendEmails();
			$logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}

Bei der verbesserten Version Logger, die nicht mehr die Konstante LOG_DIR verwendet, muss der Dateipfad im Konstruktor angegeben werden. Wie lässt sich das Problem lösen? Der Klasse NewsletterDistributor ist es egal, wohin die Nachrichten geschrieben werden; sie will sie einfach nur schreiben.

Die Lösung ist wieder Regel Nr. 1: Lass sie dir übergeben: Übergeben Sie alle Daten, die die Klasse benötigt.

Heißt das also, dass wir den Pfad zum Protokoll über den Konstruktor übergeben, den wir dann bei der Erstellung des Logger Objekts verwenden?

class NewsletterDistributor
{
	public function __construct(
		private string $file, // ⛔ NICHT AUF DIESE WEISE!
	) {
	}

	public function distribute(): void
	{
		$logger = new Logger($this->file);

Nein, nicht auf diese Weise! Der Pfad gehört nicht zu den Daten, die die Klasse NewsletterDistributor braucht, sondern die Klasse Logger braucht ihn. Verstehen Sie den Unterschied? Die Klasse NewsletterDistributor braucht den Logger selbst. Das ist es also, was wir übergeben:

class NewsletterDistributor
{
	public function __construct(
		private Logger $logger, // ✅
	) {
	}

	public function distribute(): void
	{
		try {
			$this->sendEmails();
			$this->logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$this->logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}

Nun geht aus den Signaturen der Klasse NewsletterDistributor hervor, dass auch die Protokollierung zu ihrer Funktionalität gehört. Und die Aufgabe, den Logger gegen einen anderen auszutauschen, etwa zu Testzwecken, ist völlig trivial. Wenn sich außerdem der Konstruktor der Klasse Logger ändert, hat dies keine Auswirkungen auf unsere Klasse.

Regel Nr. 2: Nimm, was dir gehört

Lassen Sie sich nicht in die Irre führen und lassen Sie sich nicht die Abhängigkeiten von Ihren Abhängigen geben. Übergeben Sie nur Ihre eigenen Abhängigkeiten.

Dadurch wird der Code, der andere Objekte verwendet, völlig unabhängig von Änderungen in deren Konstruktoren. Seine API wird wahrheitsgetreuer sein. Und vor allem wird es trivial sein, diese Abhängigkeiten durch andere zu ersetzen.

Neues Familienmitglied

Das Entwicklungsteam beschloss, einen zweiten Logger zu erstellen, der in die Datenbank schreibt. Also erstellen wir eine DatabaseLogger Klasse. Wir haben also zwei Klassen, Logger und DatabaseLogger, eine schreibt in eine Datei, die andere in eine Datenbank … kommt Ihnen die Namensgebung nicht seltsam vor? Wäre es nicht besser, Logger in FileLogger umzubenennen? Eindeutig ja.

Aber lassen Sie uns das auf intelligente Weise tun. Wir erstellen eine Schnittstelle unter dem ursprünglichen Namen:

interface Logger
{
	function log(string $message): void;
}

… die beide Logger implementieren werden:

class FileLogger implements Logger
// ...

class DatabaseLogger implements Logger
// ...

Daher muss im restlichen Code, in dem der Logger verwendet wird, nichts geändert werden. Zum Beispiel wird der Konstruktor der Klasse NewsletterDistributor immer noch damit zufrieden sein, Logger als Parameter zu benötigen. Und es bleibt uns überlassen, welche Instanz wir übergeben.

Deshalb fügen wir den Schnittstellennamen niemals das Suffix Interface oder das Präfix I hinzu. Sonst wäre es nicht möglich, den Code so schön zu entwickeln.

Houston, wir haben ein Problem

Während wir mit einer einzigen Instanz des Loggers, egal ob datei- oder datenbankbasiert, in der gesamten Anwendung auskommen und sie einfach überall dort übergeben können, wo etwas protokolliert wird, verhält es sich bei der Klasse Article ganz anders. Wir erzeugen ihre Instanzen je nach Bedarf, sogar mehrfach. Wie geht man mit der Datenbankabhängigkeit in ihrem Konstruktor um?

Ein Beispiel kann ein Controller sein, der nach dem Absenden eines Formulars einen Artikel in der Datenbank speichern soll:

class EditController extends Controller
{
	public function formSubmitted($data)
	{
		$article = new Article(/* ... */);
		$article->title = $data->title;
		$article->content = $data->content;
		$article->save();
	}
}

Eine mögliche Lösung liegt auf der Hand: Übergeben Sie das Datenbankobjekt an den EditController Konstruktor und verwenden Sie $article = new Article($this->db).

Genau wie im vorherigen Fall mit Logger und dem Dateipfad ist dies nicht der richtige Ansatz. Die Datenbank ist keine Abhängigkeit von EditController, sondern von Article. Die Übergabe der Datenbank verstößt gegen Regel #2: Nimm, was dir gehört. Wenn sich der Konstruktor der Klasse Article ändert (ein neuer Parameter wird hinzugefügt), müssen Sie den Code überall dort ändern, wo Instanzen erzeugt werden. Ufff.

Houston, was schlagen Sie vor?

Regel Nr. 3: Überlassen Sie die Abwicklung der Fabrik

Durch die Beseitigung versteckter Abhängigkeiten und die Übergabe aller Abhängigkeiten als Argumente haben wir mehr konfigurierbare und flexible Klassen erhalten. Und deshalb brauchen wir etwas anderes, um diese flexibleren Klassen für uns zu erstellen und zu konfigurieren. Wir werden es Fabriken nennen.

Die Faustregel lautet: Wenn eine Klasse Abhängigkeiten hat, überlassen Sie die Erstellung ihrer Instanzen der Fabrik.

Fabriken sind ein intelligenter Ersatz für den new Operator in der Welt der Dependency Injection.

Nicht zu verwechseln mit dem Entwurfsmuster Fabrikmethode, das eine spezielle Art der Verwendung von Fabriken beschreibt und nichts mit diesem Thema zu tun hat.

Fabrik

Eine Fabrik ist eine Methode oder Klasse, die Objekte erstellt und konfiguriert. Wir werden die Klasse, die Article erzeugt, ArticleFactory nennen, und sie könnte wie folgt aussehen:

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

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

Die Verwendung im Controller sieht folgendermaßen aus:

class EditController extends Controller
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function formSubmitted($data)
	{
		// die Fabrik ein Objekt erstellen lassen
		$article = $this->articleFactory->create();
		$article->title = $data->title;
		$article->content = $data->content;
		$article->save();
	}
}

Wenn sich nun die Signatur des Konstruktors der Klasse Article ändert, ist der einzige Teil des Codes, der darauf reagieren muss, der ArticleFactory selbst. Alle anderen Codes, die mit Article Objekten arbeiten, wie z.B. EditController, sind davon nicht betroffen.

Sie werden sich vielleicht fragen, ob wir die Dinge tatsächlich besser gemacht haben. Die Menge des Codes hat zugenommen, und das Ganze sieht verdächtig kompliziert aus.

Keine Sorge, bald werden wir zum Nette-DI-Container kommen. Und der hat einige Tricks in petto, die das Erstellen von Anwendungen mit Dependency Injection erheblich vereinfachen werden. Zum Beispiel müssen Sie anstelle der Klasse ArticleFactory nur eine einfache Schnittstelle schreiben:

interface ArticleFactory
{
	function create(): Article;
}

Aber wir greifen uns selbst vor; bitte haben Sie noch etwas Geduld :-)

Zusammenfassung

Zu Beginn dieses Kapitels haben wir versprochen, Ihnen einen Prozess zur Entwicklung von sauberem Code zu zeigen. Alles, was es braucht, ist, dass die Klassen:

Auf den ersten Blick scheinen diese drei Regeln keine weitreichenden Konsequenzen zu haben, aber sie führen zu einer radikal anderen Sichtweise des Codeentwurfs. Ist es das wert? Entwickler, die alte Gewohnheiten aufgegeben und mit der konsequenten Nutzung von Dependency Injection begonnen haben, betrachten diesen Schritt als einen entscheidenden Moment in ihrem Berufsleben. Er hat ihnen die Welt der klaren und wartbaren Anwendungen eröffnet.

Was aber, wenn der Code nicht konsequent Dependency Injection verwendet? Was ist, wenn er sich auf statische Methoden oder Singletons stützt? Verursacht das Probleme? Ja, das tut es, und zwar ganz grundlegende.