• 15 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Mis à jour le 27/04/2018

L'injection de dépendance

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans ce chapitre nous allons reprendre l'exemple précédent de l'envoi de photos en nous posant des questions d'organisation du code. Laravel ce n'est pas seulement un framework pratique, c'est aussi un style de programmation. Il vaut mieux évoquer ce style le plus tôt possible dans l'apprentissage pour prendre rapidement les bonnes habitudes.

Vous pouvez très bien créer un site complet dans le fichier des routes, vous pouvez aussi vous contenter de contrôleurs pour effectuer tous les traitements nécessaires. Je vous propose une autre approche, plus en accord avec ce que nous offre Laravel.

Le problème et sa solution

Le problème

Je vous ai déjà dit qu'un contrôleur a pour mission de réceptionner les requêtes et d'envoyer les réponses. Entre les deux il y a évidemment du traitement à effectuer, la réponse doit se construire, parfois c'est très simple, parfois plus long et délicat. Mais globalement nous avons pour un contrôleur ce fonctionnement :

Fonctionnement d'un contrôleur
Fonctionnement d'un contrôleur

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

<?php
public function postForm(ImagesRequest $request)
{
    $image = $request->file('image');

	if($image->isValid())
	{
		$chemin = config('images.path');

		$extension = $image->getClientOriginalExtension();

		do {
			$nom = str_random(10) . '.' . $extension;
		} while(file_exists($chemin . '/' . $nom));

		if($image->move($chemin, $nom)) {
			return view('photo_ok');
		}
	}

	return redirect('photo')
		->with('error','Désolé mais votre image ne peut pas être envoyée !');
}

Qu'avons-nous comme traitement ? On récupère les références de l'image transmise, on récupère le dossier de destination dans la configuration, on trouve l'extension du fichier image, on génère un nom et enfin on enregistre l'image.

La question est : est-ce qu'un contrôleur doit savoir comment s'effectue ce traitement ? Si vous avez plusieurs contrôleurs dans votre application qui doivent effectuer le même traitement vous allez multiplier cette mise en place. Imaginez que vous avez ensuite envie de modifier l'enregistrement des images, par exemple en les mettant dans une base de données, vous allez devoir retoucher le code de tous vos contrôleurs ! La répétition de code n'est jamais une bonne chose, une saine règle de programmation veut qu'on commence à se poser des questions sur l'organisation du code dès qu'on fait des copies.

Un autre élément à prendre en compte aussi est la testabilité des classes. Nous verrons cet aspect important du développement trop souvent négligé. Pour qu'une classe soit testable il faut que sa mission soit simple et parfaitement identifiée et il ne faut pas qu'elle soit étroitement liée avec 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 :

L'injection de la gestion
L'injection de la gestion

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 App\Gestion\PhotoGestion;

class PhotoController extends Controller {

    public function getForm()
	{
		return view('photo');
	}

	public function postForm(
		ImagesRequest $request,
		PhotoGestion $photogestion)
	{

		if($photogestion->save($request->file('image'))) {
			return view('photo_ok');
		} 
		return redirect('photo')
			->with('error','Désolé mais votre image ne peut pas être envoyée !');
	
	}

}

Vous remarquez qu'au niveau de la méthode postForm il y a un nouveau paramètre de type App\Gestion\PhotoGestion. On utilise la méthode save de la classe ainsi injectée pour faire le traitement.

De cette façon le contrôleur ignore totalement comment se fait la gestion, il sait juste que la classe PhotoGestion sait la faire. Il se contente d'utiliser la méthode de cette classe qui est "injectée". Maintenant vous vous demandez sans doute comment cette classe est injectée là, en d'autres termes comment et où est créée cette instance. Eh bien Laravel est assez malin pour le faire lui-même.

PHP est très tolérant sur les types des variables. Lorsque vous en déclarez une vous n'êtes pas obligé de préciser que c'est un string ou un array. PHP devine le type selon la valeur affectée. Il en est de même pour les paramètres des fonctions. Mais personne ne vous empêche de déclarer un type comme je l'ai fait ici pour le paramètre de la méthode (malheureusement pour le moment PHP ne reconnait que les tableaux et les classes). C'est même indispensable pour que Laravel sache quelle classe est concernée. Étant donné que je déclare le type, Laravel est capable de créer une instance de ce type et de l'injecter dans le contrôleur.

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 dossier de gestion

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

<?php 

namespace App\Gestion;

class PhotoGestion
{

    public function save($image)
	{
		if($image->isValid())
		{
			$chemin = config('images.path');
			$extension = $image->getClientOriginalExtension();

			do {
				$nom = str_random(10) . '.' . $extension;
			} while(file_exists($chemin . '/' . $nom));

			return $image->move($chemin, $nom);
		}

		return false;
	}

}

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

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

L'interface pour la classe PhotoGestion

Avec ce code :

<?php 

namespace App\Gestion;

interface PhotoGestionInterface
{

  public function save($image);

}

Il suffit ensuite d'en informer la classe PhotoGestion :

<?php
class PhotoGestion  implements PhotoGestionInterface

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 App\Gestion\PhotoGestionInterface;

class PhotoController extends Controller
{

    public function getForm()
	{
		return view('photo');
	}

	public function postForm(
		ImagesRequest $request,
		PhotoGestionInterface $photogestion)
	{

		if($photogestion->save($request->file('image'))) {
			return view('photo_ok');
		} 
		return redirect('photo')
			->with('error','Désolé mais votre image ne peut pas être envoyée !');
	
	}

}

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

Laravel ne sait pas instancier l'interface

Comment s'en sortir ? 

Lorsque j'ai présenté la structure de Laravel j'ai mentionné la présence de providers :

Les providers

A 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 :

<?php 
public function register()
{
    ...

	$this->app->bind(
		'App\Gestion\PhotoGestionInterface', 
		'App\Gestion\PhotoGestion'
	);
}

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\Gestion\PhotoGestionInterface et la classe App\Gestion\PhotoGestion. 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 :).

En résumé

  • Un contrôleur doit déléguer toute tâche qui ne relève pas de sa compétence.

  • L'injection de dépendance permet de bien séparer les tâches, de simplifier la maintenance du code et les tests unitaires.

  • Les providers permettent de faire des initialisations, en particulier des liaisons de dépendance entre interfaces et classes.

Exemple de certificat de réussite
Exemple de certificat de réussite