Nette Documentation Preview

syntax
Was ist ein DI-Container?
*************************

.[perex]
Dependency Injection Container (DIC) ist eine Klasse, die Objekte instanziieren und konfigurieren kann.

Es mag Sie überraschen, aber in vielen Fällen brauchen Sie keinen Dependency Injection Container, um die Vorteile von Dependency Injection (kurz DI) zu nutzen. Schließlich haben wir bereits im [vorigen Kapitel |introduction] konkrete Beispiele für DI gezeigt, für die kein Container erforderlich war.

Wenn Sie jedoch eine große Anzahl verschiedener Objekte mit vielen Abhängigkeiten verwalten müssen, ist ein Dependency Injection Container sehr nützlich. Das ist vielleicht der Fall bei Webanwendungen, die auf einem Framework aufbauen.

Im vorherigen Kapitel haben wir die Klassen `Article` und `UserController` vorgestellt. Beide haben einige Abhängigkeiten, nämlich Datenbank und Factory `ArticleFactory`. Und für diese Klassen werden wir nun einen Container erstellen. Natürlich ist es für ein so einfaches Beispiel nicht sinnvoll, einen Container zu haben. Aber wir werden einen erstellen, um zu zeigen, wie er aussieht und funktioniert.

Hier ist ein einfacher, hart kodierter Container für das obige Beispiel:

```php
class Container
{
	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection('mysql:', 'root', '***');
	}

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->createDatabase());
	}

	public function createUserController(): UserController
	{
		return new UserController($this->createArticleFactory());
	}
}
```

Die Verwendung würde wie folgt aussehen:

```php
$container = new Container;
$controller = $container->createUserController();
```

Wir fragen den Container einfach nach dem Objekt und müssen nicht mehr wissen, wie es erstellt wird oder welche Abhängigkeiten es hat; der Container weiß das alles. Der Container weiß das alles. Die Abhängigkeiten werden vom Container automatisch injiziert. Das ist seine Stärke.

Bis jetzt hat der Container alles hart kodiert. Wir gehen also den nächsten Schritt und fügen Parameter hinzu, um den Container wirklich nützlich zu machen:

```php
class Container
{
	public function __construct(
		private array $parameters,
	) {
	}

	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection(
			$this->Parameter['db.dsn'],
			$this->parameters['db.user'],
			$this->parameters['db.password'],
		);
	}

	// ...
}

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);
```

Aufmerksame Leser haben vielleicht ein Problem bemerkt. Jedes Mal, wenn ich ein Objekt `UserController` erhalte, wird eine neue Instanz `ArticleFactory` und eine neue Datenbank erstellt. Das wollen wir definitiv nicht.

Also fügen wir eine Methode `getService()` hinzu, die immer wieder die gleichen Instanzen zurückgibt:

```php
class container
{
	private array $services = [];

	public function __construct(
		private array $parameters,
	) {
	}

	public function getService(string $name): object
	{
		if (!isset($this->services[$name])) {
			// getService('Datenbank') ruft createDatabase() auf
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}
```

Beim ersten Aufruf von z.B. `$container->getService('Database')` wird `createDatabase()` ein Datenbankobjekt erstellen, das im Array `$services` gespeichert und beim nächsten Aufruf direkt zurückgegeben wird.

Wir ändern auch den Rest des Containers, um `getService()` zu verwenden:

```php
class Container
{
	// ...

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->getService('Database'));
	}

	public function createUserController(): UserController
	{
		return new UserController($this->getService('ArticleFactory'));
	}
}
```

Der Begriff Dienst bezieht sich übrigens auf jedes vom Container verwaltete Objekt. Daher auch der Name der Methode `getService()`.

Das war's. Wir haben einen voll funktionsfähigen DI-Container! Und wir können ihn benutzen:

```php
$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

$controller = $container->getService('UserController');
$database = $container->getService('Database');
```

Wie Sie sehen können, ist es nicht schwer, einen DIC zu schreiben. Bemerkenswert ist, dass die Objekte selbst nicht wissen, dass sie von einem Container erstellt werden. Es ist also möglich, jedes beliebige Objekt in PHP auf diese Weise zu erstellen, ohne den Quellcode zu verändern.

Die manuelle Erstellung und Pflege einer Containerklasse kann schnell zu einem Alptraum werden. Deshalb werden wir im nächsten Kapitel über [Nette DI Container |nette-container] sprechen, die sich fast automatisch erzeugen und aktualisieren können.

Was ist ein DI-Container?

Dependency Injection Container (DIC) ist eine Klasse, die Objekte instanziieren und konfigurieren kann.

Es mag Sie überraschen, aber in vielen Fällen brauchen Sie keinen Dependency Injection Container, um die Vorteile von Dependency Injection (kurz DI) zu nutzen. Schließlich haben wir bereits im vorigen Kapitel konkrete Beispiele für DI gezeigt, für die kein Container erforderlich war.

Wenn Sie jedoch eine große Anzahl verschiedener Objekte mit vielen Abhängigkeiten verwalten müssen, ist ein Dependency Injection Container sehr nützlich. Das ist vielleicht der Fall bei Webanwendungen, die auf einem Framework aufbauen.

Im vorherigen Kapitel haben wir die Klassen Article und UserController vorgestellt. Beide haben einige Abhängigkeiten, nämlich Datenbank und Factory ArticleFactory. Und für diese Klassen werden wir nun einen Container erstellen. Natürlich ist es für ein so einfaches Beispiel nicht sinnvoll, einen Container zu haben. Aber wir werden einen erstellen, um zu zeigen, wie er aussieht und funktioniert.

Hier ist ein einfacher, hart kodierter Container für das obige Beispiel:

class Container
{
	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection('mysql:', 'root', '***');
	}

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->createDatabase());
	}

	public function createUserController(): UserController
	{
		return new UserController($this->createArticleFactory());
	}
}

Die Verwendung würde wie folgt aussehen:

$container = new Container;
$controller = $container->createUserController();

Wir fragen den Container einfach nach dem Objekt und müssen nicht mehr wissen, wie es erstellt wird oder welche Abhängigkeiten es hat; der Container weiß das alles. Der Container weiß das alles. Die Abhängigkeiten werden vom Container automatisch injiziert. Das ist seine Stärke.

Bis jetzt hat der Container alles hart kodiert. Wir gehen also den nächsten Schritt und fügen Parameter hinzu, um den Container wirklich nützlich zu machen:

class Container
{
	public function __construct(
		private array $parameters,
	) {
	}

	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection(
			$this->Parameter['db.dsn'],
			$this->parameters['db.user'],
			$this->parameters['db.password'],
		);
	}

	// ...
}

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

Aufmerksame Leser haben vielleicht ein Problem bemerkt. Jedes Mal, wenn ich ein Objekt UserController erhalte, wird eine neue Instanz ArticleFactory und eine neue Datenbank erstellt. Das wollen wir definitiv nicht.

Also fügen wir eine Methode getService() hinzu, die immer wieder die gleichen Instanzen zurückgibt:

class container
{
	private array $services = [];

	public function __construct(
		private array $parameters,
	) {
	}

	public function getService(string $name): object
	{
		if (!isset($this->services[$name])) {
			// getService('Datenbank') ruft createDatabase() auf
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}

Beim ersten Aufruf von z.B. $container->getService('Database') wird createDatabase() ein Datenbankobjekt erstellen, das im Array $services gespeichert und beim nächsten Aufruf direkt zurückgegeben wird.

Wir ändern auch den Rest des Containers, um getService() zu verwenden:

class Container
{
	// ...

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->getService('Database'));
	}

	public function createUserController(): UserController
	{
		return new UserController($this->getService('ArticleFactory'));
	}
}

Der Begriff Dienst bezieht sich übrigens auf jedes vom Container verwaltete Objekt. Daher auch der Name der Methode getService().

Das war's. Wir haben einen voll funktionsfähigen DI-Container! Und wir können ihn benutzen:

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

$controller = $container->getService('UserController');
$database = $container->getService('Database');

Wie Sie sehen können, ist es nicht schwer, einen DIC zu schreiben. Bemerkenswert ist, dass die Objekte selbst nicht wissen, dass sie von einem Container erstellt werden. Es ist also möglich, jedes beliebige Objekt in PHP auf diese Weise zu erstellen, ohne den Quellcode zu verändern.

Die manuelle Erstellung und Pflege einer Containerklasse kann schnell zu einem Alptraum werden. Deshalb werden wir im nächsten Kapitel über Nette DI Container sprechen, die sich fast automatisch erzeugen und aktualisieren können.