Criação de extensões para Nette DI

A geração de um recipiente DI, além dos arquivos de configuração, também afeta os chamados extensões. Nós as ativamos no arquivo de configuração na seção extensions.

É assim que adicionamos a extensão representada pela classe BlogExtension com o nome blog:

extensions:
	blog: BlogExtension

Cada extensão do compilador herda de Nette\DI\CompilerExtension e pode implementar os seguintes métodos que são chamados durante a compilação DI:

  1. getConfigSchema()
  2. cargaConfiguração()
  3. Antes da Compilação()
  4. depois da Compilação()

getConfigSchema()

Este método é chamado primeiro. Ele define o esquema utilizado para validar os parâmetros de configuração.

As extensões são configuradas em uma seção cujo nome é o mesmo que aquele sob o qual a extensão foi adicionada, por exemplo blog.

# same name as my extension
blog:
	postsPerPage: 10
	comments: false

Definiremos um esquema descrevendo todas as opções de configuração, incluindo seus tipos, valores aceitos e possivelmente valores padrão:

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

Consulte o Esquema para documentação. Além disso, você pode especificar quais opções podem ser dinâmicas usando dynamic(), por exemplo Expect::int()->dynamic().

Acessamos a configuração através do $this->config, que é um objeto stdClass:

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

loadConfiguration()

Este método é usado para adicionar serviços ao contêiner. Isto é feito por 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']);
	}
}

A convenção é para prefixar os serviços acrescentados por uma extensão com seu nome, para que não surjam conflitos de nome. Isto é feito por prefix(), portanto, se a extensão for chamada de ‘blog’, o serviço será chamado blog.articles.

Se precisarmos renomear um serviço, podemos criar um pseudônimo com seu nome original para manter a retrocompatibilidade. Da mesma forma, é o que a Nette faz por exemplo routing.router, que também está disponível sob o nome anterior router.

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

Recuperar serviços de um arquivo

Podemos criar serviços utilizando o ContainerBuilder API, mas também podemos adicioná-los através do familiar arquivo de configuração NEON e sua seção services. O prefixo @extension representa a extensão atual.

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

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

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

Desta forma, acrescentaremos serviços:

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

		// carregar o arquivo de configuração para a extensão
		$this->compiler->loadDefinitionsFromConfig(
			$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
		);
	}
}

beforeCompile()

O método é chamado quando o contêiner contém todos os serviços adicionados por extensões individuais em loadConfiguration métodos, bem como arquivos de configuração do usuário. Nesta fase de montagem, podemos então modificar as definições dos serviços ou adicionar links entre eles. Você pode usar o método findByTag() para procurar serviços por tags, ou o método findByType() para procurar por classe ou interface.

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()

Nesta fase, a classe container já é gerada como um objeto ClassType, ela contém todos os métodos que o serviço cria, e está pronta para o caching como arquivo PHP. Neste ponto, ainda podemos editar o código da classe resultante.

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

$inicialização

O Configurador chama o código de inicialização após a criação do recipiente, que é criado escrevendo para um objeto $this->initialization usando o método addBody().

Mostraremos um exemplo de como iniciar uma sessão ou iniciar serviços que tenham a tag run usando o código de inicialização:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		// inicialização automática da sessão
		if ($this->config->session->autoStart) {
			$this->initialization->addBody('$this->getService("session")->start()');
		}

		// serviços com etiqueta 'run' devem ser criados depois que o container for instanciado
		$builder = $this->getContainerBuilder();
		foreach ($builder->findByTag('run') as $name => $foo) {
			$this->initialization->addBody('$this->getService(?);', [$name]);
		}
	}
}
versão: 3.x