Digital solutions
Design Patterns avec Laravel

Pipeline Pattern

  • Type : Comportemental
  • Difficulté : 7/10
  • Définition succincte : Le Pipeline Pattern est un design pattern comportemental qui permet de traiter une séquence d'actions ou de transformations sur des données de manière fluide et modulaire. Chaque étape de la pipeline reçoit une entrée, effectue une transformation ou un traitement, et passe le résultat à l'étape suivante. Ce pattern est particulièrement utile pour organiser des flux de traitement complexes où plusieurs opérations doivent être appliquées successivement.
  • Contexte spécifique à Laravel : Laravel utilise le Pipeline Pattern pour gérer les middlewares dans le traitement des requêtes HTTP, mais il est aussi utilisé pour organiser des flux de données, par exemple pour appliquer des transformations ou des validations sur des données d'entrée.

Objectif du Pipeline Pattern

L'objectif du Pipeline Pattern est de permettre la composition de plusieurs opérations ou transformations successives sur des données ou des objets. Chaque étape de la pipeline est indépendante et peut être combinée ou réutilisée facilement dans différents contextes. Ce pattern favorise la flexibilité et la modularité, en rendant les opérations de traitement plus lisibles et organisées.

Structure du Pipeline Pattern

  • Pipeline : Contient la séquence de traitements à appliquer. Il transmet les données ou objets à chaque étape.
  • Stage (ou Step) : Chaque étape dans le pipeline qui applique une transformation ou une opération spécifique.
  • Data : Les données qui circulent à travers le pipeline.

1. Exemple d'utilisation dans Laravel

En Laravel, le Pipeline Pattern est utilisé nativement dans les middlewares pour traiter les requêtes HTTP. Chaque middleware représente une étape dans le traitement de la requête, et la requête passe à travers chaque middleware jusqu'à ce qu'elle atteigne le contrôleur.

Exemple simple d'utilisation d'une pipeline dans Laravel pour transformer une requête :

use Illuminate\Pipeline\Pipeline;

$pipeline = app(Pipeline::class)
    ->send($request)
    ->through([
        \App\Http\Middleware\CheckUser::class,
        \App\Http\Middleware\LogRequest::class,
    ])
    ->thenReturn();

$response = $pipeline->handle($request);

Dans cet exemple, les middlewares appliquent des actions successives sur la requête HTTP avant de la transmettre au contrôleur.

2. Implémentation complète dans Laravel

Imaginons que tu développes une application Laravel où tu veux traiter une série d'opérations pour valider et modifier une commande client. Chaque étape doit appliquer une transformation ou une vérification sur la commande (par exemple, appliquer une réduction, vérifier le stock, calculer les taxes, etc.). Le Pipeline Pattern te permettra de gérer ces étapes de manière modulaire.

a) Créer un contract pour les étapes de la pipeline

Tu vas d'abord définir une interface Pipe pour les étapes du pipeline. Chaque étape devra appliquer une transformation sur la commande et passer à la suivante.

// app/Contracts/Pipe.php

namespace App\Contracts;

interface Pipe
{
    public function handle($order, \Closure $next);
}

b) Créer des étapes concrètes (les "pipes")

Chaque étape du traitement de la commande sera une classe qui implémente l'interface Pipe. Voici quelques exemples d'étapes : application d'une réduction, vérification du stock, calcul des taxes.

1. Pipe pour appliquer une réduction
// app/Pipes/ApplyDiscount.php

namespace App\Pipes;

use App\Contracts\Pipe;

class ApplyDiscount implements Pipe
{
    public function handle($order, \Closure $next)
    {
        // Appliquer une réduction de 10 % si applicable
        if ($order['total'] > 100) {
            $order['total'] -= $order['total'] * 0.10;
        }

        return $next($order); // Passer à l'étape suivante
    }
}
2. Pipe pour vérifier le stock
// app/Pipes/CheckStock.php

namespace App\Pipes;

use App\Contracts\Pipe;
use Exception;

class CheckStock implements Pipe
{
    public function handle($order, \Closure $next)
    {
        // Vérifier si le stock est suffisant pour chaque article
        foreach ($order['items'] as $item) {
            if ($item['quantity'] > $item['available_stock']) {
                throw new Exception("Not enough stock for item {$item['name']}");
            }
        }

        return $next($order); // Passer à l'étape suivante
    }
}
3. Pipe pour calculer les taxes
// app/Pipes/CalculateTax.php

namespace App\Pipes;

use App\Contracts\Pipe;

class CalculateTax implements Pipe
{
    public function handle($order, \Closure $next)
    {
        // Appliquer une taxe de 15 %
        $order['tax'] = $order['total'] * 0.15;
        $order['total'] += $order['tax'];

        return $next($order); // Passer à l'étape suivante
    }
}

c) Utiliser le Pipeline Pattern dans une commande CLI

Nous allons maintenant créer une commande CLI pour traiter une commande en utilisant ces étapes via une pipeline.

// app/Console/Commands/ProcessOrder.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Pipeline\Pipeline;
use App\Pipes\ApplyDiscount;
use App\Pipes\CheckStock;
use App\Pipes\CalculateTax;

class ProcessOrder extends Command
{
    protected $signature = 'order:process';
    protected $description = 'Process an order through a pipeline';

    public function handle()
    {
        $order = [
            'items' => [
                ['name' => 'Product A', 'quantity' => 1, 'available_stock' => 10, 'price' => 100],
                ['name' => 'Product B', 'quantity' => 2, 'available_stock' => 5, 'price' => 50],
            ],
            'total' => 200,
            'tax' => 0,
        ];

        // Utiliser une pipeline pour traiter la commande
        $processedOrder = app(Pipeline::class)
            ->send($order)
            ->through([
                ApplyDiscount::class,
                CheckStock::class,
                CalculateTax::class,
            ])
            ->thenReturn();

        // Afficher le résultat final de la commande traitée
        $this->info('Order processed successfully:');
        $this->line('Total: ' . $processedOrder['total']);
        $this->line('Tax: ' . $processedOrder['tax']);
    }
}

d) Exécution de la commande

Tu peux exécuter la commande suivante pour traiter une commande via la pipeline :

php artisan order:process

Exemple de sortie

Order processed successfully:
Total: 207
Tax: 27

Avantages du Pipeline Pattern

  1. Modularité : Chaque étape de la pipeline est encapsulée dans sa propre classe, ce qui rend le code plus maintenable et facile à étendre.
  2. Réutilisabilité : Les étapes de la pipeline peuvent être réutilisées dans différentes pipelines ou contextes sans modification.
  3. Flexibilité : Il est facile de réorganiser ou de remplacer des étapes dans la pipeline sans affecter les autres étapes.

Inconvénients du Pipeline Pattern

  1. Complexité supplémentaire : L'introduction d'une pipeline peut ajouter de la complexité inutile si le traitement des données est simple.
  2. Difficulté de débogage : Avec de nombreuses étapes dans la pipeline, il peut être difficile de localiser l'origine des erreurs ou des comportements inattendus.

Conclusion

Le Pipeline Pattern est idéal pour les scénarios où plusieurs opérations doivent être appliquées successivement à des données, comme la validation ou la transformation. Il est largement utilisé dans Laravel, en particulier dans le traitement des requêtes HTTP via les middlewares. Ce pattern améliore la modularité du code et permet de structurer des processus complexes de manière claire et maintenable.