Kiterjesztések készítése a Nette DI-hez
A DI konténer generálását a konfigurációs fájlokon kívül az úgynevezett kiterjesztések is
befolyásolják. Ezeket a konfigurációs fájl extensions
szekciójában aktiváljuk.
Így adjuk hozzá a BlogExtension
osztály által reprezentált kiterjesztést blog
néven:
extensions:
blog: BlogExtension
Minden compiler kiterjesztés a Nette\DI\CompilerExtension-ből öröklődik, és implementálhatja a következő metódusokat, amelyeket a DI konténer összeállítása során sorban hívnak meg:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Ez a metódus hívódik meg először. Definiálja a sémát a konfigurációs paraméterek validálásához.
A kiterjesztést abban a szekcióban konfiguráljuk, amelynek neve megegyezik azzal, amely alatt a kiterjesztést
hozzáadták, tehát blog
:
# ugyanaz a név, mint a kiterjesztésé
blog:
postsPerPage: 10
allowComments: false
Létrehozunk egy sémát, amely leírja az összes konfigurációs opciót, beleértve azok típusait, megengedett értékeit és esetleg alapértelmezett értékeit is:
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),
]);
}
}
A dokumentációt a Schema oldalon találja. Ezenkívül meg lehet határozni,
mely opciók lehetnek dinamikusak a
dynamic()
segítségével, pl. Expect::int()->dynamic()
.
A konfigurációhoz a $this->config
változón keresztül férünk hozzá, amely egy stdClass
objektum:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Szolgáltatások hozzáadására szolgál a konténerhez. Erre a Nette\DI\ContainerBuilder szolgál:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$builder->addDefinition($this->prefix('articles'))
->setFactory(App\Model\HomepageArticles::class, ['@connection']) // vagy setCreator()
->addSetup('setLogger', ['@logger']);
}
}
A konvenció az, hogy a kiterjesztés által hozzáadott szolgáltatásokat annak nevével prefixeljük, hogy ne keletkezzenek
névütközések. Ezt a prefix()
metódus teszi, tehát ha a kiterjesztés neve blog
, a szolgáltatás
neve blog.articles
lesz.
Ha át kell neveznünk egy szolgáltatást, a visszamenőleges kompatibilitás megőrzése érdekében létrehozhatunk egy
aliast az eredeti névvel. Hasonlóan teszi ezt a Nette például a routing.router
szolgáltatásnál, amely a
korábbi router
néven is elérhető.
$builder->addAlias('router', 'routing.router');
Szolgáltatások betöltése fájlból
A szolgáltatásokat nem csak a ContainerBuilder osztály API-ján keresztül hozhatjuk létre, hanem a konfigurációs
fájlban a services szekcióban használt ismert írásmóddal is. Az @extension
prefix az aktuális kiterjesztést
jelenti.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
A szolgáltatásokat betöltjük:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// a kiterjesztés konfigurációs fájljának betöltése
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
A metódus akkor hívódik meg, amikor a konténer tartalmazza az összes, az egyes kiterjesztések által a
loadConfiguration
metódusokban hozzáadott szolgáltatást, valamint a felhasználói konfigurációs fájlokból
származókat is. Az összeállítás ezen szakaszában tehát módosíthatjuk a szolgáltatásdefiníciókat, vagy
kiegészíthetjük a köztük lévő kapcsolatokat. A szolgáltatások konténerben való kereséséhez tagek alapján a
findByTag()
metódust, osztály vagy interfész alapján pedig a findByType()
metódust
használhatjuk.
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()
Ebben a fázisban a konténer osztálya már ClassType objektum formájában van generálva, tartalmazza az összes metódust, amely szolgáltatásokat hoz létre, és készen áll a cache-be írásra. Az eredményül kapott osztálykódot ebben a pillanatban még módosíthatjuk.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
A Configurator osztály a konténer létrehozása
után meghívja az inicializációs kódot, amely a $this->initialization
objektumba való írással jön létre a
addBody() metódusával segítségével.
Mutatunk egy példát, hogyan indíthatjuk el például a sessiont inicializációs kóddal, vagy futtathatunk olyan
szolgáltatásokat, amelyeknek run
tagjük van:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// session automatikus indítása
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// a run taggel rendelkező szolgáltatásokat a konténer példányosítása után kell létrehozni
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}