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:

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