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, MAMP, XAMPP, 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.