Nette DI Container

Nette DI to jedna z najciekawszych bibliotek Nette. Potrafi generować i automatycznie aktualizować kompilowane kontenery DI, które są wyjątkowo szybkie i zaskakująco łatwe do skonfigurowania.

Postać usług, które kontener DI ma tworzyć, definiuje się zwykle w plikach konfiguracyjnych w formacie NEON. Kontener, który ręcznie utworzyliśmy w poprzednim rozdziale, zapisalibyśmy tak:

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

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

Składnia jest bardzo zwięzła.

Wszystkie zależności zadeklarowane w konstruktorach klas ArticleFactory i EditController Nette DI odnajduje i przekazuje automatycznie dzięki tak zwanemu autowiringowi, nie trzeba więc niczego podawać w pliku konfiguracyjnym. Nawet jeśli parametry się zmienią, nie musisz niczego zmieniać w konfiguracji. Podczas tworzenia aplikacji Nette automatycznie regeneruje kontener. Możesz skupić się wyłącznie na rozwijaniu aplikacji.

Jeśli chcemy przekazywać zależności przez settery, użyjemy do tego sekcji setup.

Nette DI generuje kod PHP kontenera bezpośrednio. Wynikiem jest więc plik .php, który możesz otworzyć i zbadać. Pozwala to zobaczyć dokładnie, jak kontener działa. Możesz też debugować go w swoim IDE i krokować po jego wykonaniu. A co najważniejsze: wygenerowany kod PHP jest wyjątkowo szybki.

Nette DI potrafi też wygenerować kod fabryki na podstawie podanego interfejsu. Zamiast klasy ArticleFactory wystarczy więc utworzyć w aplikacji interfejs:

interface ArticleFactory
{
	function create(): Article;
}

Pełny przykład znajdziesz na GitHubie.

Użycie samodzielne

Integracja biblioteki Nette DI z aplikacją jest bardzo łatwa. Najpierw instalujemy ją przez Composera (bo pobieranie plików zip jest już takie przestarzałe):

composer require nette/di

Poniższy kod używa Compilera do utworzenia instancji kontenera DI zgodnie z konfiguracją zapisaną w pliku config.neon:

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

Kontener generowany jest tylko raz, jego kod zapisywany jest do cache (katalog __DIR__ . '/temp'), a przy kolejnych żądaniach jest już tylko stamtąd wczytywany.

Sam Compiler włącza w konfiguracji tylko sekcje services i parameters. Aby użyć innych, takich jak search, decorator, di czy inject, zarejestruj najpierw ich rozszerzenia. A aby umożliwić rejestrowanie rozszerzeń z sekcji extensions konfiguracji, dodaj ExtensionsExtension:

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

Configurator używany w pełnych aplikacjach Nette rejestruje je wszystkie automatycznie.

Jeśli trzymasz kilka różnych kontenerów w tym samym katalogu cache, odróżnij je kluczem przekazanym jako drugi argument load(); staje się on częścią nazwy wygenerowanej klasy:

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

Do tworzenia i pobierania usług służą metody getService() albo getByType(). Tak utworzymy obiekt EditController:

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

Podczas tworzenia aplikacji przydaje się włączenie trybu automatycznego odświeżania, w którym kontener regeneruje się automatycznie, gdy zmieni się jakakolwiek klasa albo plik konfiguracyjny. Wystarczy podać true jako drugi argument konstruktora ContainerLoader.

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

Praca z kontenerem

Poza getService() i getByType() obiekt kontenera oferuje kilka innych przydatnych metod:

  • getByType(string $type, bool $throw = true): ?object zwraca usługę podanego typu. Jeśli jako drugi argument podasz false, przy braku takiej usługi zwróci null zamiast zgłaszać wyjątek.
  • hasService(string $name): bool i isCreated(string $name): bool mówią, czy usługa jest zdefiniowana i czy jej instancja została już utworzona.
  • getParameters(): array zwraca wszystkie parametry kontenera, getParameter($key) zwraca pojedynczy.
  • createInstance(string $class, array $args = []): object tworzy nową instancję podanej klasy i przekazuje zależności jej konstruktora przez autowiring.
  • callMethod(callable $function, array $args = []): mixed wywołuje podany callable i przekazuje jego argumenty przez autowiring.
  • callInjects(object $service): void wywołuje na podanym obiekcie wszystkie metody inject*() i przekazuje im zależności.

Konstruktor kontenera przyjmuje też tablicę parametrów, które uzupełniają te zdefiniowane w konfiguracji:

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

Użycie z Nette Framework

Jak pokazaliśmy, użycie Nette DI nie ogranicza się do aplikacji zbudowanych na Nette Framework; możesz zintegrować je gdziekolwiek zaledwie trzema wierszami kodu. Jeśli jednak tworzysz aplikacje z użyciem Nette Framework, konfiguracją i utworzeniem kontenera zajmuje się Bootstrap.

wersja: 3.x