Creazione di estensioni per Nette DI

La generazione del container DI, oltre ai file di configurazione, è influenzata anche dalle cosiddette estensioni. Le attiviamo nel file di configurazione nella sezione extensions.

In questo modo aggiungiamo l'estensione rappresentata dalla classe BlogExtension con il nome blog:

extensions:
	blog: BlogExtension

Ogni estensione del compilatore eredita da Nette\DI\CompilerExtension e può implementare i seguenti metodi, che vengono chiamati in sequenza durante la costruzione del container DI:

  1. getConfigSchema()
  2. loadConfiguration()
  3. beforeCompile()
  4. afterCompile()

getConfigSchema()

Questo metodo viene chiamato per primo. Definisce lo schema per la validazione dei parametri di configurazione.

Configuriamo l'estensione nella sezione il cui nome è lo stesso di quello con cui è stata aggiunta l'estensione, cioè blog:

# stesso nome dell'estensione
blog:
	postsPerPage: 10
	allowComments: false

Creiamo uno schema che descrive tutte le opzioni di configurazione, inclusi i loro tipi, valori consentiti ed eventualmente anche valori predefiniti:

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),
		]);
	}
}

La documentazione si trova nella pagina Schema. Inoltre, è possibile specificare quali opzioni possono essere dinamiche usando dynamic(), ad es. Expect::int()->dynamic().

Accediamo alla configurazione tramite la variabile $this->config, che è un oggetto stdClass:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		$num = $this->config->postPerPage;
		if ($this->config->allowComments) {
			// ...
		}
	}
}

loadConfiguration()

Utilizzato per aggiungere servizi al container. A questo serve 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']);
	}
}

La convenzione è di prefissare i servizi aggiunti dall'estensione con il suo nome, per evitare conflitti di nomi. Questo lo fa il metodo prefix(), quindi se l'estensione si chiama blog, il servizio si chiamerà blog.articles.

Se dobbiamo rinominare un servizio, possiamo creare un alias con il nome originale per mantenere la compatibilità all'indietro. Nette fa qualcosa di simile, ad esempio, con il servizio routing.router, che è disponibile anche con il nome precedente router.

$builder->addAlias('router', 'routing.router');

Caricamento dei servizi da file

Non dobbiamo creare servizi solo tramite l'API della classe ContainerBuilder, ma anche con la nota sintassi utilizzata nel file di configurazione NEON nella sezione services. Il prefisso @extension rappresenta l'estensione corrente.

services:
	articles:
		create: MyBlog\ArticlesModel(@connection)

	comments:
		create: MyBlog\CommentsModel(@connection, @extension.articles)

	articlesList:
		create: MyBlog\Components\ArticlesList(@extension.articles)

Carichiamo i servizi:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		$builder = $this->getContainerBuilder();

		// caricamento del file di configurazione per l'estensione
		$this->compiler->loadDefinitionsFromConfig(
			$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
		);
	}
}

beforeCompile()

Il metodo viene chiamato nel momento in cui il container contiene tutti i servizi aggiunti dalle singole estensioni nei metodi loadConfiguration e anche dai file di configurazione utente. In questa fase di costruzione, possiamo quindi modificare le definizioni dei servizi o aggiungere legami tra di essi. Per cercare servizi nel container in base ai tag, si può utilizzare il metodo findByTag(), per classe o interfaccia invece il 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()

In questa fase, la classe del container è già generata sotto forma di oggetto ClassType, contiene tutti i metodi che creano i servizi ed è pronta per essere scritta nella cache. Possiamo ancora modificare il codice risultante della classe in questo momento.

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function afterCompile(Nette\PhpGenerator\ClassType $class)
	{
		$method = $class->getMethod('__construct');
		// ...
	}
}

$initialization

La classe Configurator, dopo la creazione del container, chiama il codice di inizializzazione, che viene creato scrivendo nell'oggetto $this->initialization tramite il metodo addBody().

Mostriamo un esempio di come, ad esempio, avviare la sessione con il codice di inizializzazione o avviare servizi che hanno il tag run:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		// avvio automatico della sessione
		if ($this->config->session->autoStart) {
			$this->initialization->addBody('$this->getService("session")->start()');
		}

		// i servizi con il tag run devono essere creati dopo l'istanza del container
		$builder = $this->getContainerBuilder();
		foreach ($builder->findByTag('run') as $name => $foo) {
			$this->initialization->addBody('$this->getService(?);', [$name]);
		}
	}
}
versione: 3.x