How to Send SMS Reminders from PHP Symfony Applications

September 25, 2018
Written by
Sylvan Ash
Contributor
Opinions expressed by Twilio contributors are their own

Symfony Logo wtih Alarm Clock

If you have a booking system, making appointments such as massage or other therapist bookings, dental or medical appointments, etc., you’d probably like to remind the client about the booking they made on the day of the appointment. In some cases, you might also want to remind the person offering the service. In this tutorial, you'll learn how to send SMS reminders to clients of their upcoming massage appointments, at a designated time before the appointment, in a Symfony project using Twilio's SMS service.

This post assumes that:

  • You have already set up a LEMP, MAMPXAMPP, or equivalent development environment.
  • You are familiar with the Symfony environment.

Setup

We’ll be using composer (a tool for dependency management) to install Symfony and Twilio's SDK. Composer installation instructions can be found here. Also, be sure to install composer globally, by following the instructions in the global sub-section.

After installing composer, we’ll first need to set up the database for our application before we install Symfony. Click on the links for a simple walk-through on how to create a database using either PHPMyAdmin or SequelPro.

Use your preferred database management tool to create a database for our app and give it the name appointments.

Once the database setup is complete, open the terminal and navigate to the folder where you’ll want to set up your project. Then run the following command to create a new Symfony project (a folder for the project files will be created automatically):

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

In this case, Appointments will be the name of the project folder, and it will be created automatically by composer.

During the installation, after the project files have been downloaded, composer will prompt you to provide some configuration parameters for the app. The important ones are:

- the database_name (which in this case is appointments).

- the database_user and database_password for accessing the database.

You can leave the default values for the rest of the parameters. In case you need to change the values later, you can do this in the project's app/config/parameters.yml file.

The app should now be ready for use. To test if everything is set up correctly, in the terminal, navigate to the folder of your app (the Appointments folder) and run:

$ php bin/console server:run

If everything is working OK, the server will run successfully and provide you with a url (usually http://localhost:8000) that you can use to view your app. Open up your browser, navigate to the provided url, and you should see something similar to:

For a refresher on Symfony, check out this up and running tutorial.

Models

Now that we've got our app installed and setup, we'll start by creating the entity models we'll need to capture the users and appointments information.

We'll start by creating an Entity folder inside of the src/AppBundle where we'll put our Entity files. We’ll then create a new file, User.php, and add the following code:

// 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;
 }
}

This is our User entity model, that uses Doctrine ORM to map out our User table. In this case, we only store the name, email, and phone number of the user. Depending on the needs of your application, you could end up with more information about the user, such as address, date of birth, gender, etc.

Next, we’ll add the Appointment.php file to our Entity folder for our Appointment entity model. Here we are only interested in the date/time of the appointment, as well as who booked the appointment. As such, the file will look like:

// 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;
 }
}

We've added a reference to the user in the appointment table to make lookups easier for us later on.

Next, open a new tab/window in your terminal (since bin/console server:run command will take control of terminal tab/window), and run the following command to create the tables in your database:

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

Seed Data

The next thing we'll do is add some sample data to work with. We could add a controller with the necessary actions and views to perform CRUD operations of users and appointments, but that is not necessary for our use case. Instead, we can either simply add the data manually in the database, or use Doctrine Fixtures Bundle to load sample data in the database.

Run the following command in the terminal to install the bundle:

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

Then open AppKernel.php inside the app folder (in the root of the project) and enable the DoctrineFixtures bundle. The DoctrineFixtures bundle is ONLY supposed to be used for testing purposes. This means it should NOT be enabled in a production environment. As such, we shall be enabling it only for development or test environments. This is done by appending a new instance of the bundle to the $bundles array inside of the registerBundlers() function [Line 9], after confirming that we are either in the dev or test environment [Line 22 for a clean install].

// app/AppKernel.php

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

Next, we’ll add a DataFixtures folder to src/AppBundle and then add an AppFixtures class to the folder that will define the sample data to be added.

/ 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) {
   // our information array
   $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'
     ]
   ];

   // loop through our array and create our users + their appointments
   foreach ($usersInfo as $info) {
   // create user
   $user = new <em>User</em>();
   $user->setName($info['name']);
   $user->setEmail($info['email']);
   $user->setPhoneNumber($info['phone']);
   $manager->persist($user);

   // get a date object from out date string
   $date = date_create_from_format('d-m-Y H:i', $info['date']);

   // create appointment for the user
   $appointment = new <em>Appointment</em>();
   $appointment->setDate($date);
   $appointment->setUser($user);
   $manager->persist($appointment);
   }

   $manager->flush();
 }
}

NOTE: Be sure to change the date to the current date for easier testing. You can also add more users or appointments if you wish.

To load our fixtures into the database, execute the following command in the terminal and select y (yes) when prompted:

$ php bin/console doctrine:fixtures:load

Install Twilio

Now that we have our entity models set up and our database seeded with some sample data, let's see how we can send the SMS reminders. For this, we'll use Twilio's SDK. You will need a Twilio account and phone number, as well as your account's SID and auth token.

Go here to create a Twilio account and phone number. Once you're done with the account creation, run the following command to install the SDK:

$ composer require twilio/sdk

After the SDK has been installed, we'll need to make it accessible in our app. We can achieve this by exposing it as a service. But before that, open parameters.yml and add the following configuration values:

# app/config/parameters.yml
parameters:
   # ...
   twilio_sid: your_acc_sid_here
   twilio_token: your_auth_token_here
   twilio_number: 'your_phone_number_here'

Then, we’ll add a new service in services.yml to expose the Twilio SDK, as follows:

# app/config/services.yml
services:
   # ...
   twilio.client:
       class: Twilio\Rest\Client
       arguments: ['%twilio_sid%', '%twilio_token%']
  
   # Add an alias for the twilio.client service
   Twilio\Rest\Client: '@twilio.client'

NOTE: Be sure to also include the line that adds an alias for the twilio.client service

With this, we can now inject the service anywhere into our application. Now we can look at the SMS command.

Create SMS Command

We want the reminder SMS messages to be sent automatically at set times using crons, so we need to be able to execute the sending of SMS messages from the command line. For that, we'll need a command.

Add a Command folder to our AppBundle and create the SmsCommand class with the following code:

// 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');
  
   // For our app, we'll be sending reminders to everyone who has an appointment on this current day, shortly after midnight.
   // As such, the start and end times we'll be checking for will be 12:00am (00:00h) and 11:59pm (23:59h).
   $start = new <em>\DateTime</em>();
   $start->setTime(00, 00);
   $end = clone $start;
   $end->modify('+1 days');
   $end->setTime(23, 59);

   // get appointments scheduled for today
   $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(), // Send text to this number
         array(
           'from' => $sender, // My Twilio phone number
           'body' => 'Hello from Awesome Massages. A reminder that your massage appointment is for today at ' . $appoint->getDate()->format('H:i') . '. Call ' . $sender . ' for any questions.'
         )
       );

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

The code above checks our appointment table (in the database) for any appointments scheduled for today, then loops through the appointments and sends an SMS (using the Twilio SDK) to each user who has an appointment today. At the end, it prints out a confirmation message to us on the console that the reminders were sent successfully.

Recommendation: For testing purposes, I’d recommend you change the phone numbers of the users (in the user table) to either your own number, or that of someone nearby, so you can actually confirm that the sent SMS is delivered correctly.

We can now test out our command and see if it works as intended. In the terminal, execute:

$ php bin/console myapp:sms

If there are any appointments scheduled for today, you should see a printout on the console of the number of SMS reminders that will be sent, and a success message once they are sent. If there aren't any appointments, you should likewise see a message informing you of the same.

Create a Cronjob

Now that we have our command configured correctly and we've verified that it's working, we'll finish by scheduling our command to run at a specific time daily.

NOTE: You could also set the command to run at intervals less than a day, depending on your requirements. You'll however need to update the $start and $end times of the appointments to ensure SMS messages are sent accordingly.

To open up the crontab, run the following in the terminal:

$ crontab -e

Then we'll set the interval (once a day at midnight) for our cronjob, as well as the task to be executed.

NOTE: be sure to set the correct absolute path of bin/console.

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

Congratulations, you did it! You can now send appointment reminders in Symfony using Twilio SMS. You can find a final version of this app on github.