Fábricas generadas
Nette DI puede generar automáticamente código de fábrica basado en interfaces, lo que le ahorra escribir código.
Una fábrica es una clase que produce y configura objetos. Por lo tanto, también les pasa sus dependencias. Por favor, no lo confunda con el patrón de diseño factory method, que describe una forma específica de usar fábricas y no está relacionado con este tema.
Mostramos cómo se ve una fábrica así en el capítulo introductorio:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Nette DI puede generar automáticamente el código de las fábricas. Todo lo que tiene que hacer es crear una interfaz y Nette
DI generará la implementación. La interfaz debe tener exactamente un método llamado create
y declarar un tipo de
retorno:
interface ArticleFactory
{
function create(): Article;
}
Es decir, la fábrica ArticleFactory
tiene un método create
que crea objetos Article
.
La clase Article
podría verse así:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Agregamos la fábrica al archivo de configuración:
services:
- ArticleFactory
Nette DI generará la implementación correspondiente de la fábrica.
En el código que usa la fábrica, solicitamos el objeto según la interfaz y Nette DI usará la implementación generada:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// dejamos que la fábrica cree el objeto
$article = $this->articleFactory->create();
}
}
Fábrica parametrizada
El método de fábrica create
puede aceptar parámetros, que luego pasa al constructor. Agreguemos, por ejemplo, a
la clase Article
el ID del autor del artículo:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
También agregamos el parámetro a la fábrica:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Gracias a que el parámetro en el constructor y el parámetro en la fábrica tienen el mismo nombre, Nette DI los pasa de forma completamente automática.
Definición avanzada
La definición también se puede escribir en forma multilínea usando la clave implement
:
services:
articleFactory:
implement: ArticleFactory
Al escribir de esta manera más larga, es posible especificar argumentos adicionales para el constructor en la clave
arguments
y configuración adicional usando setup
, al igual que con los servicios normales.
Ejemplo: si el método create()
no aceptara el parámetro $authorId
, podríamos especificar un valor
fijo en la configuración, que se pasaría al constructor de Article
:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
O viceversa, si create()
aceptara el parámetro $authorId
, pero no fuera parte del constructor y se
pasara mediante el método Article::setAuthorId()
, nos referiríamos a él en la sección setup
:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
Además de las fábricas, Nette también puede generar los llamados accessors. Son objetos con un método get()
que devuelve un servicio específico del contenedor DI. Las llamadas repetidas a get()
devuelven siempre la misma
instancia.
Los accessors proporcionan carga diferida (lazy-loading) a las dependencias. Supongamos que tenemos una clase que escribe
errores en una base de datos especial. Si esta clase recibiera la conexión a la base de datos como dependencia a través del
constructor, la conexión siempre tendría que crearse, aunque en la práctica un error ocurre solo excepcionalmente y, por lo
tanto, la conexión permanecería mayormente sin usar. En lugar de eso, la clase recibe un accessor y solo cuando se llama a su
get()
, se crea el objeto de la base de datos:
¿Cómo crear un accessor? Simplemente escriba una interfaz y Nette DI generará la implementación. La interfaz debe tener
exactamente un método llamado get
y declarar un tipo de retorno:
interface PDOAccessor
{
function get(): PDO;
}
Agregamos el accessor al archivo de configuración, donde también está la definición del servicio que devolverá:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Dado que el accessor devuelve un servicio de tipo PDO
y en la configuración hay un único servicio de este tipo,
devolverá precisamente ese. Si hubiera más servicios del tipo dado, especificaríamos el servicio devuelto usando el nombre, por
ejemplo, - PDOAccessor(@db1)
.
Fábrica/Accessor múltiple
Hasta ahora, nuestras fábricas y accessors siempre podían producir o devolver solo un objeto. Pero también es muy fácil
crear fábricas múltiples combinadas con accessors. La interfaz de tal clase contendrá cualquier número de métodos con los
nombres create<name>()
y get<name>()
, por ejemplo:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Así que en lugar de pasar varias fábricas y accessors generados, pasamos una fábrica más compleja que puede hacer más cosas.
Alternativamente, en lugar de varios métodos, se puede usar get()
con un parámetro:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Entonces se cumple que MultiFactory::getArticle()
hace lo mismo que MultiFactoryAlt::get('article')
.
Sin embargo, la notación alternativa tiene la desventaja de que no está claro qué valores de $name
son compatibles
y, lógicamente, tampoco es posible distinguir diferentes valores de retorno para diferentes $name
en la
interfaz.
Definición por lista
De esta manera se puede definir una fábrica múltiple en la configuración:
services:
- MultiFactory(
article: Article # define createArticle()
db: PDO(%dsn%, %user%, %password%) # define getDb()
)
O podemos referirnos a servicios existentes en la definición de la fábrica usando una referencia:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # define createArticle()
db: @\PDO # define getDb()
)
Definición mediante tags
La segunda opción es usar tags para la definición:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)