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
)