Symfony et les templates de mail dynamiques

Je vais maintenant aborder un aspect important dans tout bon projet Web : L’envoi de mails.

Tout site internet a, dans 90% des cas (pour ma part) besoin d’envoyer des mails automatiques :
- confirmation d’inscription
- mot de passe oublié
- notifications pour messages reçus
- etc…

Pour mes projets, j’avais plusieurs besoins (ou envies ;-) )
- un jolie template de mail (c’est toujours agréable de recevoir un jolie mail)
- véhiculer l’information principale de ce mail
- apporter des informations dynamique pour donner vie au mail.

Avec Symfony 1.3 & 1.4, l’envoi de mail a été simplifié grâce à l’apparition de SfMailer. Mais, je voulais aller plus loin, en utilisant le même modèle Symfony -> actions/templates/partials/composants pour les contenus de mail.

Je vais vous présenter cela par un exemple concret : Une confirmation d’inscription.

1. Problématique

Suite à une inscription sur votre site Internet, vous désirez envoyer un email de remerciement accompagné des derniers membres inscris et la dernière news rédigée.

2. Le template de mail

Créer un fichier apps/frontend/templates/layout_mail.php avec dedans le contenu de votre template :

<html>
  <head>...</head>
  <body>
    <div class='header'> ... Votre haut de mail </div>
    <div class='content'> <?php echo $sf_content ?>  </div>
    <div class='footer'> ... Votre pied de mail </div>
  </body>
</html>

3. Centralisation des emails

L’idée est de centraliser dans un module tous les différents mails envoyés automatiquement depuis le site Internet. Créer un module mail :

symfony generate:module frontend mail

Editer le fichier actions.class.php de ce module pour rajouter l’action pour l’email d’inscription :

<?php
class mailActions extends sfActions
{
  public function preExecute()
  {
    //On précise ici que tous les mails utiliseront le layout que nous avons spécialement défini pour cela
    $this->setLayout('layout_mail');
  }
  public function executeInscription(sfWebRequest $request)
  {
    $this->member = Doctrine::getTable('member')->find($request->getParameter('id'));
  }
}

4. Dynamisation des mails grâce aux components

Créer le fichier components.class.php dans le module mail :

<?php

class mailComponents extends sfComponents
{
  public function executeLastMembers()
  {
    $this->members = Doctrine::getTable('member')->getLast();
  }
  public function executeLastNews()
  {
    $this->news = Doctrine::getTable('news')->getLast();
  }
}

Créer ensuite les deux templates pour ces components : _lastMembers.php et _lastNews.php avec dedans l’affichage des derniers membres et de la dernière news comme vous le souhaitez.

5. Le template pour le mail d’inscription

Créer le fichier inscriptionSuccess.php dans le dossier templates du module mail :

<div>
 <?php echo $member->getFullName() ?>, nous vous remercions pour votre inscription sur www.iw2.fr.
</div>

<!-- Nous incluons ici les deux components pour afficher les derniers membres et la dernière news -->

<?php include_component('mail', 'lastMembers')?>

<?php include_component('mail', 'lastNews')?>

6. Une classe mailer pour l’envoi des mails

Cette classe se compose de deux méthodes: une pour centraliser les sujets de mails et l’autre pour l’envoi. Créer un fichier mailer.class.php dans le dossier lib.

class mailer
{
  static public function getSubject($action)
  {
    $subject = array();
    $subject['inscription'] = "Confirmation de votre inscription";
    //$subject['nom_de_l'action_du_module_mail'] = "Le sujet de ce mail";
    return $subject[$action];
  }

  static public function send ($dest, $action)
  {
    //En premier paramètre nous avons l'adresse mail du destinataire ou un tableau d'emails. En second paramètre nous avons le nom de l'action du module mail

    //On initialise l'objet mailer avec le destinataire et le sujet.
    $mailer = sfContext::getInstance()->getMailer()->compose(
              null,
              $dest,
              mailer::getSubject($action)
    );
    //On initialise le FROM avec des valeurs définie dans le app.yml par exemple
    $mailer->setFrom(sfConfig::get('app_mailer_from'), sfConfig::get('app_mailer_name'));
    //On précise un rendu de type html
    $mailer->setContentType("text/html");
    //On rajoute le contenu du mail en récupérant le rendu de l'action du module mail
    $mailer->setBody(sfContext::getInstance()->getController()->getPresentationFor('mail', $action));
    //On envoi le mail
    sfContext::getInstance()->getMailer()->send($mailer);
  }
}

Penser à faire un symfony cc à ce niveau pour prendre en compte cette nouvelle classe.

7. Au niveau de l’action d’inscription

Dans votre action qui enregistre l’inscription, exemple :

public function executeCreate(sfWebRequest $request)
{
  $this->form = new memberForm();
  if( $request->isMethod('post') )
  {
    $this->form->bind($request->getParameter( $this->form->getName() ), $request->getFiles($this->form->getName()));
    if($this->form->isValid()) {
      $member = $this->form->save();
      //On passe un paramètre à l'objet Request pour le récupérer au niveau de l'action du module mail
      $this->getRequest()->setParameter('id', $member->getId());
      //On envoi le mail
      mailer::send($pro->getEmail(), 'inscription');

      $this->redirect('page/remerciement');
    }
  }
}

8. Exemple de rendu

mail-keldeco

9. Conclusion

N’ayant rien trouvé sur internet pour répondre à mes besoins en matière d’envoi de mail « dynamique », j’ai décidé de partager avec vous ma solution. Je conçois que celle-ci ne répond peut être pas à tous les critères de BEST PRACTICES Symfony pour le moment, mais c’est une première ébauche qui remplie 100% son rôle. N’hésitez pas si vous avez des questions ou des commentaires.

To be continued…


3 réponses à “Symfony et les templates de mail dynamiques”

  1. Hugo dit :

    Hello,

    Très bon article dans l’ensemble mais tu te compliques beaucoup la vie. Je vais apporter quelques éclaircissements et améliorations à apporter. D’une part, ta classe Mailer ne sert strictement à rien si ce n’est de rajouter de l’abstraction inutile et de la complexité en plus. Tu peux t’en passer. En plus, avec tes sfContext::getInstance() dedans, tu empêches ta classe d’être testée unitairement.

    Symfony permet de rendre un template partiel depuis une action à partir de la méthode $this->renderPartial(). Tu peux donc générer le contenu d’un template partiel dans ton action en appelant cette méthode.

    public function executeSendMail(sfWebRequest $request)
    {
    $this->composeAndSend(
    ‘from@example.com’,
    ‘to@example.com’,
    ‘Subject’,
    $this->renderPartial(’module/partial’, array(’var’ => ‘value’)
    );
    }

    Pour aller plus loin, Symfony a l’avantage d’intégrer un véritable support des mails. Avec Swift_Mailer, chaque mail est en réalité un objet Swift_Message. Tu peux donc créer une classe par mail qui dérive Swift_Message ou bien une superclasse abstraite pour partager les informations des mails. Par exemple :

    // lib/email/MyApplicationMail.class.php
    abstract class MyApplicationMail extends Swift_Message
    {
    // Shared code
    }

    // lib/email/ForgottenPasswordMail.class.php
    class ForgottenPasswordMail extends MyApplicationMail
    {
    public function __construct(…)
    {
    parent::__construct(…);

    $this->setFrom(’from@example.com’);
    $this->setSubject(’Your new password’);
    }
    }

    Et dans ton action tu instancies ta classe :

    public function executeSendMail(sfWebRequest $request)
    {
    $mail = ForgottenPasswordMail::newInstance()->
    setTo(’user@example.com’)->
    setBody($this->renderPartial(’module/partial’, array(’var’ => ‘value’))
    ;

    $this->getMailer()->send($mail);
    }

    Et voilà c’est tout simple, extensible et testable.

    Hugo.

  2. admin dit :

    Merci pour ces précieuses informations! Je vais reprendre cette partie de mon code pour l’améliorer.

  3. Sam dit :

    Bonjour à tous,

    petit correction,

    setBody($this->renderPartial(’module/partial’, array(’var’ => ‘value’))

    ne marche pas chez moi (symfony 1.4), après une recherche dans l’api :

    setBody($this->getPartial(’module/partial’, array(’var’ => ‘value’))

    marche :D ! J’espère que sa vous aidera !

Laisser un commentaire