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): ?objectzwraca usługę podanego typu. Jeśli jako drugi argument podaszfalse, przy braku takiej usługi zwrócinullzamiast zgłaszać wyjątek.hasService(string $name): booliisCreated(string $name): boolmówią, czy usługa jest zdefiniowana i czy jej instancja została już utworzona.getParameters(): arrayzwraca wszystkie parametry kontenera,getParameter($key)zwraca pojedynczy.createInstance(string $class, array $args = []): objecttworzy nową instancję podanej klasy i przekazuje zależności jej konstruktora przez autowiring.callMethod(callable $function, array $args = []): mixedwywołuje podany callable i przekazuje jego argumenty przez autowiring.callInjects(object $service): voidwywołuje na podanym obiekcie wszystkie metodyinject*()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.