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:

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