Déplacer la logique dans un service applicatif
On va créer un service applicatif qui se chargera d'envoyer l'email à partir d'une instance de Newsletter.
On va donc créer une classe dans le dossier src/, là où se trouve la logique de notre application. On va même créer un namespace Mail, et créer le service dans le fichier src/Mail/NewsletterSubscribedConfirmation.php.
Création du service
Ce service sera donc une classe, nommée comme le fichier, et présentant une méthode send :
class NewsletterSubscribedConfirmation
{
public function send(Newsletter $newsletter)
{
$email = (new Email())
->from("admin@hb-corp.com")
->to($newsletter->getEmail())
->subject("Inscription à la newsletter")
->text("Votre email " . $newsletter->getEmail() . " a bien été enregistré, merci");
$mailer->send($email);
}
}
On a déplacé ici la logique de construction et d'envoi d'email qui se trouvait au préalable dans le contrôleur
Injecter le service Mailer dans un service
Nous avons déjà identifié que le type que nous pouvons type-hinter est MailerInterface. Ceci dit, l'injection de dépendances directement dans une méthode ne fonctionne que dans les contrôleurs.
Si on se trouve dans un service séparé, alors il va falloir procéder à une injection de dépendances par constructeur.
Le principe est simple : tout comme nous avons pu type-hinter un type dans une méthode de contrôleur, pour indiquer au container de services de quel type nous avions besoin, nous allons pouvoir type-hinter un type dans le constructeur d'un service. Ainsi, le container va faire en sorte de nous fournir l'instance de service correspondante, dont on pourra faire ce qu'on voudra. Ici pour nous, l'idée va donc être de déclarer un attribut privé correspondant à ce service, et lors de la construction, placer le service fourni par le container dans cet attribut :
class NewsletterSubscribedConfirmation
{
public function __construct(private MailerInterface $mailer)
{
}
//...
}
Par la suite, quand on va vouloir appeler la méthode send, on pourra alors utiliser l'attribut de notre classe :
public function send(Newsletter $newsletter)
{
//...
- $mailer->send($email);
+ $this->mailer->send($email);
}
Injecter le service dans un contrôleur
Une fois que notre service est prêt, nous devons alors l'utiliser dans notre contrôleur, en lieu et place de la MailerInterface précédemment utilisée. Ce qui tombe bien, c'est qu'avec la configuration par défaut de Symfony, tous nos services applicatifs sont autowirés :
config/services.yaml
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
On peut également vérifier que notre service est disponible en tant que type-hint avec la console :
php bin/console debug:autowiring Newsletter --all
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
(only showing classes/interfaces matching Newsletter)
App\Controller\NewsletterController
App\Form\NewsletterType
App\Mail\NewsletterSubscribedConfirmation
App\Repository\NewsletterRepository
Notre service étant disponible, nous pouvons alors le type-hinter dans le contrôleur :
public function register(
Request $request,
EntityManagerInterface $em,
NewsletterSubscribedConfirmation $notificationService
): Response {
//...
if ($form->isSubmitted() && $form->isValid()) {
//Enregistrer l'entité...
// Utilisation du service qu'on a type-hinté
$notificationService->send($newsletter);
//...
}
return $this->renderForm('newsletter/register.html.twig', [
'form' => $form
]);
}
L'injection de dépendances par constructeur fonctionne également dans une classe de contrôleurs. Cela peut être utile si vous consommez la même dépendance dans plusieurs contrôleurs (donc plusieurs méthodes), en déplaçant l'injection au moment de l'instanciation de la classe de contrôleurs