Repository et Service Pattern dans Laravel : Guide de Référence

Tim
January 10th, 2025
image description

Imaginez construire un château de cartes imposant, pièce par pièce. Chaque pièce, parfaitement alignée, contribue à la stabilité et à la beauté de l’ensemble. Dans le développement Web (dans notre cas Laravel), les Repository, Services et Controllers sont ces pièces essentielles. Ces deux concepts permettent de séparer les responsabilités et de rendre vos applications plus modulaires, testables et maintenables.

Cet article vous guidera pas à pas pour comprendre et maîtriser cette architecture, rendant vos applications plus robustes, plus maintenables et bien plus élégantes.

Pourquoi utiliser les Repository et Service Patterns ?

Repository Pattern

Le "Repository" agit comme une interface entre votre application et la base de données. Il encapsule toute la logique d’accès aux données, permettant de changer de base de données (MySQL, PostgreSQL, etc.) sans modifier le reste de votre code. C’est comme un traducteur entre deux langues différentes : le "Controller" parle “application” et le "Repository" parle “base de données”.

Avantages :

  • Centralisation des requêtes.

  • Facilité de test ("Mock" des "repositories").

  • Réduction du code dupliqué.

Service Pattern

Le Service Pattern extrait la logique métier de vos contrôleurs, les rendant plus légers et concentrés sur leur rôle principal : gérer les requêtes HTTP. Les services agissent comme des intermédiaires entre vos contrôleurs et vos "repositories" (ou autres classes).

C’est là que se déroulent donc les opérations complexes, les calculs, les validations, etc. Il utilise le "Repository" pour accéder aux données, mais se concentre sur le traitement de l’information. C’est le cœur battant de votre application !

Avantages :

  • Meilleure séparation des responsabilités.

  • Logique métier réutilisable.

  • Simplification des contrôleurs.


Architecture générale

Voici une organisation possible pour intégrer ces deux patterns dans un projet Laravel :

shell
app/
├── Http/
│   ├── Controllers/
│   │   └── UserController.php
├── Services/
│   └── UserService.php
├── Repositories/
│   ├── Interfaces/
│   │   └── UserRepositoryInterface.php
│   └── UserRepository.php

Mise en œuvre pas à pas

1. Créer l’interface du "Repository"

L’interface définit les méthodes que le "repository" doit implémenter.
Chemin : app/Repositories/Interfaces/UserRepositoryInterface.php

php
<?php

namespace App\Repositories\Interfaces;

interface UserRepositoryInterface
{
    public function getAllUsers();
    public function getUserById($id);
    public function createUser(array $data);
    public function updateUser($id, array $data);
    public function deleteUser($id);
}

2. Implémenter le "Repository"

Le "repository" implémente l’interface et contient la logique d’interaction avec la base de données.
Chemin : app/Repositories/UserRepository.php

php
<?php

namespace App\Repositories;

use App\Models\User;
use App\Repositories\Interfaces\UserRepositoryInterface;

class UserRepository implements UserRepositoryInterface
{
    public function getAllUsers()
    {
        return User::all();
    }

    public function getUserById($id)
    {
        return User::findOrFail($id);
    }

    public function createUser(array $data)
    {
        return User::create($data);
    }

    public function updateUser($id, array $data)
    {
        $user = User::findOrFail($id);
        $user->update($data);
        return $user;
    }

    public function deleteUser($id)
    {
        $user = User::findOrFail($id);
        $user->delete();
        return true;
    }
}

3. Créer le Service

Le service encapsule la logique métier, en utilisant le "repository" pour interagir avec les données.
Chemin : app/Services/UserService.php

php
<?php

namespace App\Services;

use App\Repositories\Interfaces\UserRepositoryInterface;

class UserService
{
    protected $userRepository;

    public function __construct(UserRepositoryInterface $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function getAllUsers()
    {
        return $this->userRepository->getAllUsers();
    }

    public function createUser(array $data)
    {
        // Exemple de logique métier avant création
        if (isset($data['email']) && filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            return $this->userRepository->createUser($data);
        }

        throw new \InvalidArgumentException("L'email fourni n'est pas valide.");
    }
}

4. Utiliser dans le Contrôleur

En Laravel, le "Controller" est la première porte d’entrée de votre application. Il reçoit les requêtes HTTP (GET, POST, etc.) et délègue le traitement à d’autres composants. Il ne doit pas contenir la logique métier, mais plutôt orchestrer le flux de données. Pensez-y comme au chef d’orchestre d’un grand orchestre. Il indique à chaque instrument quand jouer, sans se préoccuper de la complexité de chaque partition.

Le contrôleur appelle donc les services pour déléguer la logique métier.
Chemin : app/Http/Controllers/UserController.php

php
<?php

namespace App\Http\Controllers;

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

class UserController extends Controller
{
    protected $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    public function index()
    {
        $users = $this->userService->getAllUsers();
        return response()->json($users);
    }

    public function store(Request $request)
    {
        $data = $request->all();
        $user = $this->userService->createUser($data);
        return response()->json($user, 201);
    }
}


Configurer l’injection de dépendance

Dans AppServiceProvider, liez l’interface au "repository" concret pour que Laravel sache quelle classe utiliser.
Chemin : app/Providers/AppServiceProvider.php

php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Repositories\Interfaces\UserRepositoryInterface;
use App\Repositories\UserRepository;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
    }

    public function boot()
    {
        //
    }
}

Bonnes pratiques et conseils

  1. Respectez le principe SOLID : Le Repository Pattern contribue au respect du principe de responsabilité unique (SRP).

  2. Évitez les abus : N’utilisez pas ces patterns pour des projets simples où ils ajoutent une complexité inutile.

  3. Tests faciles : Ces patterns permettent de mocker facilement les repositories et services pour les tests unitaires.

Repository et Service Patterns : dans quels cas posent-ils problème ?

Bien que ces patterns apportent des avantages considérables, leur utilisation n'est pas toujours justifiée, et ils peuvent devenir des obstacles dans certains cas. Voici une analyse approfondie des inconvénients et des situations dans lesquelles ils ne sont pas idéaux.


1. Complexité accrue

Problème :
L’introduction des "Repository" et "Service Patterns" dans un projet simple peut ajouter une couche de complexité inutile. Chaque action nécessite de créer et de maintenir plusieurs fichiers (interface, "repository", service, contrôleur), ce qui ralentit le développement.

Quand c’est problématique :

  • Petits projets ou MVP (Minimum Viable Product) : Pour des projets où la rapidité de développement est essentielle, ces patterns peuvent être excessifs.

  • Absence d’évolution prévue : Si l’application n’a pas vocation à évoluer ou à être maintenue sur le long terme, leur utilité est limitée.


2. Duplication inutile

Problème :
Dans certains cas, le "Repository Pattern" répète simplement la logique existante dans "Eloquent" ou "Query Builder". Par exemple, une méthode getAllUsers dans un "repository" appelle souvent directement User::all(), ce qui n’ajoute aucune valeur.

Quand c’est problématique :

  • Lorsque le modèle "Eloquent" suffit : "Eloquent" offre déjà des fonctionnalités puissantes comme les relations, les "scopes", et les requêtes complexes. Créer un "repository" pour des actions simples peut alourdir le code.


3. Difficulté de maintien

Problème :
L'utilisation rigoureuse de ces patterns peut entraîner un code fragmenté et difficile à maintenir, surtout pour les nouveaux développeurs qui doivent comprendre les dépendances entre services, "repositories" et contrôleurs.

Quand c’est problématique :

  • Équipes non formées : Si vos coéquipiers ne sont pas familiarisés avec ces concepts, cela peut compliquer leur intégration et la collaboration.


4. Sur-optimisation prématurée

Problème :
Ces patterns visent à résoudre des problèmes d’organisation à grande échelle. Dans des projets modestes ou en début de développement, ils peuvent ressembler à une sur-optimisation qui ralentit le lancement initial.

Quand c’est problématique :

  • Développement à faible volume : Si votre application n’a qu’un seul type d’interaction avec une table, un "repository" dédié est superflu.

Repository et Service Patterns : dans quels cas sont-ils indispensables ?

Ces patterns sont particulièrement intéressants dans les situations suivantes :

  1. Projets complexes et évolutifs : Lorsque l'application est susceptible de croître et d’intégrer de nouvelles fonctionnalités.

  2. Travail en équipe : Dans les grandes équipes, ces patterns apportent une structure claire et réduisent les risques de conflits dans le code.

  3. Logique métier complexe : Lorsque la logique dépasse les simples actions CRUD, les services permettent de centraliser et d’organiser les règles métier.

  4. Tests automatisés : La séparation des responsabilités rend le code plus testable, notamment en permettant de "mocker" les "repositories".


Conclusion

Ce guide vous a permis de poser les bases d’une architecture robuste pour vos applications Laravel. N’hésitez pas à expérimenter et à adapter ces concepts à vos propres projets. En maîtrisant les "Repository", "Services" et "Controllers", vous construirez des applications solides et élégantes (c'est-à-dire lisible, modulaire et maintenable) ! En les intégrant, vous créez un code plus. Cependant, adaptez leur utilisation en fonction de la taille et des besoins de vos projets.