Fábricas Geradas

A Nette DI pode gerar automaticamente código de fábricas com base em interfaces, o que economiza a escrita de código.

Uma fábrica é uma classe que produz e configura objetos. Portanto, ela também passa suas dependências para eles. Por favor, não confunda com o padrão de projeto factory method, que descreve uma maneira específica de usar fábricas e não está relacionado a este tópico.

Mostramos como é uma fábrica no capítulo introdutório:

class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}

A Nette DI pode gerar automaticamente o código das fábricas. Tudo o que você precisa fazer é criar uma interface e a Nette DI gerará a implementação. A interface deve ter exatamente um método chamado create e declarar o tipo de retorno:

interface ArticleFactory
{
	function create(): Article;
}

Ou seja, a fábrica ArticleFactory tem um método create que cria objetos Article. A classe Article pode se parecer com o seguinte:

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}
}

Adicionamos a fábrica ao arquivo de configuração:

services:
	- ArticleFactory

A Nette DI gerará a implementação correspondente da fábrica.

No código que usa a fábrica, solicitamos o objeto pela interface e a Nette DI usará a implementação gerada:

class UserController
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function foo()
	{
		// deixamos a fábrica criar o objeto
		$article = $this->articleFactory->create();
	}
}

Fábrica Parametrizada

O método da fábrica create pode aceitar parâmetros, que são então passados para o construtor. Vamos adicionar, por exemplo, o ID do autor do artigo à classe Article:

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
		private int $authorId,
	) {
	}
}

Também adicionamos o parâmetro à fábrica:

interface ArticleFactory
{
	function create(int $authorId): Article;
}

Graças ao fato de que o parâmetro no construtor e o parâmetro na fábrica têm o mesmo nome, a Nette DI os passa de forma totalmente automática.

Definição Avançada

A definição também pode ser escrita em formato de múltiplas linhas usando a chave implement:

services:
	articleFactory:
		implement: ArticleFactory

Ao escrever desta forma mais longa, é possível especificar argumentos adicionais para o construtor na chave arguments e configuração adicional usando setup, assim como nos serviços comuns.

Exemplo: se o método create() não aceitasse o parâmetro $authorId, poderíamos especificar um valor fixo na configuração, que seria passado para o construtor de Article:

services:
	articleFactory:
		implement: ArticleFactory
		arguments:
			authorId: 123

Ou, inversamente, se create() aceitasse o parâmetro $authorId, mas ele não fizesse parte do construtor e fosse passado pelo método Article::setAuthorId(), faríamos referência a ele na seção setup:

services:
	articleFactory:
		implement: ArticleFactory
		setup:
			- setAuthorId($authorId)

Accessor

Além das fábricas, a Nette também pode gerar os chamados accessors. São objetos com um método get(), que retorna um determinado serviço do contêiner DI. Chamadas repetidas de get() retornam sempre a mesma instância.

Os accessors fornecem carregamento preguiçoso (lazy-loading) para dependências. Considere uma classe que registra erros em um banco de dados especial. Se essa classe recebesse a conexão com o banco de dados como dependência via construtor, a conexão sempre teria que ser criada, embora na prática um erro ocorra apenas excepcionalmente e, portanto, na maioria das vezes a conexão permaneceria inutilizada. Em vez disso, a classe recebe um accessor e somente quando seu get() é chamado, o objeto do banco de dados é criado:

Como criar um accessor? Basta escrever uma interface e a Nette DI gerará a implementação. A interface deve ter exatamente um método chamado get e declarar o tipo de retorno:

interface PDOAccessor
{
	function get(): PDO;
}

Adicionamos o accessor ao arquivo de configuração, onde também está a definição do serviço que ele retornará:

services:
	- PDOAccessor
	- PDO(%dsn%, %user%, %password%)

Como o accessor retorna um serviço do tipo PDO e há apenas um serviço desse tipo na configuração, ele retornará exatamente esse. Se houvesse mais serviços do tipo fornecido, especificaríamos o serviço retornado usando o nome, por exemplo, - PDOAccessor(@db1).

Fábrica/Accessor Múltiplo

Nossas fábricas e accessors até agora só podiam produzir ou retornar um objeto. No entanto, é muito fácil criar também fábricas múltiplas combinadas сom accessors. A interface de tal classe conterá qualquer número de métodos com os nomes create<name>() e get<name>(), por exemplo:

interface MultiFactory
{
	function createArticle(): Article;
	function getDb(): PDO;
}

Então, em vez de passar várias fábricas e accessors gerados, passamos uma fábrica mais complexa que pode fazer mais.

Alternativamente, em vez de vários métodos, pode-se usar get() сom um parâmetro:

interface MultiFactoryAlt
{
	function get($name): PDO;
}

Então, vale que MultiFactory::createArticle() faz o mesmo que MultiFactoryAlt::get('article'). No entanto, a notação alternativa tem a desvantagem de não ficar claro quais valores de $name são suportados e, logicamente, também não é possível na interface distinguir diferentes valores de retorno para diferentes $name.

Definição por lista

Desta forma, é possível definir uma fábrica múltipla na configuração:

services:
	- MultiFactory(
		article: Article                      # define createArticle()
		db: PDO(%dsn%, %user%, %password%)    # define getDb()
	)

Ou podemos, na definição da fábrica, referir-nos a serviços existentes usando uma referência:

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # define createArticle()
		db: @\PDO            # define getDb()
	)

Definição usando tags

A segunda opção é usar tags para a definição:

services:
	- App\Core\RouterFactory::createRouter # Assumindo que isso é um serviço ou factory
	- App\Model\DatabaseAccessor(
		db1: @database.db1.explorer # Assumindo que existe um serviço com este nome
	)
versão: 3.x