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
	)
versión: 3.x