Fábricas generadas
Nette DI puede generar automáticamente código de fábrica basado en la interfaz, lo que te ahorra escribir código.
Una fábrica es una clase que crea y configura objetos. Por lo tanto, también les pasa sus dependencias. Por favor, no confundir con el patrón de diseño método de fábrica, que describe una forma específica de utilizar las fábricas y no está relacionado con este tema.
Hemos mostrado cómo es una fábrica de este tipo 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 código de fábrica automáticamente. Todo lo que tiene que hacer es crear una interfaz y Nette DI
generará una implementación. La interfaz debe tener exactamente un método llamado create
y declarar un tipo de
retorno:
interface ArticleFactory
{
function create(): Article;
}
Así que la fábrica ArticleFactory
tiene un método create
que crea objetos Article
. La
clase Article
puede tener, por ejemplo, el siguiente aspecto:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Añade la factoría al fichero de configuración:
services:
- ArticleFactory
Nette DI generará la implementación de la fábrica correspondiente.
Así, en el código que utiliza la fábrica, solicitamos el objeto por interfaz y Nette DI utiliza la implementación generada:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// let the factory create an object
$article = $this->articleFactory->create();
}
}
Fábrica parametrizada
El método de fábrica create
puede aceptar parámetros que luego pasa al constructor. Por ejemplo, vamos a
añadir un ID de autor de artículo a la clase Article
:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
También añadiremos el parámetro a la fábrica:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Como el parámetro en el constructor y el parámetro en la fábrica tienen el mismo nombre, Nette DI los pasará automáticamente.
Definición avanzada
La definición también se puede escribir en forma multilínea utilizando la clave implement
:
services:
articleFactory:
implement: ArticleFactory
Cuando se escribe de esta forma más larga, es posible proporcionar argumentos adicionales para el constructor en la clave
arguments
y configuración adicional usando setup
, igual que para 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 Article
:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
O, por el contrario, si create()
aceptara el parámetro $authorId
pero no formara parte del
constructor y fuera pasado por el método Article::setAuthorId()
, haríamos referencia 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. Un accessor es un objeto con el método
get()
que devuelve un servicio concreto del contenedor DI. Múltiples llamadas a get()
siempre
devolverán la misma instancia.
Accessors trae lazy-loading a las dependencias. Tengamos una clase que registra errores en una base de datos especial. Si la
conexión a la base de datos se pasara como una dependencia en su constructor, la conexión tendría que crearse siempre, aunque
sólo se utilizaría en raras ocasiones cuando apareciera un error, por lo que la conexión no se utilizaría. En su lugar, la
clase puede pasar un accessor y cuando se llame a su método get()
, sólo entonces se creará el objeto de la base
de datos:
¿Cómo crear un accessor? Escriba sólo una interfaz y Nette DI generará la implementación. La interfaz debe tener
exactamente un método llamado get
y debe declarar el tipo de retorno:
interface PDOAccessor
{
function get(): PDO;
}
Añade el accessor al fichero de configuración junto con la definición del servicio que devolverá el accessor:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
El accesor devuelve un servicio de tipo PDO
y como sólo hay un servicio de este tipo en la configuración, el
accesor lo devolverá. Con múltiples servicios configurados de ese tipo puedes especificar cuál debe ser devuelto usando su
nombre, por ejemplo - PDOAccessor(@db1)
.
Multifactory/Accessor
Hasta ahora, los factories (fábricas) y los accessors sólo podían crear o devolver un único objeto. También se puede
crear un multifactory combinado con un accessor. La interfaz de esta clase multifactory puede consistir en múltiples métodos
llamados create<name>()
y get<name>()
, por ejemplo:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
En lugar de pasar múltiples fábricas y accessors generados, puedes pasar un único multifactory complejo.
También puede utilizar get()
con un parámetro en lugar de varios métodos:
interface MultiFactoryAlt
{
function get($name): PDO;
}
En este caso, MultiFactory::getArticle()
hace lo mismo que MultiFactoryAlt::get('article')
. Sin
embargo, la sintaxis alternativa tiene algunas desventajas. No está claro qué valores de $name
son compatibles y el
tipo de retorno no se puede especificar en la interfaz cuando se utilizan varios valores diferentes de $name
.
Definición con una lista
De esta forma se puede definir una fábrica múltiple en la configuración:
services:
- MultiFactory(
article: Article # defines createArticle()
db: PDO(%dsn%, %user%, %password%) # defines getDb()
)
O bien, en la definición de la fábrica, podemos hacer referencia a los servicios existentes utilizando una referencia:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # defines createArticle()
db: @\PDO # defines getDb()
)
Definición con etiquetas
Otra opción para definir un multifactory es utilizar tags:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)