Ustvarjanje razširitev za Nette DI
Generiranje DI vsebnika poleg konfiguracijskih datotek vplivajo še t.i. razširitve. Aktiviramo jih
v konfiguracijski datoteki v sekciji extensions
.
Tako dodamo razširitev, predstavljeno z razredom BlogExtension
, pod imenom blog
:
extensions:
blog: BlogExtension
Vsaka razširitev kompilerja deduje od Nette\DI\CompilerExtension in lahko implementira naslednje metode, ki so postopoma klicane med sestavljanjem DI vsebnika:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Ta metoda se kliče prva. Definira shemo za validacijo konfiguracijskih parametrov.
Razširitev konfiguriramo v sekciji, katere ime je enako tistemu, pod katerim je bila razširitev dodana, torej
blog
:
# enako ime kot ima extension
blog:
postsPerPage: 10
allowComments: false
Ustvarimo shemo, ki opisuje vse konfiguracijske možnosti, vključno z njihovimi tipi, dovoljenimi vrednostmi in po potrebi tudi privzetimi vrednostmi:
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),
]);
}
}
Dokumentacijo najdete na strani Shema. Poleg tega lahko določimo, katere
možnosti so lahko dinamične z uporabo
dynamic()
, npr. Expect::int()->dynamic()
.
Do konfiguracije dostopamo prek spremenljivke $this->config
, ki je objekt stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Uporablja se za dodajanje storitev v vsebnik. Za to služi 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']) // or setCreator()
->addSetup('setLogger', ['@logger']);
}
}
Konvencija je, da storitve, dodane z razširitvijo, predponamo z njenim imenom, da ne pride do konflikta imen. To počne
metoda prefix()
, tako da če se razširitev imenuje blog
, bo storitev nosila ime
blog.articles
.
Če moramo storitev preimenovati, lahko zaradi ohranjanja povratne združljivosti ustvarimo alias s prvotnim imenom. Podobno
to počne Nette npr. pri storitvi routing.router
, ki je dostopna tudi pod prejšnjim imenom router
.
$builder->addAlias('router', 'routing.router');
Nalaganje storitev iz datoteke
Storitve ne ustvarjamo samo z API-jem razreda ContainerBuilder, ampak tudi z znanim zapisom, uporabljenim v konfiguracijski
datoteki NEON v sekciji services. Predpona @extension
predstavlja trenutno razširitev.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Storitve naložimo:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// nalaganje konfiguracijske datoteke za razširitev
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
Metoda se kliče v trenutku, ko vsebnik vsebuje vse storitve, dodane z posameznimi razširitvami v metodah
loadConfiguration
in tudi z uporabniškimi konfiguracijskimi datotekami. V tej fazi sestavljanja torej lahko
definicije storitev urejamo ali dopolnimo povezave med njimi. Za iskanje storitev v vsebniku po oznakah lahko uporabimo metodo
findByTag()
, po razredu ali vmesniku pa metodo 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()
V tej fazi je razred vsebnika že generiran v obliki objekta ClassType, vsebuje vse metode, ki ustvarjajo storitve, in je pripravljen za zapis v predpomnilnik. Rezultatno kodo razreda lahko v tej točki še vedno urejamo.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
Razred Configurator po ustvarjanju vsebnika kliče
inicializacijsko kodo, ki se ustvarja z zapisom v objekt $this->initialization
z uporabo metode addBody().
Pokažimo si primer, kako na primer z inicializacijsko kodo zagnati sejo ali zagnati storitve, ki imajo oznako
run
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// samodejni zagon seje
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// storitve z oznako run morajo biti ustvarjene po instanciranju vsebnika
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}