Factories generadas
Nette DI puede generar automáticamente el código de las factories a partir de interfaces, lo que le ahorra escribir código.
Una factory es una clase que se encarga de crear objetos y de pasarles sus dependencias. No lo confunda, por favor, con el patrón de diseño factory method, que describe una forma concreta de usar las factories y no está relacionado con este tema.
Ya hemos mostrado qué aspecto tiene una factory 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 la factory. 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 factory ArticleFactory tiene un método create que crea objetos Article. La
clase Article podría tener, por ejemplo, este aspecto:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Añada la factory al archivo de configuración:
services:
- ArticleFactory
Nette DI generará la implementación correspondiente de la factory.
En el código que usa la factory, pida el objeto por su interfaz y Nette DI le proporcionará la implementación generada:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// dejamos que la factory cree el objeto
$article = $this->articleFactory->create();
}
}
Factory parametrizada
El método create de la factory puede aceptar parámetros, que después pasa al constructor. Añadamos, 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,
) {
}
}
Añadiremos el parámetro también a la factory:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Como el nombre del parámetro en el constructor ($authorId) coincide con el nombre del parámetro del método de
la factory, Nette DI lo pasa automáticamente.
Definición avanzada
La definición también se puede escribir en forma de varias líneas con la clave implement:
services:
articleFactory:
implement: ArticleFactory
Usar este formato más largo permite indicar argumentos adicionales para el constructor con la clave arguments y
configurar más cosas con setup, de forma parecida a las definiciones de servicios corrientes.
Ejemplo: si el método create() no aceptara el parámetro $authorId, podríamos indicar en la
configuración un valor fijo que se pasaría al constructor de Article:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Y al revés: si create() aceptara $authorId, pero este no formara parte del constructor y se pasara
mediante un método como Article::setAuthorId(), nos referiríamos al parámetro en la sección
setup:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
Además de las factories, Nette también puede generar los llamados accessors. Son objetos con un método get()
que devuelve un servicio concreto del contenedor DI. Las llamadas repetidas a get() devuelven siempre la misma
instancia.
Los accessors ofrecen carga diferida de las dependencias. Imagine una clase que registra errores en una base de datos dedicada.
Si esa clase recibiera la conexión a la base de datos por inyección en el constructor, la conexión se establecería siempre,
aunque los errores se produzcan rara vez y la conexión quede sin usar la mayor parte del tiempo. En su lugar, la clase puede
recibir un accessor. El objeto de la base de datos (la conexión) se crea solo cuando se llama por primera vez al método
get() del accessor.
¿Cómo se crea un accessor? Basta con escribir una interfaz y Nette DI generará la implementación. La interfaz debe tener
exactamente un método llamado get que no acepte parámetros y declare el tipo de retorno:
interface PDOAccessor
{
function get(): PDO;
}
Añada el accessor al archivo de configuración junto con la definición del servicio que debe devolver:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Como el accessor devuelve un servicio PDO y en la configuración solo hay un servicio así definido, el accessor
devolverá justamente ese servicio. Si existieran varios servicios de ese tipo, indique por su nombre cuál debe devolver el
accessor, p. ej. - PDOAccessor(@db1).
Multifactory/Accessor
Hasta ahora, nuestras factories y accessors solo podían crear o devolver un único tipo de objeto. Pero puede crear con
facilidad multifactories, que combinan las características de las factories y de los accessors. La interfaz de un componente así
puede contener varios métodos llamados create<Name>() y get<Name>(), por ejemplo:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Así, en lugar de inyectar varias factories y accessors individuales, puede inyectar un único componente más completo.
Alternativamente, en lugar de varios métodos se puede usar get() con un parámetro:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Entonces, MultiFactory::getDb() hace lo mismo que MultiFactoryAlt::get('db'). Esta notación
alternativa tiene, sin embargo, la desventaja de que los valores admitidos para $name no se ven claramente en la
firma de la interfaz. Además, no puede definir tipos de retorno distintos para distintos valores de $name dentro de
la interfaz.
En lugar de get($name), la interfaz puede declarar create($name), que devuelve una instancia nueva en
cada llamada (mientras que get() devuelve una compartida). La interfaz solo puede contener un método parametrizado
de este tipo. Si el tipo de retorno del método admite null (p. ej. ?PDO), devuelve null para un
$name desconocido en lugar de lanzar una excepción.
Definición con una lista
Puede definir una multifactory en la configuración con una lista, escribiendo los servicios en línea:
services:
- MultiFactory(
article: Article() # define createArticle()
db: PDO(%dsn%, %user%, %password%) # define getDb()
)
Alternativamente puede referirse a servicios existentes en la definición de la multifactory mediante referencias:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # define createArticle()
db: @\PDO # define getDb()
)
Definición con etiquetas
Otra forma de definir una multifactory es usar etiquetas. El valor de la etiqueta determina el nombre del método correspondiente:
services:
article:
create: Article
tags: {multi: article} # define createArticle()
db:
create: PDO(%dsn%, %user%, %password%)
tags: {multi: db} # define getDb()
- MultiFactory(tagged: multi)