Logomark

LARAVEL

Un framework qui rend heureux
Voir cette catégorie
Vers le bas
Voir cette série
Cours Laravel 12 – les bases – injection de dépendance, conteneur et façades
Mardi 4 mars 2025 17:37

Dans ce chapitre, nous allons approfondir l'exemple précédent de l'envoi de photos en nous concentrant sur l'organisation du code. Laravel n'est pas seulement un framework pratique ; c'est aussi un style de programmation. Adopter ce style dès le début de l'apprentissage permet de développer rapidement de bonnes habitudes.

Bien que vous puissiez créer un site complet directement dans le fichier des routes ou vous contenter de contrôleurs pour tous les traitements, je vous propose une approche différente, plus alignée avec les meilleures pratiques offertes par Laravel.

Le problème et sa solution

Le problème

Je vous ai déjà expliqué qu'un contrôleur a pour mission de recevoir les requêtes et de renvoyer les réponses. Entre ces deux étapes, des traitements doivent être effectués pour construire la réponse. Parfois, cela peut être très simple, mais d'autres fois, cela peut être plus complexe et délicat. Globalement, le fonctionnement d'un contrôleur se résume ainsi :

Reprenons la méthode store de notre contrôleur PhotoController du précédent chapitre :

public function store(ImagesRequest $request): View
{
    $request->image->store(config('images.path'), 'public');
     
    return view('photo_ok');
}

Quel traitement effectuons-nous ici ? Nous récupérons l'image transmise et l'enregistrons.

La question est : un contrôleur doit-il savoir comment s'effectue ce traitement ? Si plusieurs contrôleurs de votre application doivent réaliser le même traitement, vous allez dupliquer ce code. Imaginez que vous souhaitiez ensuite modifier l'enregistrement des images, par exemple en les stockant sur le cloud. Vous devrez alors retoucher le code de tous vos contrôleurs ! La répétition de code n'est jamais une bonne pratique. Une règle de programmation saine veut que l'on commence à se poser des questions sur l'organisation du code dès que l'on fait des copies.

Cette recommandation est connue sous l'acronyme DRY (« Don't Repeat Yourself »).

Un autre aspect important à considérer est la testabilité des classes. Nous aborderons cet aspect crucial du développement, souvent négligé. Pour qu'une classe soit testable, sa mission doit être simple et clairement définie, et elle ne doit pas être étroitement liée à une autre classe. En effet, cette dépendance rend les tests plus difficiles.

La solution

Alors quelle est la solution ? L’injection de dépendance ! Voyons de quoi il s’agit. Regardez ce schéma :

Une nouvelle classe entre en jeu pour la gestion, c’est elle qui est effectivement chargée du traitement, le contrôleur fait juste appel à ses méthodes. Mais comment cette classe est-elle injectée dans le contrôleur ? Voici le code du contrôleur modifié :

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Requests\ImagesRequest;
use Illuminate\View\View;
use App\Repositories\PhotosRepository;

class PhotoController extends Controller
{
 
    public function create(): View
    {
        return view('photo');
    }
 
    public function store(ImagesRequest $request, PhotosRepository $photosRepository): View
    {
        $photosRepository->save($request->image);
         
        return view('photo_ok');
    }
}

Vous remarquerez que dans la méthode store, un nouveau paramètre de type App\Repositories\PhotosRepository a été ajouté. Nous utilisons la méthode save de cette classe injectée pour effectuer le traitement.

Ainsi, le contrôleur n'a plus besoin de savoir comment se déroule la gestion ; il sait simplement que la classe PhotosRepository s'en charge. Il se contente d'utiliser la méthode de cette classe, qui est « injectée ».

Vous vous demandez peut-être comment cette classe est injectée, c'est-à-dire comment et où cette instance est créée. Laravel s'en charge automatiquement.

PHP est très tolérant quant aux types de variables, bien que cela se soit amélioré à partir de la version 7. Lorsque vous déclarez une variable, vous n'êtes pas obligé de préciser si c'est une chaîne de caractères ou un tableau; PHP déduit le type en fonction de la valeur affectée. Il en va de même pour les paramètres des fonctions. Cependant, rien ne vous empêche de déclarer un type, comme je l'ai fait ici pour le paramètre de la méthode. C'est même indispensable pour que Laravel sache quelle classe est concernée. En déclarant le type, Laravel peut créer une instance de ce type et l'injecter dans le contrôleur.

Pour trouver la classe, Laravel utilise l'introspection (reflection en anglais) de PHP, qui permet d'inspecter le code en cours d'exécution. Elle permet également de manipuler le code, par exemple en créant un objet d'une certaine classe. Vous pouvez trouver plus d'informations dans le manuel PHP.

La gestion

Maintenant qu’on a dit au contrôleur qu’une classe s’occupe de la gestion, il nous faut la créer. Pour bien organiser notre application, on crée un nouveau dossier et on place notre classe dedans :

Le codage ne pose aucun problème parce qu’il est identique à ce qu’on avait dans le contrôleur :

<?php
 
namespace App\Repositories;
 
use Illuminate\Http\UploadedFile;
 
class PhotosRepository
{
    public function save(UploadedFile $image)
    {
        $image->store(config('images.path'), 'public');
    }
}

Attention à ne pas oublier les espaces de noms !

Désormais notre code est parfaitement organisé et facile à maintenir et à tester.

Mais allons un peu plus loin, créons une interface pour notre classe :

Avec ce code :

<?php
 
namespace App\Repositories;
 
use Illuminate\Http\UploadedFile;
 
interface PhotosRepositoryInterface
{
  public function save(UploadedFile $image);
}

Il suffit ensuite d’en informer la classe PhotosRepository :

class PhotosRepository implements PhotosRepositoryInterface

Ce qui serait bien maintenant serait dans notre contrôleur de référencer l’interface :

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Requests\ImagesRequest;
use Illuminate\View\View;
use App\Repositories\PhotosRepositoryInterface;

class PhotoController extends Controller
{
 
    public function create(): View
    {
        return view('photo');
    }
 
    public function store(ImagesRequest $request, PhotosRepositoryInterface $photosRepository): View
    {
        $photosRepository->save($request->image);
         
        return view('photo_ok');
    }
}

Le souci, c'est que Laravel n’arrive pas à deviner la classe à instancier à partir de cette interface : 

Comment s’en sortir ? 

Lorsque j’ai présenté la structure de Laravel j’ai mentionné la présence de providers :
À quoi sert un provider ? Tout simplement à procéder à des initialisations : événements, middlewares, et surtout des liaisons de dépendance. Laravel possède un conteneur de dépendances qui constitue le cœur de son fonctionnement. C’est grâce à ce conteneur qu’on va pouvoir établir une liaison entre une interface et une classe.

Ouvrez le fichier app\Providers\AppServiceProvider.php et ajoutez cette ligne de code :

public function register(): void
{
    $this->app->bind(
        'App\Repositories\PhotosRepositoryInterface',
        'App\Repositories\PhotosRepository'
    );
}

La méthode register est activée au démarrage de l’application, c’est l’endroit idéal pour notre liaison. Ici, on dit à l’application (app) d’établir une liaison (bind) entre l’interface App\Repositories\PhotosRepositoryInterface et la classe App\Repositories\PhotosRepository. Ainsi, chaque fois qu’on se référera à cette interface dans une injection, Laravel saura quelle classe instancier. Si on veut changer la classe de gestion, il suffit de modifier le code du provider.

Maintenant notre application fonctionne.

Si vous obtenez encore un message d’erreur vous disant que l’interface ne peut pas être instanciée, lancez la commande :

composer dumpautoload

Les façades

Laravel propose de nombreuses façades pour simplifier la syntaxe. On l'a vu de façon récurrente pour les routes :

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Une façade est une interface statique pour une classe disponible dans le conteneur de services de Laravel. Laravel propose des façades pour la plupart de ses classes, simplifiant ainsi la syntaxe et les tests. Avec les façades, il n'est pas nécessaire d'injecter la classe correspondante.

Mais comment fonctionnent ces façades ?

Laravel dispose d'une classe de base, Illuminate\Support\Facades\Facade, qui contient le code nécessaire pour faire fonctionner les façades. Par conséquent, toutes les façades créées doivent hériter de cette classe de base.

Par exemple, pour la gestion des routes, nous avons la façade Route, qui correspond à la classe Illuminate\Support\Facades\Route. Examinons cette classe :

<?php
 
namespace Illuminate\Support\Facades;
 
...
class Route extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'router';
    }
}

On se contente de retourner router. Il faut aller voir dans le fichier Illuminate\Routing\RoutingServiceProvider pour trouver l’enregistrement du router :

protected function registerRouter()
{
    $this->app->singleton('router', function ($app) {
        return new Router($app['events'], $app);
    });
}

Les providers permettent d’enregistrer des composants dans le conteneur de Laravel. Ici, on déclare router et on voit qu’on crée une instance de la classe Router (new Router…). Le nom complet est Illuminate\Routing\Router. Si vous allez voir cette classe, vous trouverez les méthodes qu’on a utilisées dans ce chapitre, par exemple get :

public function get($uri, $action = null)
{
    return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}

Autrement dit, si j’écris en utilisant la façade :

Route::get('/', function() { return 'Coucou'; });

J’obtiens le même résultat que si j’écris en allant chercher le routeur dans le conteneur :

$this->app['router']->get('/', function() { return 'Coucou'; });

Ou encore en utilisant un helper :

app('router')->get('/', function() { return 'Coucou'; });

La différence est que la première syntaxe est plus simple et intuitive, mais certains n’aiment pas trop ce genre d’appel statique.

En résumé

  • Un contrôleur doit déléguer toute tâche qui ne relève pas de sa responsabilité principale.
  • L'injection de dépendances permet de séparer clairement les tâches, facilitant ainsi la maintenance du code et les tests unitaires.
  • Les providers permettent d'effectuer des initialisations, notamment en établissant des liens de dépendance entre interfaces et classes.
  • Laravel propose de nombreuses façades qui simplifient la syntaxe et l'utilisation des fonctionnalités.
  • Des helpers sont également disponibles pour simplifier la syntaxe et rendre le code plus lisible.


Par bestmomo

Aucun commentaire

Article précédent : Cours Laravel 12 – les bases – configuration, session et gestion de fichiers
Article suivant : Cours Laravel 12 – les données – migrations et modèles
Cet article contient un quiz. Vous devez être authentifié pour y avoir acces.