Envoyer des rappels SMS en PHP depuis votre app Symfony

April 15, 2021
Rédigé par
Sylvan Ash
Contributeur
Les opinions exprimées par les contributeurs de Twilio sont les leurs
Révisé par

rappels-sms-php-symphony-banner

Si vous avez un système de réservations, et que vous prenez des rendez-vous de type massages, psychologues, dentaires ou médicaux, etc… ça serait sûrement pratique de rappeler aux clients la date de ce rendez-vous après qu’ils l’aient réservé, ou même à la personne qui leur offre le service.

Dans ce tutoriel, vous apprendrez à envoyer des rappels SMS aux clients à un temps donné avant leur rendez-vous dans un projet Symfony qui utilise le service SMS de Twilio.

Cet article part du principe que :

  • Vous avez déjà paramétré LAMP, MAMP, XAMPP, ou un environnement de développement équivalent.
  • Vous êtes familiers avec Symfony 

Mise en place

On utilisera composer (un outil pour la gestion des dépendances) afin d’installer Symfony et le  SDK Twilio. Les instructions d’installation de composer peuvent être trouvées ici. Aussi, soyez sûrs d’installer composer globalement en suivant les instructions dans la sous-partie globale.

Après composer, on aura d’abord besoin de mettre en place la base de données pour notre application avant de pouvoir installer Symfony. Cliquez sur les liens pour être guidés sur comment créer une base données en utilisant PHPMyAdmin ou SequelPro.

Utilisez votre outil de gestion de base de données préféré pour créer la bdd de notre application et appelez-là appointments.

Une fois que l’installation est complète, ouvrez votre terminal et allez dans le dossier où vous souhaitez créer votre projet. Exécutez ensuite la commande suivante pour créer un nouveau projet Symfony (un dossier où les fichiers seront créés automatiquement):

composer create-project symfony/framework-standard-edition Appointments

Dans notre cas, Appointments sera le nom du dossier et il sera automatiquement créé par composer.

Pendant l’installation, après que les fichiers du projets aient été téléchargés, composer va vous demander de fournir quelques paramètres de configuration pour l’application. Voici les plus importants :

  • Le nom de la base de données database_name (dans notre cas : Appointments)
  • Le nom utilisateur database_user et le mot de passe database_password pour accéder à la base de données.

Vous pouvez laisser les valeurs par défaut pour le reste des paramètres. Mais au cas où vous auriez besoin de les changer plus tard, vous pouvez le faire dans ce fichier projet : app/config/parameters.yml.

L’appli devrait maintenant être prête à l’emploi ! Pour tester si tout est bien paramétré, allez dans le dossier Appointments de votre app sur votre terminal et exécutez :

$ php bin/console server:run

Si tout fonctionne correctement, le serveur va tourner avec succès et nous donnera une URL (généralement http://localhost:8000) que l’on pourra utiliser pour voir notre appli. Ouvrez votre navigateur, rendez vous à l’URL indiquée et vous devriez voir quelque chose qui ressemble à ça :

capture d'écran de la page de base de symfony

Pour un petit rappel de Symfony, allez voir ce tutoriel.

Modèles

Maintenant que nous avons installé et paramétré notre appli, on va commencer par créer les modèles d’entités dont nous aurons besoin pour récupérer les informations des utilisateurs et les dates des rendez-vous.

On va d’abord créer un dossier Entity à l’intérieur de src/AppBundle - ça sera là que l’on rangera nos fichiers Entity. Ensuite, nous ouvrirons un nouveau fichier, User.php et nous ajouterons le code suivant :

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* User
*
* @ORM\Entity
* @ORM\Table(name="user")
*/
class User {
 /**
  * @var integer
  *
  * @ORM\Column(name="id", type="integer")
  * @ORM\Id
  * @ORM\GeneratedValue(strategy="AUTO")
  */
 protected $id;

 /**
  * @var string
  * @ORM\Column(name="name", type="string", length=255)
  */
 protected $name;

 /**
  * @var string
  * @ORM\Column(name="email", type="string", length=255)
  */
 protected $email;

 /**
  * @var string
  * @ORM\Column(name="phone", type="string", length=255, nullable=true)
  */
 protected $phoneNumber;

 public function getId() {
   return $this->id;
 }

 public function getName() {
   return $this->name;
 }
 public function setName($name) {
   $this->name = $name;
   return $this;
 }

 public function getEmail() {
   return $this->email;
 }
 public function setEmail($email) {
   $this->email = $email;
   return $this;
 }

 public function getPhoneNumber() {
   return $this->phoneNumber;
 }
 public function setPhoneNumber($phoneNumber) {
   $this->phoneNumber = $phoneNumber;
   return $this;
 }
}

Ci-dessus, c’est notre modèle d’entité User qui utilise Doctrine ORM pour mapper notre tableau User. Dans ce cas, nous stockons seulement le nom, l’e-mail, et le numéro de téléphone de l’utilisateur. Selon les besoins de votre application, vous pourrez entrer plus d’infos comme l’adresse, la date de naissance, le genre, etc…

Ensuite, nous ajouterons le fichier Appointment.php à notre dossier Entity pour notre entité de modèle Appointment. Ce qui nous intéresse ici, c’est la date et l’heure du rendez-vous, ainsi que l’identité de la personne qui l’a réservé. Le fichier ressemble donc à ça :

// src/AppBundle/Entity/Appointment.php
namespace AppBundle\Entity;

use AppBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;

/**
* Appointment
*
* @ORM\Entity
* @ORM\Table(name="appointment")
*/
class Appointment {
 /**
  * @var integer
  * @ORM\Column(name="id", type="integer")
  * @ORM\Id
  * @ORM\GeneratedValue(strategy="AUTO")
  */
 protected $id;

 /**
  * @var User
  * @ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
  * @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
  */
 protected $user;

 /**
  * @var \DateTime
  * @ORM\Column(name="date", type="datetime")
  */
 protected $date;

 public function getId() {
   return $this->id;
 }

 public function getUser() {
   return $this->user;
 }
 public function setUser(User $user) {
   $this->user = $user;
   return $this;
 }

 public function getDate() {
   return $this->date;
 }
 public function setDate(\DateTime $date) {
   $this->date = $date;
   return $this;
 }
}

On a ajouté une référence à user dans le tableau  appointment pour faciliter nos recherches plus tard.

Ensuite, ouvrez une nouvelle fenêtre dans votre terminal (comme la commande bin/console server:run va prendre le contrôle de la fenêtre de terminal dans lequel elle sera exécutée) et exécutez la commande suivante pour créer les tables dans votre base de données :  

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

Ajouter la data de base

Notre prochaine étape est d’ajouter quelques échantillons de données sur lesquelles nous pourrons travailler ensuite. On pourrait ajouter un controller avec les actions et vues nécessaires pour réaliser des opérations CRUD pour les utilisateurs et les rendez-vous, mais ce n’est pas adapté à notre cas. A la place, on peut soit : simplement ajouter les infos à la main dans la base de données, ou alors utiliser Doctrine Fixtures Bundle pour injecter la data dans la base de données.

Exécutez la commande suivante dans le terminal pour installer le bundle :

$ composer require --dev doctrine/doctrine-fixtures-bundle

Ensuite, ouvrez AppKernel.php à l’intérieur du dossier app (à la racine du projet) et permettez le bundle DoctrineFixtures. 

Attention : le bundle DoctrineFixtures doit être utilisé UNIQUEMENT à des fins de TEST. Cela veut dire qu’il ne doit pas être activé dans un environnement de production.

Nous l’activerons donc seulement dans des environnements de development ou test. Pour ça, il faut joindre une nouvelle instance du bundle au tableau $bundles dans la fonction registerBundlers() [Ligne 9] après avoir confirmé que nous sommes bien dans les environnements dev ou test [Ligne 22 pour une installation propre].

// app/AppKernel.php

// ...
// registerBundles()
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
   // ...
   $bundles[] = new Doctrine\Bundle\FixturesBundle\<em>DoctrineFixturesBundle</em>();
}

Ensuite, nous avons besoin d’ajouter un dossier DataFixtures à src/AppBundle et ajouter une classe AppFixtures à ce dossier. Il définira l’échantillon de données qui doit être ajouté.

// src/AppBundle/DataFixtures/AppFixtures.php
namespace AppBundle\DataFixtures;

use AppBundle\Entity\<em>User</em>;
use AppBundle\Entity\<em>Appointment</em>;
use Doctrine\Bundle\FixturesBundle\<em>Fixture</em>;
use Doctrine\Common\Persistence\<em>ObjectManager</em>;

<em>class</em> AppFixtures extends <em>Fixture</em> {
 public <em>function</em> load(<em>ObjectManager</em> $manager) {
   // notre tableau d'informations
   $usersInfo = [
     [
       'name' => 'Jon snow',
       'email' => 'jon.snow@thewatch.com',
       'phone' => '+1231234567890',
       'date' => '16-08-2018 13:00'
     ],
     [
       'name' => 'Arya Stark',
       'email' => 'arya.star@winterfell.com',
       'phone' => '+1230987654321',
       'date' => '16-08-2018 15:00'
     ],
     [
       'name' => 'Tyrion Lannister',
       'email' => 'tyrion.lannister@casterlyrock.com',
       'phone' => '+1234567890123',
       'date' => '17-08-2018 15:00'
     ]
   ];

   // Boucle qui itère sur notre tableau et crée nos users et leurs rendez-vous
   foreach ($usersInfo as $info) {
   // créer l'user
   $user = new <em>User</em>();
   $user->setName($info['name']);
   $user->setEmail($info['email']);
   $user->setPhoneNumber($info['phone']);
   $manager->persist($user);

   // obtenir un date object depuis une date string
   $date = date_create_from_format('d-m-Y H:i', $info['date']);

   // créer le rendez-vous pour l'user
   $appointment = new <em>Appointment</em>();
   $appointment->setDate($date);
   $appointment->setUser($user);
   $manager->persist($appointment);
   }

   $manager->flush();
 }
}

Note : Assurez-vous d’entrer la date actuelle pour tester facilement. Vous pouvez aussi entrer plus d’utilisateurs ou de rendez-vous si vous voulez !

Pour charger nos fixtures dans notre base de données, exécutez la commande suivante dans le terminal et sélectionnez y (yes) quand ça s’affichera :

$ php bin/console doctrine:fixtures:load

Installer Twilio

Maintenant que nous avons mis en place nos modèles d’entité et que notre base de données est remplie avec quelques données, allons voir comment on peut envoyer des rappels SMS !

On utilisera le SDK Twilio. Vous aurez besoin d’un compte Twilio et d’un numéro de téléphone ainsi que votre Account SID et Auth Token.

Rendez-vous ici pour créer un compte Twilio et prendre un numéro de téléphone. Une fois que ça sera fait, exécutez la commande suivante pour installer le SDK :

$ composer require twilio/sdk

Une fois le SDK installé, on aura besoin de le rendre accessible dans notre application, ce qu’on peut faire en l’exposant comme un service. Mais avant ça, ouvrez parameters.yml et ajoutez les valeurs de configuration suivantes :

# app/config/parameters.yml
parameters:
   # ...
   twilio_sid: <votre Twilio SID>
   twilio_token: <votre Auth Token>
   twilio_number: <votre numéro Twilio>

Ensuite, on ajoute un nouveau service dans services.yml pour exposer le Twilio SDK comme suit :

# app/config/services.yml
services:
   # ...
   twilio.client:
       class: Twilio\Rest\Client
       arguments: ['%twilio_sid%', '%twilio_token%']
  
   # Ajoute un alias pour le service twilio.client
   Twilio\Rest\Client: '@twilio.client'

Note : Assurez-vous d’inclure la ligne qui crée un alias pour le service twilio.client.

Avec ça, on peut désormais injecter le service n’importe où dans notre appli. Et on peut maintenant s’intéresser à la commande SMS.

Créer une commande SMS

On veut que le message de rappel SMS soit automatiquement envoyé à des heures prédéfinies via des crons. Donc nous avons besoin d’exécuter l’envoi de SMS depuis la ligne de commande avec une commande.

Ajoutons un dossier Command à notre AppBundle et créons la classe SmsCommand avec le code suivant :

// src/AppBundle/Command/SMSCommand.php
namespace AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\<em>ContainerAwareCommand</em>;
use Symfony\Component\Console\Input\<em>InputArgument</em>;
use Symfony\Component\Console\Input\<em>InputInterface</em>;
use Symfony\Component\Console\Input\<em>InputOption</em>;
use Symfony\Component\Console\Output\<em>OutputInterface</em>;
use \Twilio\Rest\<em>Client</em>;

<em>class</em> SmsCommand extends <em>ContainerAwareCommand</em> {
 private $twilio;

 public <em>function</em> __construct(<em>Client</em> $twilio) {
   $this->twilio = $twilio;
   parent::__construct();
 }

 protected <em>function</em> configure() {
   $this->setName('myapp:sms')
        ->setDescription('Send reminder text message');
 }

 protected <em>function</em> execute(<em>InputInterface</em> $input, <em>OutputInterface</em> $output) {
   $em = $this->getContainer()->get('doctrine');
   $userRepository = $em->getRepository('AppBundle:User');
   $appointmentRepository = $em->getRepository('AppBundle:Appointment');
  
   // On enverra des messages à tout le monde ayant un rendez-vous le jour même, un peu après minuit.
   // C'est pourquoi les heures de début et de fin que nous allons vérifier seront 00:00h et 23:59h.
   $start = new <em>\DateTime</em>();
   $start->setTime(00, 00);
   $end = clone $start;
   $end->modify('+1 days');
   $end->setTime(23, 59);

   // récupérer les rendez-vous prévus ce jour
   $appointments = $appointmentRepository->createQueryBuilder('a')
                                         ->select('a')
                                         ->where('a.date BETWEEN :now AND :end')
                                         ->setParameters(array(
                                           'now' => $start,
                                           'end' => $end,
                                         ))
                                         ->getQuery()
                                         ->getResult();
  
   if (count($appointments) > 0) {
     $output->writeln('SMSes to send: #' . count($appointments));
     $sender = $this->getContainer()->getParameter('twilio_number');

     foreach ($appointments as $appoint) {
       $user = $appoint->getUser();
       $message = $this->twilio->messages->create(
         $user->getPhoneNumber(), // Envoyer un message à ce numéro
         array(
           'from' => $sender, // numéro de téléphone Twilio
           'body' => 'Bonjour! Nous vous rappelons que votre rendez-vous est prévu pour aujourd\'hui à ' . $appoint->getDate()->format('H:i') . '. Appelez ' . $sender . ' pour toute demande ou question.'
         )
       );

       $output->writeln('SMS #' . $message->sid . ' sent to: ' . $user->getPhoneNumber());
     }
    
   } else {
     $output->writeln('No appointments for today.');
   }
 }
}

Le code ci-dessus regarde dans la table appointment de la base de données pour voir les rendez-vous prévus aujourd’hui, puis envoie des SMS via le SDK Twilio à chaque utilisateur qui à un rd prévu ce jour. A la fin, un message de confirmation s’affiche sur notre console, signe que le rappel a bien été envoyé.

Recommandation : Pour faciliter le test, je vous conseille de changer les numéros des utilisateurs (dans le tableau user) par votre numéro ou celui de quelqu’un proche de vous afin que vous puissiez vraiment confirmer que le SMS a bien été reçu.

On peut maintenant tester notre commande et voir si elle fonctionne comme prévu. Dans le terminal, exécutez :

$ php bin/console myapp:sms

S’il y a des rendez-vous programmés pour aujourd’hui, vous devriez voir, sur votre console, un rendu de tous les rappels SMS à envoyer et un message de confirmation une fois qu’ils le sont. S’il n’y a pas de rendez-vous, un message devrait s’afficher vous en informant.

Créer un Cronjob

Maintenant que nous avons correctement configuré notre commande et vérifié qu’elle fonctionnait, nous allons finir par la programmer pour qu’elle s’exécute à un moment précis de la journée.

Note : vous pouvez aussi paramétrer la commande pour qu’elle s’exécute à intervalle plus régulier qu’une fois par jour, selon vos besoins. Cependant, vous aurez besoin de mettre à jour les variables $start and $end dans votre code.

Pour ouvrir le crontab, exécutez la commande suivante dans le terminal :

$ crontab -e

Nous pouvons ensuite régler l’intervalle pour notre cronjob (une fois par jour à minuit), ainsi que la tâche à exécuter.

Note : Assurez-vous de définir le chemin absolu correct de bin/console

0 0 * * * php ~/www/Appointments/bin/console myapp:sms --env=prod

Félicitations, vous l’avez fait ! Vous pouvez maintenant envoyer des rappels de rendez-vous dans Symfony en utilisant les SMS Twilio. Vous pouvez trouver la version finale de cette application sur github.