Séance 1 : Notion de service, exemple du mail

Le concept de service a déjà été évoqué en S3, il s'agit ici d'un rappel et d'une mise en pratique.

Cours

Dans Symfony, tous les objets (au sens d'un ensemble de code qui apporte une fonctionnalité : mail, base de données, entitée, ...) sont des services. Dès l'instanciation d'une application symfony, de nombreux services sont lancés et accessible de partout dans l'application. Il est aussi possible de créer ses propres services.

La documentation officielle de Symfony sur les services : https://symfony.com/doc/current/service_container.html

Utilisation d'un service existant (rappel sur l'usage des mails)

Dans un controller, on peut utiliser un service existant en l'injectant dans la méthode de notre classe "controller". Par exemple, pour envoyer un mail, on peut utiliser le service mailer :

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mailer\MailerInterface;

class MailController extends AbstractController
{
    #[Route("/mail", name:"mail")]
    public function index(MailerInterface $mailer): Response
    {
        $email = (new Email())
            ->from('mail@mail.com')
            ->to('destinataire@mail.com')
            ->subject('Hello Email')
            ->text('Sending emails is fun again!')
            ->html('<p>See Twig integration for better HTML integration!</p>');

        $mailer->send($email);

        return $this->render('mail/index.html.twig', []);

    }
}

Dans cet exemple, on injecte le service mailer (par l'intermédiaire de son interface MailerInterface) dans la méthode index de notre controller. On peut ensuite utiliser ce service pour envoyer un mail.

Grâce au mécanisme d'injection de dépendance de Symfony, le service mailer est automatiquement instancié et injecté dans notre controller. On peut donc l'utiliser dans notre méthode index. Il n'est pas nécessaire de l'instancier nous même et de le passer à la méthode. Cela fonctionne parce que tout est considéré comme un service dans Symfony.

Il est possible de cumuler des services et des paramètres issus de nos routes. D'ailleurs, vous le faites déjà dans vos controllers. Par exemple, dans le controller EditeurController, on pourrait avoir :

<?php

namespace App\Controller;

use App\Entity\Editeur;
use App\Form\EditeurType;
use App\Repository\EditeurRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class EditeurController extends AbstractController
{
    #[Route('/editeur', name: 'editeur')]
    public function index(EditeurRepository $editeurRepository): Response
    {
        $editeurs = $editeurRepository->findAll();

        return $this->render('editeur/index.html.twig', [
            'editeurs' => $editeurs,
        ]);
    }

    #[Route('/editeur/{id}/edit', name: 'editeur_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, Editeur $editeur, EntityManagerInterface $entityManager): Response
    {
        $form = $this->createForm(EditeurType::class, $editeur);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->flush();

            return $this->redirectToRoute('editeur');
        }

        return $this->render('editeur/edit.html.twig', [
            'editeur' => $editeur,
            'form' => $form->createView(),
        ]);
    }
}

La méthode index utilise le service EditeurRepository pour récupérer tous les éditeurs. La méthode edit utilise les services Requestet EntityManagerInterface pour récupérer les données du formulaire et sauvegarder les modifications d'un article, qui est passé en paramètre de la méthode et dans la route.

Notez au passage que la récupération de l'éditeur déclenche aussi un mécanisme particulier qui vient executer une requete SQL pour récupérer l'éditeur à partir de l'id passé en paramètre de la route. C'est le mécanisme de "ParamConverter".

Création d'un service

Tout comme nous disposons de nombreux services déjà créés, il est possible de créer ses propres services. Pour cela, il faut créer une classe qui va contenir le code de notre service. Vous pouvez mettre ce fichier dans un repertoire dédié, par exemple src/Service. Par définition et sauf indication contraire cette classe sera considérée comme un service par Symfony.

Exemple, créons un service qui va nous permettre de récupérer le nombre d'éditeurs dans la base de données :

<?php

namespace App\Service;

use App\Repository\EditeurRepository;

class EditeurService
{
    public function __construct(private EditeurRepository $editeurRepository)
    {
    }

    public function countEditeurs(): int
    {
        return $this->editeurRepository->count([]);
    }
}

Dans cet exemple, on a créé une classe EditeurService qui contient une méthode countEditeurs qui va nous permettre de récupérer le nombre d'éditeur dans la base de données.

Notez que dans ce service, on utilise le service EditeurRepository qui est injecté dans le constructeur de notre classe.

Pour utiliser ce service, on peut l'injecter dans un controller :

<?php

namespace App\Controller;

use App\Service\EditeurService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class EditeurController extends AbstractController
{
    #[Route('/editeur', name: 'editeur')]
    public function index(EditeurService $editeurService): Response
    {
        $count = $editeurService->countEditeurs();

        return $this->render('editeur/index.html.twig', [
            'count' => $count,
        ]);
    }
}

A quoi ca sert ?

Les services sont très utiles pour plusieurs raisons :

  • Ils permettent de découper notre code en plusieurs parties. Par exemple, on peut avoir un service qui va nous permettre de récupérer les articles, un autre qui va nous permettre de récupérer les utilisateurs, un autre qui va nous permettre de récupérer les commentaires, etc.

  • On peut ainsi avoir un service par entité. Cela permet de découper notre code en plusieurs parties et de rendre notre code plus lisible et plus maintenable.

Il n'y a pas de règle pour savoir quand créer un service. Cela dépend de votre code et de vos besoins. Par exemple, si vous avez un controller qui fait trop de choses, vous pouvez créer un service qui va vous permettre de déplacer une partie du code dans ce service.

L'objectif est de garder vos contrôleurs et les méthodes qu'ils contiennent relativement légers. Les services permettent aussi de réduire le code qui serait dupliqué dans plusieurs contrôleurs en mettant à un seul endroit les méthodes qui sont communes à plusieurs contrôleurs.

Exercice de (re)découverte

Reprenons quelques bases

Pour faciliter le point de départ, vous avez une correction du S3 à votre disposition dans le dossier ci-dessous. Cette correction peut être incomplète par rapport au S3 et peu contenir des différences de nomages ou d'organisation.

Ou via GitHub : https://github.com/Dannebicque/mmiples4.git

Un fois le dossier récupéré il faut lancer les commandes suivantes :

  • Installer les dépendances :

composer install
  • Créer et mettre à jour le fichier .env.local avec les informations de connexion à votre base de données.

  • puis créer la base de données :

php bin/console doctrine:database:create
  • puis créer les tables :

php bin/console doctrine:schema:update --force

On pourrait utiliser une migration comme vu en S3, mais pour simplifier nous allons utiliser cette commande qu va ici reconstruire toutes les tables de la base de données

  • Charger les données du fichier sql qui se trouve dans le dossier.

  • N'oubliez pas de faire le nécessaire du côté de docker pour rendre ce projet accessible depuis votre navigateur sur l'adresse http://mmiple.mmi-troyes:8319/. (Url à adapter en fonction de votre configuration).

Utilisation du mailer

Dans un premier temps vous allez créer un contrôleur et une méthode, associée à une route permettant d'envoyer un mail avec avec des informations pré-remplies (n'hésitez pas à reprendre les séances du S3 sur le mailer).

Création d'un service

Dans un deuxième temps nous allons utiliser notre propre service de mailer pour gérer l'envoi des mails.

L'intérêt de faire cela est que nous pourrons ainsi définir un ensemble de paramètre par défaut pour nos mails (expéditeur, destinataire, sujet, etc.) et ainsi simplifier l'utilisation de notre service, sans repeter à chaque fois que nécessaire ces paramètres.

L'autre intéret est que nous pourrons ainsi facilement changer les paramètres de notre service de mailer, voire changer de bibliothèque, sans avoir à modifier le code de nos contrôleurs.

Cela évite donc que nos contrôleurs et notre code ne soit trop dépendant des bibliothèques que nous utilisons.

  1. Créer une classe MailerService dans le dossier src/Service et ajouter les méthodes suivantes :

    • le constructeur qui prend en paramètre l'objet MailerInterface et qui le stocke dans une propriété

    • sendMail : qui prend en paramètre le destinataire, le sujet et le corps du mail et qui envoie le mail

  2. Modifier le contrôleur pour utiliser le service de mailer

  3. Pour aller plus loin

    1. Ajoutez une méthode qui permet d'envoyer un email en utilisant "twig"

      1. Quelles sont les propriétés nécessaires ?

      2. Comment modifier le code ?

Exercice de mise en pratique

  • Créer un formulaire de contact.

  • Créer un nouveau contrôleur nommé ContactController

  • Ajouter une méthode index qui va se charger d'afficher le formulaire de contact (adresse de l'expéditeur, sujet, message), vous serez le destinataire par défaut..

  • Ajouter une méthode send qui va se charger de récupérer les informations du formulaire et de les envoyer par mail en utilisant le service de mailer.

Dernière mise à jour

Cet article vous a-t-il été utile ?