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
)