Digital solutions
Design Patterns avec Laravel

Service Pattern

  • Type : Architectural
  • Difficulté : 5/10
  • Définition succincte : Le Service Pattern est un design pattern architectural qui consiste à encapsuler une logique métier spécifique ou un ensemble de fonctionnalités dans des classes appelées services. Ces services sont conçus pour être réutilisables et découplés de la logique de présentation ou de la persistance des données. Le but est de structurer et centraliser la logique métier de manière cohérente et modulaire.
  • Contexte spécifique à Laravel : En Laravel, le Service Pattern est généralement implémenté via le Service Container, permettant d'injecter des services dans d'autres classes comme des contrôleurs, des commandes ou des middlewares. Cela favorise un code plus modulaire et facilement testable.

Objectif du Service Pattern

L'objectif principal du Service Pattern est de centraliser la logique métier et de la rendre réutilisable à travers l'application. Ce pattern aide à séparer les préoccupations en déplaçant la logique métier hors des contrôleurs ou des modèles, ce qui permet un meilleur découplage, une facilité de maintenance et des tests unitaires plus simples.

Structure du Service Pattern

  • Service : La classe qui encapsule une ou plusieurs méthodes représentant des fonctionnalités spécifiques ou des règles métiers.
  • Client : L'objet (souvent un contrôleur, une commande ou un autre service) qui utilise le service pour accomplir une tâche.
  • Service Container : En Laravel, le Service Container permet de gérer l'enregistrement, la résolution et l'injection des services.

1. Exemple d'utilisation dans Laravel

Dans Laravel, le Service Container permet de gérer l'enregistrement et l'injection des services de manière transparente. Voici un exemple où un service est utilisé pour gérer la logique d'envoi d'e-mails.

a) Créer un service pour l'envoi d'e-mails

// app/Services/EmailService.php

namespace App\Services;

use Illuminate\Support\Facades\Mail;

class EmailService
{
    public function send($recipient, $subject, $message)
    {
        Mail::raw($message, function ($mail) use ($recipient, $subject) {
            $mail->to($recipient)->subject($subject);
        });
    }
}

b) Enregistrement du service dans le Service Container

Dans certains cas, il est nécessaire de lier ou d'enregistrer le service dans le Service Container de Laravel. Cela se fait dans un Service Provider, mais dans cet exemple, Laravel peut automatiquement injecter le service grâce à la résolution du type (type-hinting).

Si tu devais le faire manuellement, tu le ferais dans AppServiceProvider :

// app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\EmailService;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(EmailService::class, function ($app) {
            return new EmailService();
        });
    }
}

c) Injection du service dans un contrôleur

Plutôt que d'implémenter la logique d'envoi d'emails directement dans le contrôleur, nous injectons le EmailService dans le contrôleur via l'injection de dépendances, évitant ainsi d'encombrer le contrôleur avec cette logique..

// app/Http/Controllers/EmailController.php

namespace App\Http\Controllers;

use App\Services\EmailService;
use Illuminate\Http\Request;

class EmailController extends Controller
{
    protected $emailService;

    public function __construct(EmailService $emailService)
    {
        $this->emailService = $emailService;
    }

    public function sendEmail(Request $request)
    {
        $this->emailService->send($request->input('recipient'), $request->input('subject'), $request->input('message'));
        return response()->json(['message' => 'Email sent successfully!']);
    }
}

2. Implémentation complète dans Laravel

Prenons un exemple où tu souhaites centraliser la logique de gestion de factures. Plutôt que d'avoir cette logique dispersée dans plusieurs contrôleurs, nous allons utiliser le Service Pattern pour encapsuler la logique dans un service unique, qui sera utilisé par d'autres parties de l'application.

a) Créer un service InvoiceService

Nous allons créer un service qui gère la création de factures et l'envoi des factures par e-mail à des clients.

// app/Services/InvoiceService.php

namespace App\Services;

use App\Models\Invoice;
use App\Services\EmailService;

class InvoiceService
{
    protected $emailService;

    // Injecter le service d'e-mail dans le service de facturation
    public function __construct(EmailService $emailService)
    {
        $this->emailService = $emailService;
    }

    // Méthode pour créer une facture
    public function createInvoice($customer, $amount)
    {
        // Créer une nouvelle facture dans la base de données
        $invoice = Invoice::create([
            'customer' => $customer,
            'amount' => $amount,
            'status' => 'pending',
        ]);

        return $invoice;
    }

    // Méthode pour envoyer la facture par e-mail
    public function sendInvoiceByEmail(Invoice $invoice, $recipient)
    {
        $message = "Invoice #{$invoice->id} for {$invoice->amount} has been created.";
        $this->emailService->send($recipient, 'New Invoice', $message);

        // Mettre à jour le statut de la facture
        $invoice->status = 'sent';
        $invoice->save();
    }
}

b) Enregistrer le service dans le Service Container

// app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\InvoiceService;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(InvoiceService::class, function ($app) {
            return new InvoiceService($app->make(EmailService::class));
        });
    }
}

c) Utilisation du Service Pattern dans une commande CLI

Nous allons maintenant utiliser ce service dans une commande CLI pour créer et envoyer des factures à des clients.

// app/Console/Commands/CreateInvoice.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\InvoiceService;

class CreateInvoice extends Command
{
    protected $signature = 'invoice:create {customer} {amount} {recipient}';
    protected $description = 'Créer et envoyer une facture à un client';

    protected $invoiceService;

    public function __construct(InvoiceService $invoiceService)
    {
        parent::__construct();
        $this->invoiceService = $invoiceService;
    }

    public function handle()
    {
        $customer = $this->argument('customer');
        $amount = $this->argument('amount');
        $recipient = $this->argument('recipient');

        // Créer une nouvelle facture
        $invoice = $this->invoiceService->createInvoice($customer, $amount);
        $this->info("Invoice #{$invoice->id} created for {$customer}");

        // Envoyer la facture par e-mail
        $this->invoiceService->sendInvoiceByEmail($invoice, $recipient);
        $this->info("Invoice sent to {$recipient}");
    }
}

d) Exécution de la commande

Tu peux maintenant exécuter cette commande pour créer et envoyer une facture.

php artisan invoice:create "John Doe" 1000 "john@example.com"

Cela affichera :

Invoice #1 created for John Doe
Invoice sent to john@example.com

Avantages du Service Pattern

  1. Modularité : Le Service Pattern permet de centraliser et d'organiser la logique métier dans des services, facilitant la maintenance et la compréhension du code.
  2. Réutilisabilité : En regroupant la logique dans des services dédiés, ces derniers peuvent être réutilisés dans plusieurs parties de l'application, réduisant la duplication du code.
  3. Facilité de test : Les services sont plus faciles à tester, car ils ne dépendent pas directement de la logique de présentation ou de la persistance des données.

Inconvénients du Service Pattern

  1. Surcharge initiale : La mise en place du Service Pattern peut ajouter de la complexité au projet, en particulier dans les petites applications où la logique métier est relativement simple.
  2. Abstraction excessive : Si le pattern est utilisé à outrance, il peut introduire des couches d'abstraction inutiles qui rendent le code plus difficile à suivre.

Conclusion

Le Service Pattern est une pratique largement utilisée en développement Laravel pour organiser et centraliser la logique métier ou les fonctionnalités complexes dans des classes dédiées appelées services. Ce pattern favorise la séparation des préoccupations, améliore la réutilisabilité et la testabilité du code. C'est un moyen efficace d'organiser et de structurer les applications Laravel, en rendant les contrôleurs plus simples et en centralisant les règles métiers dans des services spécialisés.