Prendre Rendez-vous
Logo Olympe Studio blanc

Les Factory en PHP | définition et intérêt

Les Factory en PHP sont des méthodes, fonctions ou class qui permettent de générer des objets sans avoir à utiliser la syntaxe new NomDeClass();.

Ce concept est un paterne de développement souvent utilisé, en particulier dans l’écosystème PHP parce qu’il permet de rendre le code plus lisible et logique.

Il existe plusieurs écoles dans la mise en œuvre de ce patron de conception, mais dans cet article, on va se contenter de vous partager notre point de vue sur la question.

Sommaire

Pourquoi utilise-t-on les Factory en PHP ?

Lorsque l’on souhaite créer une instance d’une classe en PHP, on dispose d’une méthode prévue par le langage : le keyword new.

Bien que le ce keyword soit basique dans la plupart des langages de programmation orientés objet, il crée de multiples problématiques.

Voici un ensemble de bonne raison de vouloir utiliser des factory plutôt que le keyword new.

Améliorer la lisibilité de l’instanciation des objets

Lorsque vous programmez, en particulier en équipe et sur des projets complexes, rendre le code très lisible c’est faire en sorte que l’on puisse déduire ce qu’une ligne fait, sans avoir à lire le code qui se cache derrière.

Lorsque vous écrivez par exemple :

<?php

use Framework\Core\HTTPClient;

// Fait une GET request avec un client HTTP.
$response = HTTPClient::get('https://www.exemple.com/');

Il est très simple de déduire que vous réalisez une requête HTTP en GET vers l’URL précisée. Vous n’avez pas besoin de cliquer sur ::get pour lire le code, vous SAVEZ que $response sera la réponse du serveur à votre requête.

À contrario, quand vous tombez sur du code qui ressemble à ça :

?php

$wpml_include_url_filter = new WPML_Include_Url( $wpdb, $_SERVER['HTTP_HOST'] );
$icl_plugin_url          = $wpml_include_url_filter->filter_include_url( $icl_plugin_url );

Bon courage pour comprendre ce qu’il se passe.

Et c’est à ça que servent les factory (lorsqu’elles sont bien utilisées). Elles permettent d’ajouter de la clarté à votre code de manière à comprendre rapidement ce que vous faites sans avoir à entrer en profondeur dans le code que vous écrivez.

Par exemple, dans notre article sur les singletons, nous utilisons la méthode ::getInstance() qui nous permet de créer une instance et la renvoyer si elle n’existe pas encore.

Même si l’opération derrière est quelque peu complexe, il est très simple de comprendre en lisant SEOAPI::getInstance() que l’ont va recevoir une class qui permet d’interagir avec l’API SEO de notre application.

Effectuer des opérations implicitement

Quand vous utiliser new Post($title, $description, $author, ...); pour créer un nouvel article de blog, vous ne faites qu’en créer une instance dans votre application PHP, mais aucune opération de persistance n’est effectuée dans la base de données.

Mais pourtant, il n’y a aucune situation dans le monde réel, ou vous voudriez représenter un article de blog qui n’existe pas.

Bien sûr, la plupart des ORM proposent une méthode save() qui permet d’enregistrer l’article dans la base de données, mais vous pourriez tout autant intégrer ce processus dans une méthode statique create() qui combinerait l’instanciation et l’inscription en base de données.

Je le rappelle, mais dans cet exemple, une instance d’un article de blog n’a pas lieu d’être sans un jeu de donnée persistant.

Vous pouvez ainsi combiner instanciation et enregistrement du brouillon en base de données en une ligne, plutôt qu’en deux, le tout en rendant votre code très explicite sur ce qu’il fait.

Vous pouvez également profiter d’une méthode factory pour effectuer des tâches connexes implicitement.

Par exemple, si vous créez un block pour Gutenberg, et que sa catégorie n’existe pas, votre factory de Block peut la créer à votre place.

Éviter la confusion entre différentes opérations

Que se passerait-il maintenant si nous mélangions nos deux points précédents ?

Vous avez des articles de blog, et vous voulez pouvoir les créer ou les retrouver via leurs attributs.

Si vous n’avez pas de factory, il est très probable que vous essayiez de récupérer un Post en l’instanciant comme ceci :

<?php

use Framework\Models\Post;

$postId = 125;
$post = new Post($postId);

Et, un peu plus loin, vous voulez créer un nouveau post, et vous pensez alors à :

<?php

use Framework\Models\Post;

$newPost = new Post($title, $description, $author, ...);

Et là, le doute prend place …

Alors que si vous aviez utilisé des méthodes statiques en guise de factory, vous auriez résolu ce problème comme ceci :

<?php

// Création d'un post
$newPost = Post::create(...$args);
// Récupération d'un post par son ID.
$anotherPost = Post::getById($id);

Ainsi, les choses sont à la fois très simples, et très claires.

Vous pouvez même écrire des getters statiques qui vont vous permettre de récupérer des articles de blogs de différentes manières de manière très simple : getByCategoryId, getByAuthor, etc.

Et c’est ça, la force des factory en PHP : elles permettent de réaliser très facilement des choses chronophages ou complexes, tout en optimisant la lisibilité de votre code.

Les différentes catégories de Factory en PHP

Ce design pattern englobe un spectre large de possibilité, voici les 3 modèles principaux que vous retrouverez au quotidien :

  • Des méthodes statiques, qui permettent de créer des instances de la classe actuelle ou de ses enfants.
  • Des fonctions globales, qui permettent de créer des instances de class. (par exemple wp_insert_post)
  • Des class ClassNameFactory, qui ne servent qu’à instancier d’autres class.

Bien qu’elles réalisent toutes la même chose, elles n’ont pas du tout le même intérêt, en tout cas à mon sens.

Dans le cas des fonctions globales, on perd en clarté

En effet, même si elles ont à peu près le même usage que les méthodes statiques, elles ne sont pas rattachées à la classe dont on va recevoir une instance, et ça laisse un certain doute à la lecture de la suite.

Ensuite, il y a les fonction du type ClassNameFactory.

Voici un exemple concret utilisé dans Laravel :

// database/factories/UserFactory.php

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    protected $model = User::class;

    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => bcrypt('password'), // password
            'remember_token' => Str::random(10),
        ];
    }
}

// Utilisation dans un seeder
User::factory()->count(50)->create();

// Utilisation dans un test
$user = User::factory()->create([
    'name' => 'John Doe',
]);

Dans ce cas, on crée des instances pour effectuer des tests, ou remplir la base de données de données fictives.

Comme vous pouvez le voir, il existe différentes façons de créer une factory et elles n’ont pas toujours le même rôle à jouer.

Voici nos recommandations quant à leur usage :

  • Utilisez toujours une factory lorsqu’elle rend le code plus lisible ou explicite.
  • Évitez les Factory sous forme de class, comme dans laravel. Elles ajoutent de la confusion et augmentent la complexité de votre application.
  • Évitez les fonctions globales, les méthodes statiques sont presque toujours mieux.

J’attire particulièrement votre attention sur les factories sous forme de class nommées en ClassNameFactory.

Beaucoup de Framework PHP mettent en avant cette façon de faire, comme dans l’exemple avec Laravel par exemple.

Même si leur exemple d’utilisation type est cohérent, ce genre de Factory ne fait que rendre votre code plus compliqué à lire et ce n’est pas le but recherché.

Vous pourriez tout aussi bien réécrire cette factory sous le format d’une méthode statique User::getTestInstances($number); et ça n’en serait que plus simple et lisible à comprendre.

Avoir un code clair et explicite est toujours la priorité.

Conclusion

Les factories en PHP sont un puissant modèle de conception qui offre une flexibilité et une abstraction significatives dans la création d’objets.

Elles sont essentielles pour générer des objets de manière plus lisible et logique, sans recourir directement à new NomDeClass();.

Les factories en PHP améliorent significativement la clarté du code, en particulier dans des contextes complexes ou en équipe. Elles permettent d’effectuer des opérations de manière implicite.

Elles aident également à éviter la confusion entre différentes opérations en offrant des méthodes explicites pour créer ou retrouver des objets.

C’est également une manière de développer qui permet optimiser la lisibilité et la simplicité du code en PHP, tout en facilitant la réalisation de tâches complexes ou chronophages.

Aller plus loin

https://refactoring.guru/fr/design-patterns/factory-method/php/example#example-0 – Version Refactoring Guru des factory en PHP

https://r.je/static-methods-bad-practice – Article intéressant qui permet d’apporter un peu de nuance.