Send SMS Updates for Background Tasks with Symfony Messenger and Twilio SMS
Time to read: 6 minutes
Overtime, APIs and web apps that were once performant can become sluggish and unresponsive. This is often due to the natural progression of a codebase which has evolved to perform more work with an increasing amount of data. This slow down is mostly felt in areas that involve communicating with third-party APIs, report generating, or crunching a lot of data.
Ultimately, this extra time affects how long users have to wait to get a response to their actions.
There are a variety of ways one could reduce these performance issues. For this tutorial we're going to be focusing on processing heavy workloads in the background so that we can respond to users quicker. Specifically, we will learn how to queue background, resource-intensive tasks using Symfony and Symfony's Messenger Component whilst keeping users notified of the work status via Twilio SMS.
Prerequisites
In order to complete this tutorial you will need the following:
- Basic knowledge of Symfony
- Symfony and Composer installed locally
- Redis setup on your machine.
- Sign up for a free Twilio account.
Creating a New Symfony Application
To begin we will create a new Symfony project. We'll be using Composer to generate the project for us, so if you don't already have it installed, you can follow the installation instructions from the Composer documentation. Once you have Composer installed run the following command in a terminal:
We will also install the Twilio PHP SDK and Symfony Messenger Component as we'll need them both later on. Run the following command in a terminal:
We will be using secret API keys so we need to ensure they aren't committed to the version control system. By default, Symfony provides a .gitignore
file with .env.local
as one of the entries. This file is where we will store our secret API keys. Run the following command in a terminal to create this file:
Setting Up the Twilio SDK
Before we can start using the Twilio SDK, we first need to fetch our Twilio credentials. If you haven't already got an account you can create a new account here. Twilio will also provide you with free credits to test the API. Once you have logged in, navigate to the dashboard and you will see your Account SID and Auth Token.
You will also need an active phone number with SMS capabilities. If you don't already have a phone number you can find one here.
We now have all of the data required to communicate with Twilio’s API. Copy your Account SID, Auth Token, and phone number to the .env.local
file we created earlier as follows:
Building the Form
To simulate a heavy workload we're going to create a simple form that when submitted randomly takes between 10 and 15 seconds to respond. To get started create a new file named HeavyWorkloadController.php
in the src/Controller
directory and insert the following code:
You will need to create the new
and show
templates as outlined above. Add a new file named new.html.twig
to the templates
directory and insert the following code:
Additionally, create a file named show.html.twig
and insert the following code:
Now navigate to your local site by running symfony server:start
in your terminal. You'll see a single input with a submit button. Type in your name and press submit and you'll see that it takes a considerable amount of time to respond. This may be reminiscent of other web apps you have used or have built.
Background Resource Intensive Tasks
We're now at a point where we have highlighted a part of our application that we can offload to the background, rather than making the user wait for a response. In order to fix this, we can use Symfony's Messenger Component and Redis to add the work to a queue to be processed in the background.
The Symfony Messenger component consists of two main elements: the message class and the message handler class. The message class is a container for the data that we want to pass to our background worker. The data that is stored in this class must be serializable so that the data can be transferred correctly. The message handler class is responsible for consuming the message and performing a task.
First, let's create a message class. Create a new directory named Message
in the src
directory. Inside the Message
directory, create a new file named GenerateReport.php
. Replace the contents of the file with the following code:
Now we need to create the message handler. Create a new directory named MessageHandler
in the src
directory. Inside the MessageHandler
directory, create a new file named GenerateReportHandler.php
. Replace the contents of the file with the following code:
The __invoke
method is a Magic Method. This function is called when an instance of the class is called as a function.
You'll notice that the inside of the __invoke
function body is the same sleep
call we used in the HeavyWorkloadController
new action. We're now going to update the controller action by dispatching a GenerateReport
message. To do this, open the HeavyWorkloadController
file and replace with existing sleep
like so:
If you submit the form again you'll notice that it still takes a long time for the request to send a response. This is because, by default, Symfony handles the message as soon as it is dispatched. To dispatch the message so that it is handled asynchronously we need to configure a transport. The Messenger component supports many transports out of the box, but in our case, we're going to use Redis.
To get started we need to set a MESSENGER_TRANSPORT_DSN
environment variable. This variable should point to a Redis instance and database. Open the .env.local
file we created earlier and append the following line:
We now need to configure the transport in our yaml config. Add the following to the config/packages/messenger.yaml
file:
The configuration above first creates a transport named async
using the DSN provided via the environment variables. Additionally, we inform Symfony that we want all App\Message\GenerateReport
messages to be sent through the async
transport rather than being handled immediately.
It's important to note that any newly dispatched messages are added to a queue, but currently are not being processed. To consume and process the messages, open a terminal and run the following command:
NOTE: If you change contents of the src/MessageHandler/GenerateReportHandler.php
class you'll need to restart the consumer.
Now navigate back to your local site and resubmit the form. This time you'll notice that the response is instant! That is because we've moved the heavy work out of the request-response cycle into a queue so that it can be processed in the background. If you look at your terminal that is running the messenger:consume
command you should see "Report generated!"
Keeping Users Informed of Their Report Progress
We have now moved the resource-intensive task of generating a report to the background so that our application is fast once again. However, we have lost the ability to inform the user that their report has been generated. This is where Twilio comes in. Using Twilio's SMS functionality we can send notifications to the user informing them that the report generation has started, and also when the report has been generated.
First, we need to update our form to capture the phone number the user wants us to send the SMS messages to. Open the HeavyWorkloadController.php
file and add a new field to the form like so:
Thanks to Symfony's form
twig function that we used in the templates/new.html.twig
file, this phone number field will be automatically rendered for us.
Secondly, we need to pass the provided phone number to the GenerateReportHandler
via the GenerateReport
message. To accomplish this, we need to update the GenerateReport
message class to also hold the phone number. Open the GenerateReport.php
file and add a phone number field like so:
We can then pass the phone number to the GenerateReport
object that we created in the HeavyWorkloadController
like so:
Our GenerateReportHandler
now has the phone number that the user wants us to send the notifications to. However, we first need to configure a Twilio client so that we can send the SMS messages.
To do this we're going to configure a Twilio API client with the Account SID and Auth Token we added to our .env.local
file earlier. We're also going to inject the TWILIO_NUMBER
environment variable as a parameter to the GenerateReportHandler
class via Symfony's named arguments. Add the following service definition to your config/services.yaml
file:
With our Twilio client configured, we can now update our GenerateReportHandler
to use this client to send the SMS messages to the provided phone number. Open the GenerateReportHandler
class and replace it with the following code:
Testing
Restart your consumer so that it reflects the new code changes. Navigate back to your local site and resubmit the form with your phone number.
You should receive the initial message almost immediately, followed by the completed report message 10 to 15 seconds later.
Conclusion
Congratulations! You have successfully used Symfony, Symfony’s Messenger Component, and Redis to implement a queue allowing you to background an expensive task. You also used Twilio’s Programmable SMS to keep your users updated with their report generation progress.
If you want to extend this tutorial, I recommend trying different transport types like AMPQ or Doctrine or use SendGrid to notify your users.
Alex Dunne is a Software Engineer based in Birmingham, UK. He loves experimenting with new technology to further develop a well-rounded skill set.
- Twitter: @i_dunne_that
- Website: http://alexdunne.net/
- GitHub: https://github.com/alexdunne
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.