Tworzenie rozszerzeń dla Nette DI
Oprócz plików konfiguracyjnych, na generowanie kontenera DI mają wpływ również tzw. extensions. Są
one aktywowane w pliku konfiguracyjnym w sekcji extensions
.
W ten sposób dodajemy rozszerzenie reprezentowane przez klasę BlogExtension
pod nazwą blog
:
extensions:
blog: BlogExtension
Każde rozszerzenie kompilatora dziedziczy po Nette\DI\CompilerExtension i może implementować następujące metody, które są wywoływane sekwencyjnie w miarę budowania kontenera DI:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Ta metoda jest wywoływana jako pierwsza. Definiuje on schemat walidacji parametrów konfiguracyjnych.
Rozszerzenie jest konfigurowane w sekcji, której nazwa jest taka sama jak ta, pod którą rozszerzenie zostało dodane, czyli
blog
:
# ta sama nazwa co rozszerzenie
blog:
postsPerPage: 10
allowComments: false
Tworzymy schemat opisujący wszystkie opcje konfiguracyjne wraz z ich typami, dozwolonymi wartościami oraz, jeśli to konieczne, wartościami domyślnymi:
use Nette\Schema\Expect;
class BlogExtension extends Nette\DI\CompilerExtension
{
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::structure([
'postsPerPage' => Expect::int(),
'allowComments' => Expect::bool()->default(true),
]);
}
}
Zobacz stronę Schemat, aby uzyskać dokumentację. Dodatkowo można określić,
które opcje mogą być dynamiczne za pomocą
dynamic()
, np. Expect::int()->dynamic()
.
Dostęp do konfiguracji odbywa się poprzez zmienną $this->config
, która jest obiektem
stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Służy do dodawania usług do kontenera. Aby to zrobić, należy użyć Nette\DI\ContainerBuilder:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$builder->addDefinition($this->prefix('articles'))
->setFactory(App\Model\HomepageArticles::class, ['@connection']) //lub setCreator()
->addSetup('setLogger', ['@logger']);
}
}
Konwencją jest poprzedzanie usług dodawanych przez rozszerzenie jego nazwą, aby uniknąć konfliktów nazw. Odbywa się to
za pomocą metody prefix()
, więc jeśli rozszerzenie ma nazwę blog
, usługa będzie miała nazwę
blog.articles
.
Jeśli musimy zmienić nazwę usługi, możemy stworzyć alias z oryginalną nazwą, aby zachować kompatybilność wsteczną.
Nette robi podobnie na przykład w przypadku serwisu routing.router
, który jest dostępny również pod dawną
nazwą router
.
$builder->addAlias('router', 'routing.router');
Ładowanie usług z pliku
Usługi muszą być tworzone nie tylko przy użyciu API klasy ContainerBuilder, ale także przy użyciu znanej notacji
stosowanej w pliku konfiguracyjnym NEON w sekcji services. Przedrostek @extension
reprezentuje aktualne
rozszerzenie.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Ładujemy usługi:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// načtení konfiguračního souboru pro rozšíření
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
Metoda jest wywoływana, gdy kontener zawiera wszystkie usługi dodane przez poszczególne rozszerzenia w metodach
loadConfiguration
, a także pliki konfiguracyjne użytkownika. Dzięki temu na tym etapie budowania możemy edytować
definicje usług lub dodawać wiązania między nimi. Do wyszukiwania usług w kontenerze po znacznikach można użyć metody
findByTag()
, a po klasie lub interfejsie – metody findByType()
.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('logaware') as $serviceName => $tagValue) {
$builder->getDefinition($serviceName)->addSetup('setLogger');
}
}
}
afterCompile()
Na tym etapie klasa kontenera jest już wygenerowana jako obiekt ClassType, zawiera wszystkie metody tworzące usługi i jest gotowa do zapisania w pamięci podręcznej. W tym momencie możemy jeszcze zmodyfikować wynikowy kod klasy.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$inicjalizacja
Po utworzeniu kontenera klasa Configurator wywołuje
kod inicjalizacyjny, który powstaje poprzez zapis do obiektu $this->initialization
za pomocą metody addBody().
Pokażemy przykład, jak np. kod inicjalizujący może uruchomić sesję lub uruchomić usługi, które mają znacznik
run
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// automatyczny start sesji
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// služby s tagem run musejí být vytvořeny po instancování kontejneru
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}