Crearea extensiilor pentru Nette DI
Generarea containerului DI, pe lângă fișierele de configurare, este influențată și de așa-numitele
extensii. Le activăm în fișierul de configurare în secțiunea extensions
.
Astfel adăugăm extensia reprezentată de clasa BlogExtension
sub numele blog
:
extensions:
blog: BlogExtension
Fiecare extensie a compilatorului moștenește de la Nette\DI\CompilerExtension și poate implementa următoarele metode, care sunt apelate succesiv în timpul construirii containerului DI:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Această metodă este apelată prima. Definește schema pentru validarea parametrilor de configurare.
Configurăm extensia în secțiunea al cărei nume este același cu cel sub care a fost adăugată extensia, adică
blog
:
# același nume ca extensia
blog:
postsPerPage: 10
allowComments: false
Creăm o schemă care descrie toate opțiunile de configurare, inclusiv tipurile lor, valorile permise și, eventual, valorile implicite:
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),
]);
}
}
Documentația o găsiți pe pagina Schema. În plus, se poate specifica ce
opțiuni pot fi dinamice folosind
dynamic()
, de ex. Expect::int()->dynamic()
.
Accesăm configurația prin variabila $this->config
, care este un obiect stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Se utilizează pentru adăugarea serviciilor în container. Pentru aceasta se folosește 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']) // sau setCreator()
->addSetup('setLogger', ['@logger']);
}
}
Convenția este de a prefixa serviciile adăugate de extensie cu numele său, pentru a evita conflictele de nume. Acest lucru
îl face metoda prefix()
, deci dacă extensia se numește blog
, serviciul va purta numele
blog.articles
.
Dacă trebuie să redenumim un serviciu, putem crea un alias cu numele original pentru a menține compatibilitatea
retroactivă. Nette face acest lucru similar, de exemplu, pentru serviciul routing.router
, care este disponibil și
sub numele anterior router
.
$builder->addAlias('router', 'routing.router');
Încărcarea serviciilor din fișier
Serviciile nu trebuie create doar folosind API-ul clasei ContainerBuilder, ci și prin sintaxa cunoscută utilizată în
fișierul de configurare NEON în secțiunea services. Prefixul @extension
reprezintă extensia curentă.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Încărcăm serviciile:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// încărcarea fișierului de configurare pentru extensie
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
Metoda este apelată în momentul în care containerul conține toate serviciile adăugate de extensiile individuale în
metodele loadConfiguration
și, de asemenea, de fișierele de configurare ale utilizatorului. În această fază a
construirii, putem deci modifica definițiile serviciilor sau completa legăturile dintre ele. Pentru căutarea serviciilor în
container după tag-uri se poate utiliza metoda findByTag()
, iar după clasă sau interfață, metoda
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()
În această fază, clasa containerului este deja generată sub forma unui obiect ClassType, conține toate metodele care creează servicii și este pregătită pentru scrierea în cache. Putem încă modifica codul rezultat al clasei în acest moment.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
Clasa Configurator, după crearea containerului,
apelează codul de inițializare, care se creează prin scrierea în obiectul $this->initialization
folosind metoda addBody().
Vom arăta un exemplu despre cum, de exemplu, să pornim sesiunea cu codul de inițializare sau să rulăm servicii care au
tag-ul run
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// pornirea automată a sesiunii
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// serviciile cu tag-ul run trebuie create după instanțierea containerului
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}