Guide Laravel 13 : Gérer les années académiques (Actions, Resources & Context)

Par Devinsto
Posté le 9 mai 2026
Mis à jour le 3 novembre 2026
29 min de lecture
143 vues

Vous gérez un établissement scolaire et vous utilisez Laravel ? Alors la gestion des années académiques est probablement au cœur de votre application. Mais comment passer d'un contrôleur « fourre-tout » à une architecture moderne, maintenable et prête pour la production ?

Dans cet article, on va voir ensemble comment transformer un simple contrôleur CRUD en une machine de guerre bien huilée, en utilisant trois piliers de Laravel 13 : le Pattern Action, les API Resources avec leur auto-discovery, et la Context Facade pour l'audit. Préparez-vous, ça va changer votre façon de coder !

Mots-clés : Laravel 13, gestion année académique, PHP 8 attributes, CloseAcademicYearAction, API Resources Laravel, Context facade.

Pourquoi cette refonte était indispensable

Avant, notre contrôleur AnneeAcademiqueController faisait tout. Il validait, créait, activait, désactivait, et même gérait les transactions. Un vrai couteau suisse. Mais un couteau suisse, c'est pratique jusqu'au jour où vous voulez ajouter une fonctionnalité comme le verrouillage des notes en fin d'année. Là, le bordel commence.

Le constat était simple :

  • Logique métier mélangée avec la réponse HTTP — impossible de tester l'activation d'une année sans faire une requête HTTP complète.

  • Aucune traçabilité — quand une année était activée, personne ne savait qui l'avait fait ni quelle année était active avant.

  • Couplage fort — pour fermer une année (verrouiller les notes, archiver les bilans), il fallait tout réécrire dans le contrôleur.

Bref, c'était le moment de passer à une architecture plus propre. Et Laravel 13 nous tendait la perche avec des fonctionnalités excellentes.

Le Pattern Action : sortez la logique de vos contrôleurs

Qu'est-ce qu'une Action ?

Une Action est une classe PHP dédiée à une seule responsabilité métier. Elle ne sait pas ce qu'est une requête HTTP, une redirection ou un message flash. Elle reçoit des données, fait son travail, et éventuellement retourne un résultat.

Imaginez un chef cuisinier. Le contrôleur, c'est le service en salle qui prend la commande. L'Action, c'est le chef en cuisine qui prépare le plat. Le service n'a pas besoin de savoir comment le plat est cuisiné, et le chef ne s'occupe pas de la présentation en salle.

CloseAcademicYearAction : notre première Action

Voici ce que fait concrètement CloseAcademicYearAction :

class CloseAcademicYearAction
{
    public function execute(AnneeAcademique $annee): void
    {
        // 1. Désactiver l'ancienne année active
        // 2. Activer la nouvelle année
        // 3. Enregistrer le contexte d'audit (Context::add)
        // 4. Logger l'opération
    }
}

Pourquoi c'est mieux :

  • ✅ Testable unitairement — on peut tester execute() sans jamais toucher à une route HTTP.

  • ✅ Réutilisable — l'activation est appelée depuis store(), update() ET la route dédiée activate(). Plus de duplication.

  • ✅ Évolutif — quand il faudra verrouiller les notes, archiver les bilans, ou envoyer une notification, on ajoute une étape dans l'Action. Le contrôleur, lui, ne change pas.

Et les middleware alors ?

Avec Laravel 13, on n'écrit plus les middleware dans le constructeur ou les routes. On utilise des attributs PHP 8 directement sur la classe :

use Illuminate\Routing\Attributes\Controllers\Middleware;

#[Middleware('auth')]
#[Middleware('verified')]
#[Middleware('role:admin,superadmin')]
class AnneeAcademiqueController extends Controller
{
    // ...
}

Pourquoi c'est génial :

  • La sécurité est déclarée là où elle s'applique — directement sur le contrôleur.

  • Plus besoin de chercher dans les routes pour savoir si un contrôleur est protégé.

  • Les attributs sont lisibles par les outils d'analyse statique (PHPStan, IDE).

API Resources : dites adieu au json_encode sauvage

Le problème

Avant, pour sérialiser une année académique en JSON, on faisait ça :

return response()->json([
    'id' => $annee->id,
    'libelle' => $annee->libelle,
    'is_active' => $annee->is_active,
]);

Pas faux. Mais imaginez 10 endpoints, 15 modèles, et des relations qui changent. Vous allez vite vous retrouver avec 40 endroits différents qui sérialisent la même chose. Et si vous ajoutez un champ, il faut le changer partout.

La solution : AnneeAcademiqueResource

On crée une classe dédiée qui dit comment transformer le modèle :

class AnneeAcademiqueResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'libelle' => $this->libelle,
            'date_debut' => $this->date_debut?->format('Y-m-d'),
            'date_fin' => $this->date_fin?->format('Y-m-d'),
            'is_active' => $this->is_active,
            'statut' => $this->when($this->is_active, 'Année Actuelle'),
        ];
    }
}

Les petits plus qui changent tout :

Méthode

Utilité

Exemple

when()

N'affiche un champ que si une condition est vraie

$this->when($this->is_active, 'Année Actuelle')

whenCounted()

N'affiche un count que s'il a été chargé

$this->whenCounted('users')

whenAggregated()

N'affiche une agrégation SQL que si elle a été calculée

$this->whenAggregated('evaluations', 'note', 'avg')

Résultat : plus de N+1, plus de données inutiles envoyées au client.

L'auto-discovery : encore plus simple

Avec Laravel 13, on enregistre la Resource directement sur le modèle :

use App\Http\Resources\AnneeAcademiqueResource;
use Illuminate\Database\Eloquent\Attributes\UseResource;

#[UseResource(AnneeAcademiqueResource::class)]
class AnneeAcademique extends Model
{
    // ...
}

Et maintenant, on peut faire :

// Au lieu de : new AnneeAcademiqueResource($annee)
return $annee->toResource();

// Au lieu de : AnneeAcademiqueResource::collection($annees)  
return AnneeAcademique::all()->toResourceCollection();

Pas besoin de se rappeler quel Resource va avec quel modèle. Laravel le découvre tout seul. Magique, non ?


Context Facade : l'audit qui vous veut du bien

Le problème du logging traditionnel

Quand vous loggez une action :

Log::info('Année activée', ['annee_id' => 1]);

Ce log vit tout seul. Il ne sait pas dans quel contexte il a été émis — quel administrateur était connecté, quelle était l'année précédente, à quelle heure précise. Et si vous devez reconstituer l'historique des activations, bon courage.

La solution : Context::add()

La Context Facade de Laravel 13 permet d'attacher des métadonnées à TOUS les logs émis pendant une requête :

use Illuminate\Support\Facades\Context;

Context::add('academic_year_id', $annee->id);
Context::add('academic_year', [
    'activated_id' => $annee->id,
    'previous_active_id' => $previousActive?->id,
    'action' => 'close_academic_year',
    'admin_id' => auth()->id(),
]);

Ce que ça change :

  • Tous les logs émis après cet appel incluront automatiquement ces données.

  • Même les jobs dispatchés plus tard récupèrent le contexte. Imaginez un job qui envoie un email de confirmation après l'activation : le log de cet email contiendra academic_year_id sans que vous ayez à le passer manuellement.

  • La traçabilité devient automatique et exhaustive, sans effort supplémentaire.

Question rhétorique : Combien de fois avez-vous passé 2 heures à chercher "qui a changé quoi et quand" dans vos logs ? Avec Context, cette question trouve une réponse en 2 secondes.


Architecture finale : vue d'ensemble

Voici comment tout s'articule :

[Route] → AnneeAcademiqueController
              │
              ├── Appelle → CloseAcademicYearAction
              │                ├── Désactive l'ancienne année
              │                ├── Active la nouvelle
              │                ├── Context::add('academic_year_id')
              │                └── Log::info(...)
              │
              ├── Retou
e → $annee->toResource() (JSON)
              │              └── AnneeAcademiqueResource
              │                   ├── when()
              │                   ├── whenCounted()
              │                   └── whenAggregated()
              │
              └── Middleware (attributs) → #[Middleware('auth')]
                                           #[Middleware('role:admin,superadmin')]

Avant (contrôleur monolithique) :

  • ~150 lignes de tout et n'importe quoi

  • Logique d'activation noyée dans le store()

  • Aucune traçabilité

  • Duplication de code

Après (architecture moderne) :

  • Contrôleur allégé : ~80 lignes, que de l'orchestration

  • Action dédiée, testable, réutilisable

  • Resource standardisée, auto-découverte

  • Contexte d'audit automatique


Bonnes pratiques à retenir

  1. Une Action = une responsabilité. Si votre méthode execute() fait plus de 15-20 lignes, découpez.

  2. Les contrôleurs ne devraient jamais faire de calcul métier. Validez les entrées, appelez une Action, retournez une réponse.

  3. Utilisez #[UseResource] sur tous vos modèles. Même si vous ne faites que du Blade aujourd'hui, vos futures API vous remercieront.

  4. Context::add() dès qu'une opération importante est faite. Le logging basique ne suffit pas pour l'audit.

  5. Adoptez les attributs PHP 8 pour les middleware. Plus propre, plus déclaratif, plus maintenable.


❓ FAQ

Q1 : L'Action doit-elle gérer les transactions ?

Ça dépend. Si l'Action fait plusieurs opérations atomiques (désactiver + activer + logger), oui, gérez la transaction dans l'Action. Sinon, laissez le contrôleur gérer. L'important, c'est la cohérence.

Q2 : Les API Resources sont-elles utiles si je n'utilise que Blade ?

Oui, et voici pourquoi : le jour où vous aurez besoin d'une API (et vous en aurez besoin — une SPA, une app mobile, un partenaire externe), vous serez prêt. Et même pour Blade, la Resource vous force à réfléchir à la représentation des données, ce qui améliore la qualité du code.

Q3 : Context ralentit-il l'application ?

Non. Context est conçu pour être léger. Il utilise un stockage local au processus, sans accès disque ni réseau. Les données sont sérialisées automatiquement dans les logs et les jobs sans surcoût significatif.

Q4 : Puis-je utiliser toResource() dans une vue Blade ?

Théoriquement oui, mais ce n'est pas l'usage principal. Les vues Blade ont besoin d'accéder aux propriétés des modèles directement ($annee->libelle). La Resource brille surtout pour le JSON. Pour Blade, gardez le modèle.

Q5 : Que faire si je veux fermer définitivement une année (verrouiller les notes) ?

C'est exactement le scénario pour lequel CloseAcademicYearAction a été conçu. Ajoutez une étape dans la méthode execute() :

$annee->evaluations()->update(['is_locked' => true]);
$annee->bilansCompetences()->update(['is_finalized' => true]);

Le contrôleur ne change pas. L'Action s'enrichit. La maintenance reste simple.


En résumé

On a vu comment transformer la gestion des années académiques avec Laravel 13 :

  • ✅ CloseAcademicYearAction extrait la logique métier du contrôleur

  • ✅ #[Middleware] attributs PHP 8 remplacent les appels middleware dans les routes

  • ✅ AnneeAcademiqueResource standardise la sérialisation avec when/whenCounted/whenAggregated

  • ✅ #[UseResource] sur le modèle permet l'auto-discovery par toResource()

  • ✅ Context::add('academic_year_id') assure une traçabilité automatique dans tous les logs

Ce n'est pas juste une refonte technique. C'est un changement de philosophie : chaque couche a un rôle précis, chaque classe fait une seule chose, et le système devient prévisible, testable et évolutif.

Alors, prêt à moderniser votre code ? Ouvrez votre IDE, créez votre première Action, et dites adieu aux contrôleurs spaghetti. Vous verrez, c'est jouissif.

Et surtout : amusez-vous !

Ajouter un commentaire

Vous devez être connecté pour laisser un commentaire.

Soyez le premier à commenter cet article !