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:

  1. getConfigSchema()
  2. loadConfiguration()
  3. beforeCompile()
  4. 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]);
		}
	}
}
verzió: 3.x