Cours de Rémi JarjatCours de Rémi Jarjat
  • Liste des cours
  • Culture numérique
  • Git
    • Terminologie
    • Avant de commencer
    • Créer un dépôt (local)
    • Enregistrer des changements
    • Des branches
    • Mise en commun du travail
    • Annuler des changements
    • Réécrire l'historique
    • Des outils pour se simplifier Git
    • Exercices
    • Exemples pratiques
  • Linux
    • Installation
    • Historique
    • Rangement des fichiers
    • Les processus
    • Commandes de base
    • Commandes avancées
    • /linux/6-other-technologies.html
    • Exercices
    • Correction des exercices
  • PHP
    • Environnement de travail
    • Bases du PHP
    • Tests et boucles
    • Procédures et fonctions
    • Interagir avec l'utilisateur
    • La temporisation de sortie
    • PHP Doc et PSR
    • PHP Orienté objet
    • Héritage et objets
    • Factorisation
    • Manipuler la BdD avec PDO (PHP Data Object)
    • Architecture MVC
    • Webservices REST
    • Exercices - Bases
    • Exercices - Séparer en plusieurs fichiers
    • Exercices - POST et SESSION
    • Exercices - Panier et validation
    • Exercices - Objets
    • Exercices - BdD avec PDO
    • Projet - montage d'ordinateurs
    • Projet - Personnages de Jeux de Rôle
  • Symfony
    • Installer Symfony et son environnement de travail
    • Structure et utilisation d'un projet
    • Le routing
    • Les controllers
    • Twig
    • Les services et l'injection de dépendances
    • Doctrine et la BdD
    • Formulaires
    • Les traductions
    • Event listeners/subscribers
    • Connexion et sécurisation
    • Bundles
    • Easy Admin Bundle
    • API Platform
    • Pense-bêtes
    • Symfony au quotidien
    • Travailler avec Docker
    • Projet : annonces de SPA / éleveurs
    • Exercices
  • Javascript
    • Les bases du langage
    • Manipulation logique
    • Le DOM
    • JQuery
    • Ajax
    • Programmation orientée objet
    • Webpack
    • Outils utiles
    • Révisions
  • Serveur Lamp
  • Déploiement
    • Des outils et manières de faire
    • Déploiement par FTP
    • Wordpress
    • Intégrer Git dans le processus
    • GitHub Pages pour déployer facilement
    • Symfony et Angular
  • Docker
  • Intégration continue
  • Sécurité informatique

Les services et l'injection de dépendances

  • Pour résumer
  • Comprendre l'injection de dépendances

La documentation spécifique aux services

Pour résumer

  • Un service est une classe, dont une instance est créé par Symfony au chargement (on ne fait jamais nous-même un new sur un service)
  • Toutes les classes dans le dossier src, à part les entités (et quelques autres éléments) sont des services
  • Un service est une fonctionnalité, qui peut être injectée dans le constructeur de n'importe quel autre service ou dans des actions de controller.

Comprendre l'injection de dépendances

Avec Symfony, tout est service. Un service est une fonctionnalité (peut être seulement une classe), comme l'affichage d'un template, l'envoie des emails, etc. Il en existe déjà de nombreux dans Symfony, déjà fournis (comme Twig, plusieurs éléments de Doctrine ou un Mailer).

Dans les faits, l'exemple suivant return $this->render('blog/index.html.twig', ['page' => 3]); fait appel, en interne, au service Twig pour construire le HTML et le met dans un objet Response.

Nous avons donc déjà utilisé un service !

Dans les faits, une partie des services sont disponibles dans ce que l'on appelle le Container (ni plus ni moins un tableau d'objets utilisables).

Dans les faits, la déclaration des services est faite dans le fichier config/services.yaml.

Ce sont ces lignes qui se chargent du plus gros du travail :

services:
    # La configuration de base sur Symfony
    _defaults:
        autowire: true # Injection de dépendance automatique (il suffit de déclarer dans le controller d'un service d'autres service dont on va avoir besoin pour effectivement les avoir dans ce premier service)
        autoconfigure: true # Nous pouvons déclarer des services spéciaux, cette configuration assure qu'ils seront déclarés comme tels automatiquement
        public: false # Par défaut, aucun de nos services ne sont publiques (c'est à dire que très peu de services sont disponibles directement depuis le controller) 
        
    # Ce que disent ces lignes : toutes les classes dans le dossier source sont définies comme des services.
    # À l'exception du contenu des dossiers DependencyInjection, Entity, Migrations et Tests et du fichier Kernel.php, qui ne sont donc pas considérés comme des services
    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
        
    # Ces lignes viennent modifier la configuration pour le dossier src/Controller. Elles associent le tag controller.service_arguments aux controllers, qui permet de leur donner un comportement spécifique (être liés à des routes par exemple)
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

Ainsi, un service de génération de mail pourra être déclaré comme suit :

// src/Service/MailGenerator.php
namespace App\Service;

use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class MailGenerator
{
    /** @var MailerInterface */
    private $mailer;

    // On demande un service qui implémente l'interface MailerInterface (voir la doc de Symfony pour la liste des services disponibles ;) )
    // Le service correspondant est automatiquement injecté
    public function __construct(MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }

    public function generateAndSend(string $message, string $emailDest = 'you@example.com'): bool
    {
        $email = (new Email())
            ->from('admin@example.com')
            ->to($emailDest)
            ->subject('Un message !')
            ->text($message);

        // Nous pouvons utiliser notre service $mailer où bon nous chante, comme dans n'importe quel objet
        $this->mailer->send($email);

        // ...

        return true;
    }
}

Maintenant, imaginons que nous voulions ajouter un deuxième paramètre à notre constructeur, mais pas un service. Par exemple, l'adresse utilisée pour l'envoi, que nous voulons récupérer dans la configuration. Il faut alors modifier notre service et le déclarer manuellement.

// src/Service/MailGenerator.php
namespace App\Service;

use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class MailGenerator
{
    /** @var MailerInterface */
    private $mailer;

    /** @var string */
    private $fromEmail;

    // On demande un service qui implémente l'interface MailerInterface (voir la doc de Symfony pour la liste des services disponibles ;) )
    // Le service correspondant est automatiquement injecté
    public function __construct(MailerInterface $mailer, string $fromEmail)
    {
        $this->mailer = $mailer;
        $this->fromEmail = $fromEmail;
    }

    public function generateAndSend(string $message, string $emailDest = 'you@example.com'): bool
    {
        $email = (new Email())
            ->from($this->fromEmail)
            ->to($emailDest)
            ->subject('Un message !')
            ->text($message);

        // Nous pouvons utiliser notre service $mailer où bon nous chante, comme dans n'importe quel objet
        $this->mailer->send($email);

        // ...

        return true;
    }
}

Pour déclarer manuellement notre service, tout en profitant (quand même) de l'autowiring, il suffit d'ajouter un paramètre pour tous nos services, ou de surcharger la déclaration de notre service. Les deux possibilités sont présentées ci-dessous, il suffit d'en choisir une.

services:
    _defaults:
        autowire: true 
        autoconfigure: true 
        public: false  
        bind: # Ici, nous pouvons directement associer TOUS les paramètres $fromEmail qui sont à injecter dans des services
            # Dès qu'une variable $fromEmail est déclarée dans le constructeur d'un service (ou une action de contrôleur)
            # on lui donnera une valeur
            $fromEmail: 'admin@example.com'
    
    # ...
    # Tout ce qui était avant est inchangé
    # ...
    
    # Ou nous pouvons surcharger la définition de notre service, pour modifier ses paramètres
    App\Service\MailGenerator: # L'identifiant de notre service est son FQCN (nom complet de la classe)
        arguments: # On modifie le comportement de l'injection en lui disant de modifier les arguments (du constructeur) du service
            $fromEmail: 'admin@example.com' # on fait alors correspondre notre paramètre $fromEmail à une valeur

Et voilà ! Notre service est configuré et prêt à l'emploi. Nous pouvons maintenant l'appeler dans un controller ou n'importe quel autre service (en utilisant là encore l'autowiring pour l'appeler) :

// src/Controller/MailController.php
namespace App\Controller;

use App\Service\MailGenerator; // On va avoir besoin de cette classe et on va utiliser son nom court
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class MailController extends AbstractController
{
    /**
     * @Route("/email/envoie", name="mail_send")
     */
    public function sendMail(MailGenerator $mailGenerator): Response
    {
        $mailGenerator->generateAndSend('Un message à caractère informatif !', 'toto@example.com');
        
        return $this->render('mail/sendMail.html.twig');
    }
}

L'un des services que nous allons le plus souvent injecter dans les controllers et l'objet Request de Symfony, mais nous en parlerons dans la partie sur les formulaires.

Pour résumer, dans les paramètres de notre action, nous pouvons récupérer :

  • les paramètres de notre route
  • des services que nous injectons pour les utiliser dans l'action.

L'un des principaux objectifs de ce découpage en services est de réduire le controller à son minimum et ce pourquoi il est fait : être un chef d'orchestre entre la requête et le rendu final.

Dernières mise à jour :
Prev
Twig
Next
Doctrine et la BdD