Nette DI Container

Nette DI ist eine der interessantesten Bibliotheken von Nette. Sie kann kompilierte DI-Container erzeugen und automatisch aktualisieren, die außerordentlich schnell und bemerkenswert einfach zu konfigurieren sind.

Wie die Services aussehen sollen, die der DI-Container erzeugt, legt man üblicherweise über Konfigurationsdateien im Format NEON fest. Der Container, den wir im vorigen Kapitel von Hand gebaut haben, würde so geschrieben:

parameters:
	db:
		dsn: 'mysql:'
		user: root
		password: '***'

services:
	- Nette\Database\Connection(%db.dsn%, %db.user%, %db.password%)
	- ArticleFactory
	- EditController

Die Syntax ist sehr knapp.

Alle Abhängigkeiten, die in den Konstruktoren der Klassen ArticleFactory und EditController deklariert sind, findet und übergibt Nette DI dank des sogenannten Autowirings automatisch, in der Konfigurationsdatei muss also nichts angegeben werden. Selbst wenn sich die Parameter ändern, müssen Sie an der Konfiguration nichts ändern. Während der Entwicklung erzeugt Nette den Container automatisch neu. Sie können sich ganz auf die Entwicklung der Anwendung konzentrieren.

Wollen wir Abhängigkeiten über Setter übergeben, verwenden wir dafür den Abschnitt setup.

Nette DI erzeugt den PHP-Code des Containers direkt. Das Ergebnis ist also eine .php-Datei, die Sie öffnen und untersuchen können. So sehen Sie genau, wie der Container funktioniert. Sie können ihn auch in Ihrer IDE debuggen und Schritt für Schritt durchgehen. Und vor allem: Der erzeugte PHP-Code ist außerordentlich schnell.

Nette DI kann auch den Code einer Factory anhand eines vorgegebenen Interfaces erzeugen. Statt der Klasse ArticleFactory müssen wir in der Anwendung also nur ein Interface anlegen:

interface ArticleFactory
{
	function create(): Article;
}

Das vollständige Beispiel finden Sie auf GitHub.

Eigenständige Verwendung

Die Bibliothek Nette DI in eine Anwendung einzubinden ist sehr einfach. Zuerst installieren wir sie über Composer (denn das Herunterladen von ZIP-Dateien ist so von gestern):

composer require nette/di

Der folgende Code verwendet den Compiler, um anhand der Konfiguration in der Datei config.neon eine Instanz des DI-Containers zu erzeugen:

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function ($compiler) {
	$compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;

Der Container wird nur einmal erzeugt, sein Code wird in den Cache geschrieben (in das Verzeichnis __DIR__ . '/temp'), und bei weiteren Requests wird er nur noch von dort geladen.

Für sich allein schaltet der Compiler in der Konfiguration nur die Abschnitte services und parameters frei. Um weitere zu nutzen – etwa search, decorator, di oder inject -, registrieren Sie zuerst deren Extensions. Und damit sich Extensions aus dem Abschnitt extensions der Konfiguration registrieren lassen, ergänzen Sie die ExtensionsExtension:

$compiler->addExtension('search', new Nette\DI\Extensions\SearchExtension($tempDir));
$compiler->addExtension('extensions', new Nette\DI\Extensions\ExtensionsExtension);

Der Configurator, der in vollständigen Nette-Anwendungen verwendet wird, registriert sie alle automatisch.

Wenn Sie mehrere verschiedene Container im selben Cache-Verzeichnis halten, unterscheiden Sie sie über einen Schlüssel, den Sie load() als zweites Argument übergeben; er wird Teil des Namens der erzeugten Klasse:

$class = $loader->load(
	fn($compiler) => $compiler->loadConfig(__DIR__ . '/config.neon'),
	'my-key',
);

Zum Erzeugen und Holen von Services dienen die Methoden getService() oder getByType(). So erzeugen wir das Objekt EditController:

$controller = $container->getByType(EditController::class);
$controller->someMethod();

Während der Entwicklung ist es nützlich, den Modus der automatischen Aktualisierung einzuschalten, in dem sich der Container automatisch neu erzeugt, sobald sich eine Klasse oder eine Konfigurationsdatei ändert. Geben Sie dazu im Konstruktor des ContainerLoader als zweites Argument true an.

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true);

Mit dem Container arbeiten

Neben getService() und getByType() bietet das Objekt des Containers mehrere weitere nützliche Methoden:

  • getByType(string $type, bool $throw = true): ?object gibt den Service des angegebenen Typs zurück. Übergeben Sie als zweites Argument false, gibt sie null zurück, statt eine Exception zu werfen, wenn es keinen solchen Service gibt.
  • hasService(string $name): bool und isCreated(string $name): bool sagen Ihnen, ob ein Service definiert ist und ob er bereits instanziiert wurde.
  • getParameters(): array gibt alle Parameter des Containers zurück, getParameter($key) einen einzelnen.
  • createInstance(string $class, array $args = []): object erzeugt eine neue Instanz der angegebenen Klasse und übergibt ihrem Konstruktor die Abhängigkeiten über Autowiring.
  • callMethod(callable $function, array $args = []): mixed ruft das angegebene Callable auf und übergibt ihm die Argumente über Autowiring.
  • callInjects(object $service): void ruft auf dem angegebenen Objekt alle Methoden inject*() auf und übergibt ihnen die Abhängigkeiten.

Der Konstruktor des Containers nimmt außerdem ein Array von Parametern entgegen, die die in der Konfiguration definierten ergänzen:

$container = new $class(['host' => 'localhost']);

Verwendung mit dem Nette Framework

Wie wir gezeigt haben, ist die Verwendung von Nette DI nicht auf Anwendungen beschränkt, die auf dem Nette Framework aufbauen; Sie können es mit nur drei Zeilen Code überall einbinden. Entwickeln Sie Ihre Anwendungen jedoch mit dem Nette Framework, übernimmt die Konfiguration und das Erzeugen des Containers der Bootstrap.

Version: 3.x