Fábricas Geradas

Nette DI pode gerar automaticamente o código de fábrica com base na interface, o que poupa você de escrever o código.

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

Mostramos como é uma fábrica desse tipo 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 código de fábrica automaticamente. Tudo que você precisa fazer é criar uma interface e a Nette DI irá gerar uma implementação. A interface deve ter exatamente um método chamado create e declarar um tipo de retorno:

interface ArticleFactory
{
	function create(): Article;
}

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

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

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

services:
	- ArticleFactory

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

Assim, no código que utiliza a fábrica, solicitamos o objeto por interface e a Nette DI utiliza a implementação gerada:

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

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

Fábrica parametrizada

O método de fábrica create pode aceitar parâmetros que depois passa para o construtor. Por exemplo, vamos adicionar uma identificação do autor do artigo à classe Article:

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

Acrescentaremos também o parâmetro à fábrica:

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

Como o parâmetro no construtor e o parâmetro na fábrica têm o mesmo nome, a Nette DI os passará automaticamente.

Definição avançada

A definição também pode ser escrita em várias linhas usando a chave implement:

services:
	articleFactory:
		implement: ArticleFactory

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

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 Article:

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

Ou, inversamente, se create() aceitasse o parâmetro $authorId mas não fizesse parte do construtor e fosse aprovado pelo método Article::setAuthorId(), referir-nos-íamos 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 acessores. O acessor é um objeto com método get() devolvendo um determinado serviço a partir do contêiner DI. Várias chamadas get() retornarão sempre a mesma instância.

Os acessores trazem a carga preguiçosa para as dependências. Vamos ter um registro de erros de classe em um banco de dados especial. Se a conexão de banco de dados fosse passada como uma dependência em seu construtor, a conexão precisaria ser sempre criada, embora só fosse usada raramente quando um erro aparecesse, de modo que a conexão permaneceria, na maioria das vezes, sem uso. Em vez disso, a classe pode passar por um acessor e quando seu método get() é chamado, somente então o objeto banco de dados é criado:

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

interface PDOAccessor
{
	function get(): PDO;
}

Adicione o acessor ao arquivo de configuração junto com a definição do serviço que o acessor retornará:

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

O acessor devolve um serviço do tipo PDO e como só existe um serviço desse tipo na configuração, o acessor o devolverá. Com vários serviços desse tipo configurados, você pode especificar qual deles deve ser devolvido usando seu nome, por exemplo - PDOAccessor(@db1).

Multifábrica/Acessor

Até agora, as fábricas e os acessores só podiam criar ou devolver um único objeto. Uma multifábrica combinada com um acessório também pode ser criada. A interface de tal classe multifatorial pode consistir de múltiplos métodos chamados create<name>() e get<name>()por exemplo:

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

Em vez de passar por múltiplas fábricas e acessores gerados, você pode passar apenas por uma complexa multifábrica.

Como alternativa, você pode usar get() com um parâmetro em vez de vários métodos:

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

Nesse caso, MultiFactory::getArticle() faz a mesma coisa que MultiFactoryAlt::get('article'). Entretanto, a sintaxe alternativa tem algumas desvantagens. Não está claro quais valores de $name são compatíveis e o tipo de retorno não pode ser especificado na interface ao usar vários valores diferentes de $name.

Definição com uma lista

Essa maneira pode ser usada para definir uma fábrica múltipla na configuração:

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

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

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

Definição com tags

Outra opção para definir uma multifábrica é a utilização de etiquetas:

services:
	- App\Router\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.explorer
	)
versão: 3.x