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): ?objectgibt den Service des angegebenen Typs zurück. Übergeben Sie als zweites Argumentfalse, gibt sienullzurück, statt eine Exception zu werfen, wenn es keinen solchen Service gibt.hasService(string $name): boolundisCreated(string $name): boolsagen Ihnen, ob ein Service definiert ist und ob er bereits instanziiert wurde.getParameters(): arraygibt alle Parameter des Containers zurück,getParameter($key)einen einzelnen.createInstance(string $class, array $args = []): objecterzeugt eine neue Instanz der angegebenen Klasse und übergibt ihrem Konstruktor die Abhängigkeiten über Autowiring.callMethod(callable $function, array $args = []): mixedruft das angegebene Callable auf und übergibt ihm die Argumente über Autowiring.callInjects(object $service): voidruft auf dem angegebenen Objekt alle Methodeninject*()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.