Digital solutions
Design Patterns avec Laravel

Factory Method Pattern

  • Type : Créationnel
  • Difficulté : 4/10
  • Définition succincte : Le Factory Method Pattern est un design pattern de création qui définit une interface pour créer des objets, mais laisse aux sous-classes le soin de déterminer quelle classe concrète instancier. Cela permet de déléguer la responsabilité de la création d'objets à des sous-classes, tout en laissant la classe parente abstraite sur les détails de l'instanciation.
  • Contexte spécifique à Laravel : En Laravel, le Factory Method Pattern est couramment utilisé dans les Database Factories et les Test Factories, où l'on délègue la création d'instances de modèles à une méthode spécialisée qui décide comment et quoi instancier.

Objectif du Factory Method Pattern

L'objectif principal du Factory Method Pattern est de centraliser et standardiser la création d'objets dans un système, tout en permettant aux sous-classes de personnaliser cette création. Cela est utile lorsque le code client n'a pas besoin de connaître la classe exacte qui sera instanciée, ou lorsque le type d'objet à instancier dépend de certaines conditions.

Structure du Factory Method Pattern

  • Product : Interface ou classe abstraite qui définit le type d'objet à créer.
  • ConcreteProduct : Classe concrète qui implémente ou hérite de Product, c'est l'objet créé par le Factory Method.
  • Creator : Classe abstraite ou interface qui déclare le Factory Method pour créer des objets de type Product. Le Factory Method peut être abstrait ou avoir une implémentation par défaut.
  • ConcreteCreator : Classe concrète qui hérite de Creator et implémente le Factory Method pour créer des instances de ConcreteProduct.

1. Exemple d'utilisation dans Laravel

Dans Laravel, les Database Factories utilisent le Factory Method Pattern pour générer des instances de modèles. Voici un exemple où une factory est utilisée pour créer un utilisateur :

$user = \App\Models\User::factory()->create();

Laravel utilise une méthode factory() pour déléguer l'instanciation du modèle à une classe spécifique sans que tu aies besoin de t'occuper des détails. Cette logique est un exemple d'implémentation du Factory Method Pattern.

2. Implémentation complète dans Laravel

Imaginons que tu développes un système qui génère différents types de documents (comme des PDF, des rapports en CSV, et des invitations). Chaque document a un processus de création légèrement différent, mais le système doit les traiter de manière uniforme.

Le Factory Method Pattern permet de créer une interface ou une classe abstraite pour créer des documents, puis de laisser les sous-classes décider du type de document à générer.

a) Définir l'interface Document

Tu commences par créer une interface ou une classe abstraite pour les documents. Cela définit le contrat pour la génération de documents.

// app/Contracts/Document.php

namespace App\Contracts;

interface Document
{
    public function generate();
}

b) Créer les classes concrètes de document

Ensuite, tu implémentes différentes classes de documents qui respectent ce contrat.

2.1. Classe PdfDocument
// app/Documents/PdfDocument.php

namespace App\Documents;

use App\Contracts\Document;

class PdfDocument implements Document
{
    public function generate()
    {
        return "PDF document generated!";
    }
}
2.2. Classe CsvReport
// app/Documents/CsvReport.php

namespace App\Documents;

use App\Contracts\Document;

class CsvReport implements Document
{
    public function generate()
    {
        return "CSV report generated!";
    }
}
2.3. Classe InvitationDocument
// app/Documents/InvitationDocument.php

namespace App\Documents;

use App\Contracts\Document;

class InvitationDocument implements Document
{
    public function generate()
    {
        return "Invitation document generated!";
    }
}

c) Créer le Factory Method

Tu crées ensuite une classe abstraite DocumentFactory qui déclare le Factory Method. Ce Factory Method sera responsable de la création de documents, mais sans définir explicitement quelle sous-classe instancier.

// app/Factories/DocumentFactory.php

namespace App\Factories;

abstract class DocumentFactory
{
    abstract public function createDocument(): Document;

    public function generateDocument()
    {
        $document = $this->createDocument();
        return $document->generate();
    }
}

d) Implémenter les Concrete Creators

Maintenant, tu vas créer des sous-classes concrètes qui implémentent le Factory Method pour générer les différents types de documents.

4.1. Classe PdfDocumentFactory
// app/Factories/PdfDocumentFactory.php

namespace App\Factories;

use App\Documents\PdfDocument;
use App\Contracts\Document;

class PdfDocumentFactory extends DocumentFactory
{
    public function createDocument(): Document
    {
        return new PdfDocument();
    }
}
4.2. Classe CsvReportFactory
// app/Factories/CsvReportFactory.php

namespace App\Factories;

use App\Documents\CsvReport;
use App\Contracts\Document;

class CsvReportFactory extends DocumentFactory
{
    public function createDocument(): Document
    {
        return new CsvReport();
    }
}
4.3. Classe InvitationDocumentFactory
// app/Factories/InvitationDocumentFactory.php

namespace App\Factories;

use App\Documents\InvitationDocument;
use App\Contracts\Document;

class InvitationDocumentFactory extends DocumentFactory
{
    public function createDocument(): Document
    {
        return new InvitationDocument();
    }
}

e) Utiliser le Factory Method dans une commande CLI

Tu peux maintenant utiliser le Factory Method Pattern dans une commande CLI pour générer différents types de documents sans te soucier des détails d'instanciation.

// app/Console/Commands/GenerateDocument.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Factories\PdfDocumentFactory;
use App\Factories\CsvReportFactory;
use App\Factories\InvitationDocumentFactory;

class GenerateDocument extends Command
{
    protected $signature = 'document:generate {type}';
    protected $description = 'Generate a specific type of document';

    public function handle()
    {
        $type = $this->argument('type');
        $factory = $this->getFactory($type);
        if ($factory) {
            $result = $factory->generateDocument();
            $this->info($result);
        } else {
            $this->error("Unknown document type: $type");
        }
    }

    protected function getFactory($type)
    {
        return match($type) {
            'pdf' => new PdfDocumentFactory(),
            'csv' => new CsvReportFactory(),
            'invitation' => new InvitationDocumentFactory(),
            default => null,
        };
    }
}

f) Exécuter la commande

Tu peux maintenant utiliser la commande pour générer les documents. Par exemple :

php artisan document:generate pdf

Renvoie :

PDF document generated!

Ou :

php artisan document:generate csv

Renvoie :

CSV report generated!

Exemple de résultat

En exécutant la commande avec différents arguments (pdf, csv, invitation), le Factory Method est utilisé pour créer et générer le bon type de document :

  • document:generate pdf : Renvoie "PDF document generated!"
  • document:generate csv : Renvoie "CSV report generated!"
  • document:generate invitation : Renvoie "Invitation document generated!"

Avantages du Factory Method Pattern

  1. Centralisation de la création : La création d'objets est centralisée dans des factory methods, ce qui permet de modifier la logique de création d'un objet sans toucher au code client.
  2. Extensibilité : Ajouter de nouveaux types d'objets devient facile. Il suffit de créer une nouvelle sous-classe de DocumentFactory et de l'utiliser sans modifier le code client.
  3. Découplage : Le client est découplé des détails spécifiques d'instanciation des objets concrets. Le client travaille avec des interfaces ou des classes abstraites.

Inconvénients du Factory Method Pattern

  1. Complexité supplémentaire : Ce pattern peut introduire une complexité supplémentaire si la création d'objets est simple ou n'a pas besoin d'être personnalisée.
  2. Surcharge de sous-classes : S'il y a beaucoup de types d'objets à créer, cela peut entraîner une explosion de sous-classes, augmentant la complexité du projet.

Conclusion

Le Factory Method Pattern est un excellent moyen de centraliser la création d'objets et de permettre aux sous-classes de personnaliser cette création. En PHP et Laravel, ce pattern est utile pour des scénarios où la création d'objets est complexe ou doit être ajustée en fonction des différents types d'objets. Il permet également de découpler le code client de l'instanciation, rendant le système plus

flexible et extensible.