Factories générées

Nette DI peut générer automatiquement le code des factories basé sur des interfaces, ce qui vous évite d'écrire du code.

Une factory est une classe qui produit et configure des objets. Elle leur transmet donc également leurs dépendances. Ne confondez pas, s'il vous plaît, avec le patron de conception factory method, qui décrit une manière spécifique d'utiliser les factories et n'est pas lié à ce sujet.

Nous avons montré à quoi ressemble une telle factory dans le chapitre d'introduction :

class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}

Nette DI peut générer automatiquement le code des factories. Tout ce que vous avez à faire est de créer une interface et Nette DI générera l'implémentation. L'interface doit avoir exactement une méthode nommée create et déclarer un type de retour :

interface ArticleFactory
{
	function create(): Article;
}

Ainsi, la factory ArticleFactory a une méthode create qui crée des objets Article. La classe Article peut ressembler à ceci :

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}
}

Nous ajoutons la factory au fichier de configuration :

services:
	- ArticleFactory

Nette DI générera l'implémentation correspondante de la factory.

Dans le code qui utilise la factory, nous demandons donc l'objet par l'interface et Nette DI utilisera l'implémentation générée :

class UserController
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function foo()
	{
		// laissons la factory créer l'objet
		$article = $this->articleFactory->create();
	}
}

Factory paramétrée

La méthode de factory create peut accepter des paramètres, qu'elle transmet ensuite au constructeur. Ajoutons par exemple à la classe Article l'ID de l'auteur de l'article :

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
		private int $authorId,
	) {
	}
}

Nous ajoutons également le paramètre à la factory :

interface ArticleFactory
{
	function create(int $authorId): Article;
}

Grâce au fait que le paramètre dans le constructeur et le paramètre dans la factory portent le même nom, Nette DI les transmet de manière entièrement automatique.

Définition avancée

La définition peut également être écrite sous forme multiligne en utilisant la clé implement :

services:
	articleFactory:
		implement: ArticleFactory

Lors de l'écriture de cette manière plus longue, il est possible de spécifier des arguments supplémentaires pour le constructeur dans la clé arguments et une configuration supplémentaire à l'aide de setup, tout comme pour les services normaux.

Exemple : si la méthode create() n'acceptait pas le paramètre $authorId, nous pourrions spécifier une valeur fixe dans la configuration, qui serait transmise au constructeur de Article :

services:
	articleFactory:
		implement: ArticleFactory
		arguments:
			authorId: 123

Ou inversement, si create() acceptait le paramètre $authorId, mais qu'il ne faisait pas partie du constructeur et était transmis par la méthode Article::setAuthorId(), nous nous y référerions dans la section setup :

services:
	articleFactory:
		implement: ArticleFactory
		setup:
			- setAuthorId($authorId)

Accessor

En plus des factories, Nette peut également générer ce qu'on appelle des accessors. Ce sont des objets avec une méthode get() qui renvoie un certain service du conteneur DI. Les appels répétés à get() renvoient toujours la même instance.

Les accessors fournissent un chargement paresseux (lazy-loading) pour les dépendances. Supposons une classe qui écrit des erreurs dans une base de données spéciale. Si cette classe se faisait passer la connexion à la base de données comme dépendance par le constructeur, la connexion devrait toujours être créée, même si en pratique une erreur n'apparaît qu'exceptionnellement et donc la plupart du temps la connexion resterait inutilisée. Au lieu de cela, la classe se fait passer un accessor et ce n'est que lorsque son get() est appelé que l'objet de base de données est créé :

Comment créer un accessor ? Il suffit d'écrire une interface et Nette DI générera l'implémentation. L'interface doit avoir exactement une méthode nommée get et déclarer un type de retour :

interface PDOAccessor
{
	function get(): PDO;
}

Nous ajoutons l'accessor au fichier de configuration, où se trouve également la définition du service qu'il renverra :

services:
	- PDOAccessor
	- PDO(%dsn%, %user%, %password%)

Comme l'accessor renvoie un service de type PDO et qu'il n'y a qu'un seul service de ce type dans la configuration, il renverra précisément celui-ci. S'il y avait plusieurs services de ce type, nous spécifierions le service renvoyé à l'aide de son nom, par ex. - PDOAccessor(@db1).

Factory/Accessor multiple

Nos factories et accessors ne pouvaient jusqu'à présent produire ou renvoyer qu'un seul objet. Mais il est très facile de créer également des factories multiples combinées avec des accessors. L'interface d'une telle classe contiendra un nombre quelconque de méthodes nommées create<name>() et get<name>(), par ex. :

interface MultiFactory
{
	function createArticle(): Article;
	function getDb(): PDO;
}

Ainsi, au lieu de nous passer plusieurs factories et accessors générés, nous passons une seule factory plus complexe qui en fait plus.

Alternativement, au lieu de plusieurs méthodes, on peut utiliser get() avec un paramètre :

interface MultiFactoryAlt
{
	function get($name): PDO;
}

Alors, MultiFactory::getArticle() fait la même chose que MultiFactoryAlt::get('article'). Cependant, la notation alternative a l'inconvénient qu'il n'est pas clair quelles valeurs de $name sont prises en charge et logiquement, il n'est pas non plus possible de distinguer différentes valeurs de retour pour différents $name dans l'interface.

Définition par liste

De cette manière, on peut définir une factory multiple dans la configuration :

services:
	- MultiFactory(
		article: Article                      # définit createArticle()
		db: PDO(%dsn%, %user%, %password%)    # définit getDb()
	)

Ou nous pouvons nous référer à des services existants dans la définition de la factory à l'aide d'une référence :

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # définit createArticle()
		db: @\PDO            # définit getDb()
	)

Définition par tags

La deuxième option consiste à utiliser des tags pour la définition :

services:
	- App\Core\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.explorer
	)
version: 3.x