Module 20 – Architecture avancée
Niveau 5 – Laravel expert
Objectifs
Au programme :
- Organiser la logique métier dans des Services (couche entre contrôleur et modèle)
- Utiliser le pattern Repository pour abstraire l’accès aux données
- Introduire des DTO (Data Transfer Objects) pour des données structurées et typées
- Comprendre les principes de Clean Architecture et DDD (Domain-Driven Design) adaptés à Laravel
- Choisir le niveau d’architecture selon la taille du projet (éviter la sur-ingénierie)
Théorie
Services
Un Service est une classe qui encapsule une logique métier ou une orchestration (plusieurs modèles, appels externes). Le contrôleur délègue au service au lieu de tout faire lui-même : contrôleur fin, service « gras ».
Exemple : création d’une commande (vérifier le stock, créer la commande, vider le panier, envoyer un email). Au lieu de mettre tout ça dans le contrôleur, on a un OrderService::createFromCart($user).
namespace App\Services;
class OrderService
{
public function __construct(
private CartRepository $cartRepo,
private Mailer $mailer,
) {}
public function createFromCart(User $user): Order
{
$cart = $this->cartRepo->getForUser($user);
if ($cart->isEmpty()) {
throw new EmptyCartException();
}
$order = Order::create([...]);
$this->cartRepo->clear($user);
$this->mailer->sendOrderConfirmation($order);
return $order;
}
}
Contrôleur :
public function store(StoreOrderRequest $request, OrderService $orderService)
{
$order = $orderService->createFromCart($request->user());
return redirect()->route('orders.show', $order);
}
Les services sont injectés via le conteneur (constructeur). On peut les enregistrer dans AppServiceProvider ou laisser Laravel les résoudre automatiquement (autowiring).
Repositories
Un Repository est une classe qui centralise l’accès aux données pour une entité (ou un agrégat). Il expose des méthodes du type find($id), findAll(), save($entity), delete($entity) ou des méthodes métier (findByStatus, getRecent). Le reste de l’app (contrôleurs, services) ne parle plus au modèle Eloquent directement mais au repository. Avantages : tests plus simples (on peut mocker le repository), changement de source de données (Eloquent, autre BDD, API) sans toucher au métier.
Exemple minimal :
namespace App\Repositories;
use App\Models\Article;
class ArticleRepository
{
public function find(int $id): ?Article
{
return Article::find($id);
}
public function getPublished(): \Illuminate\Database\Eloquent\Collection
{
return Article::where('published', true)->orderBy('published_at', 'desc')->get();
}
public function save(Article $article): Article
{
$article->save();
return $article;
}
}
Interface (optionnel mais utile pour les tests et l’inversion de dépendance) :
interface ArticleRepositoryInterface
{
public function find(int $id): ?Article;
public function getPublished(): \Illuminate\Database\Eloquent\Collection;
}
Enregistrer dans AppServiceProvider : $this->app->bind(ArticleRepositoryInterface::class, ArticleRepository::class);. Les contrôleurs ou services type-hintent l’interface.
Quand l’utiliser ? Projets moyens à gros, équipes, besoin de tester sans BDD. Pour une petite app Laravel classique, Eloquent dans le contrôleur ou dans un service peut suffire ; le repository devient utile quand la logique d’accès se complexifie ou qu’on veut découpler.
DTO (Data Transfer Objects)
Un DTO est un objet immuable (ou considéré comme tel) qui transporte des données entre couches (requête → service, service → vue, API → service). Il n’a pas de logique métier, seulement des propriétés typées. Utile pour : valider une fois les entrées, passer des données cohérentes, typer strictement.
Exemple (PHP 8) :
namespace App\DTOs;
readonly class CreateArticleData
{
public function __construct(
public string $title,
public string $body,
public int $userId,
public ?\DateTimeImmutable $publishedAt = null,
) {}
}
Création depuis la requête validée :
$data = new CreateArticleData(
title: $request->validated('title'),
body: $request->validated('body'),
userId: $request->user()->id,
publishedAt: $request->validated('published_at') ? \DateTimeImmutable::createFromFormat('Y-m-d', $request->validated('published_at')) : null,
);
$article = $articleService->create($data);
Le service reçoit un CreateArticleData au lieu d’un tableau ou d’un Request : contrat clair, IDE et static analysis friendly.
Clean Architecture (principes adaptés à Laravel)
Clean Architecture (Uncle Bob) organise le code en couches avec des dépendances qui pointent vers l’intérieur : le domaine (entities, use cases) ne dépend pas du framework ni de la BDD. En Laravel on en fait une version pragmatique :
- Domain / Métier : entités (modèles « pauvres » ou classes métier), règles métier pures si besoin.
- Application (Use cases) : services, DTOs ; orchestrent le domaine.
- Infrastructure : Eloquent, repositories concrets, HTTP, filesystem ; implémentent les interfaces définies plus « au centre ».
- Présentation : contrôleurs, Form Requests, Resources ; appellent les services et renvoient des réponses.
En pratique Laravel : Models = entités + persistance (Eloquent) ; Services = use cases ; Repositories = abstraction persistance ; Controllers = présentation. On évite que le contrôleur ou le service dépende directement de détails (ex. nom de table, requête SQL brute) en passant par des interfaces (Repository).
DDD (Domain-Driven Design) – introduction
DDD met le domaine métier au centre : langage ubiquitaire (même vocabulaire que le métier), entités, value objects, agrégats, bounded contexts. En Laravel :
- Entités : modèles qui ont une identité (User, Order).
- Value objects : objets sans identité, définis par leurs attributs (Money, Email, Address). En PHP on peut les représenter par des classes readonly ou des objets immuables.
- Agrégats : groupe d’entités avec une racine (ex. Order + OrderLines ; on accède aux lignes via la commande).
- Repositories par agrégat : un repository par racine d’agrégat (OrderRepository, pas OrderLineRepository séparé si OrderLine n’est accédé que via Order).
Pour une formation « expert », on introduit les idées ; pour aller plus loin, des livres comme Domain-Driven Design (Eric Evans) ou Implementing DDD (Vernon) sont recommandés.
Bonnes pratiques
- Services pour toute logique qui dépasse « lire un modèle et afficher » ou « valider + save ».
- Repositories quand l’accès aux données devient complexe ou qu’on veut tester/mocker facilement.
- DTOs pour les échanges entre couches (requête → service, API → service) avec un contrat typé.
- Ne pas sur-architecturer : petit projet = contrôleur + modèle + Form Request peut suffire ; ajouter services puis repositories quand la complexité le justifie.
Quiz – Module 20
Q1. Quel est le rôle d’un Service dans une application Laravel ?
Q2. À quoi sert un Repository ? Quelle couche ne doit plus appeler Eloquent directement si on utilise des repositories ?
Q3. Qu’est-ce qu’un DTO et pourquoi l’utiliser ?
Q4. Dans Clean Architecture, vers où doivent pointer les dépendances (du centre vers l’extérieur ou l’inverse) ?
Q5. En DDD, quelle est la différence entre une entité et un value object ?
Réponses
R1. À encapsuler la logique métier ou l’orchestration (plusieurs modèles, appels externes) ; le contrôleur délègue au service pour rester fin.
R2. À abstraire l’accès aux données (requêtes, persistance) pour une entité/agrégat. Les contrôleurs et services ne doivent plus appeler directement le modèle Eloquent pour cette entité, mais le repository.
R3. Un objet qui transporte des données entre couches (propriétés typées, pas de logique). Utile pour un contrat clair, le typage et la cohérence des données (ex. requête validée → service).
R4. Les dépendances pointent vers l’intérieur : le domaine ne dépend pas du framework ni de l’infrastructure ; c’est l’extérieur qui dépend du centre.
R5. Une entité a une identité (id, même si les attributs changent). Un value object est défini uniquement par ses valeurs (pas d’identité), souvent immuable (ex. Money, Email).
Suite
Module 21 – Laravel en production (environnements, logs, config cache, filesystem, déploiement).