How to Send an SMS with Mezzio PHP Framework
Time to read: 10 minutes
Whether we’re voting for our favorite candidate on shows such as X Factor, or receiving two-factor authentication codes to log in to services like MailChimp and GitLab, SMS are virtually ubiquitous in modern life. Not only do they make communication in life and work much easier, it also doesn’t take a lot of code to send them either.
In this tutorial, you’ll learn how to create a simplistic API that can send an SMS using PHP’s Mezzio framework and Twilio’s PHP SDK.
Once completed, you will be able to send a POST request to the API’s default endpoint, supplying the phone number to send the message to, and the message to send. If the message was successfully sent, then a JSON response will be returned showing a number of details about the sent SMS. If the SMS was not able to be sent, then an appropriate JSON response will be returned, which will show what went wrong.
Overall, this tutorial will provide you with a basic understanding of creating endpoints in Mezzio and sending SMS with Twilio.
Prerequisites
In order to complete this tutorial, you will need the following:
- PHP 7.4
- Composer globally installed
- A Twilio account and phone number
- twilio-cli
- curl
- jq
- libsecret
- Node.js 10.12.0 or higher
NOTE: Twilio’s PHP SMS Quickstart shows how to install libsecret, Node.js, and twilio-cli.
Create a Mezzio Application
If you’re not familiar with the name, Mezzio is the latest iteration of Zend Framework and Zend Expressive. It was renamed earlier in the year so that it could be rehomed at the Linux Foundation. Mezzio is suited to create applications of any size. It makes growing from a small proof-of-concept to a large, enterprise-grade application possible — without requiring large architectural changes.
The first thing that you’ll need to do is to create a new Mezzio application. To save some time, use Composer’s create-project
command, as in the example below, to bootstrap a basic application.
This command will launch an installation wizard to help you create the right type of application, which asks five questions:
- What type of installation would you like? For this question, select 3, which is Mezzio’s recommended choice
- Which container do you want to use for dependency injection? For this question, accept the default option, which is laminas-servicemanager
- Which router do you want to use? For this question, accept the default option, which is FastRoute
- Which template engine do you want to use? For this question, choose n. We don’t need to use a template engine, as the application will, at most, return JSON responses.
- Which error handler do you want to use during development? For this question, accept the default option, which is Whoops. This package provides excellent support when attempting to track down application errors.
After you answer the fifth question, the wizard will bootstrap the application. Installation should be finished in under 60 seconds, depending on the speed of your network connection.
Add the Additional Composer Dependencies
Next, you need to install a few additional dependencies. These dependencies are required later in this tutorial to validate the phone number which the SMS will be sent to as well as the SMS body. While not strictly necessary, it’s responsible for a developer to write code that is properly validated.
To install them, change into the project directory with, cd sms-sender-api
, and run the following command.
NOTE: When prompted to inject Laminas\Validator\ConfigProvider into config/config.php, accept the default option (1) and accept the default option (Y) to remember this option for other packages of the same type.
Add your Twilio credentials
With the application bootstrapped and the additional dependencies installed, you need to supply the application with Twilio credentials available. Navigate to the Twilio Console and locate the ACCOUNT SID and AUTH TOKEN inside of the Project Info dashboard as seen below:
Figure 1. Retrieve the account SID and authentication token from your Twilio account
To retrieve the phone number, run twilio phone-numbers:list
in your command line and choose the applicable number from the list, copying the value from the Phone Number column. If you haven't done so already you can search and buy a Twilio Phone Number from the console.
Next, create a new file named twilio.local.php inside of the config/autoload folder. Copy the following PHP snippet into the file and update it with your Twilio credentials and phone number.
NOTE: There are many ways to store secure credentials in Mezzio. The reason why I’m suggesting this approach is that by default, files ending in *.local.php are explicitly ignored by git, so they cannot be stored under version control — unless they are explicitly added using either the -f or --force options. We could store the credentials in a .env file, but to retrieve them would require additional third-party libraries, such as dotenv. What’s more, this approach is more succinct.
Create and Register a Twilio Service
Now that the credentials and phone number are ready to use, write the code that will use those credentials to send an SMS. Create a new file called TwilioService.php inside of the src/App/src/Service/ folder. Add the following code inside of that file:
The TwilioService
constructor receives an array called $configuration
. This array will store the Twilio configuration which was created earlier. The array initializes a new Twilio\Rest\Client
object which will send the SMS.
Next comes the sendSMS
function. This function takes two arguments:
$sendTo
, which is the phone number to send the SMS to$body
, which is the SMS message body
This function calls the create
method, passing in the phone number to send the message to, and the message body details.
NOTE: The method returns a MessageInterface
object. This is because the application will return some information about the SMS if it was successfully sent, which will be retrieved from this object.
With the TwilioService object created, you need to create a class to instantiate it. In src/App/src/Service/, create a new file named TwilioServiceFactory.php and add the following code:
The TwilioServiceFactory
class effectively implements the factory pattern. This is a common paradigm in Mezzio for instantiating classes which have constructor dependencies. That’s why the __invoke
magic method receives a ContainerInterface
object so that it can retrieve the Twilio configuration details from the application’s dependency injection (DI) container.
The function does a bit of sanity checking to see if the DI container has a service called config
, which contains the application’s global configuration. If it does, it is used to initialize a new variable called $config
. However, if the Twilio configuration isn’t available within the global configuration, an InvalidArgumentException
will be thrown. Assuming that config
was available, it is then used to instantiate a TwilioService
object, which is then returned.
With the TwilioService
and accompanying TwilioServiceFactory
classes defined, you then need to register the service in the DI container. To do that, update the getDependencies
method in src/App/src/ConfigProvider.php to match the code below:
The getDependencies()
function registers a service called TwilioService
in the DI container. When that service is retrieved from the container, the result of calling TwilioServiceFactory
’s __invoke
magic method is returned.
NOTE: In Mezzio, it’s almost trivial to register services with the DI container and retrieve those services later. Doing so discourages direct dependency instantiation within using classes, making applications more maintainable.
Create an Object to Store the SMS Data
Create a new file under src/App/src/ValueObjects called SMSData.php, and add the code in the example below to it. This class will perform two roles in the application.
Firstly, it will provide us with an object-oriented way of managing the phone number to send the SMS to, along with the message to send. Secondly, it provides a compact way of instantiating a form that we can use to validate the POST data used to make a request to send an SMS.
This class contains two public member variables; sendTo
which will store the phone number to send the SMS to, and body, which will store the SMS message. For the sake of simplicity, this example uses Doctrine annotations, which \Laminas\Form\Annotation\AnnotationBuilder
will later use to instantiate a form.
For sendTo
, a text input field will be generated named sendTo
. Input retrieved from it will be trimmed before it is returned. It will be also validated using the following regular expression: ^((\+1|001)?[ \-]?)(([\d]{3}[ \-]?)?|(\([\d]{3}\)[ \-]?))([\d]{3}[ \-]?[\d]{4}){1}$
.
This regex only allows valid US phone numbers, such as the following:
- 754-3010
- 754 3010
- 7543010
- 541-754-3010
- 5417543010
- (541) 754-3010
- (541)-754-3010
- +1-541-754-3010
- +1 541 754 3010
- +15417543010
- +1-541-754-3010
- +1-541-754-3010
- 001 541 754 3010
- 0015417543010
- 001-541-754-3010
body
will be rendered as a textarea field named body
. As with sendTo
, input retrieved from it will be trimmed before it is returned. Its content will be validated to ensure that it is no more than 500 characters long. Both of the fields have custom error messages set, as the default ones are not as human-readable as preferred for real-world application.
If you’d like to experiment with the regular expression, here’s a link to it on Regex101.com.
Refactor the Route’s Handler Class
You’ve retrieved and stored your Twilio details in the application’s global configuration. You’ve created all of the supporting classes and registered them where necessary with the DI container. Now it’s time to refactor the default route’s handler to use that configuration and functionality to send an SMS.
Refactor the class constructor
The first thing you need to do is to refactor src/App/src/Handler/HomePageHandler.php’s constructor to:
- Replace the first argument with a new
\App\Service\TwilioService
object called$twilioService
, to initialize a new class variable also called$twilioService
- Instantiate a second class variable called
$form
which will be a\Laminas\Form\FormInterface
object. To do this, useAnnotationBuilder
’screateForm
method, passing to it\App\ValueObjects\SMSData::class
as the name of the class to use
When finished, the constructor should look like the example below:
Refactor the handle method
Remove all of the code from the body of the handle
method. It’s all boilerplate code that you don’t need.
Then, add an if condition to check if the result of calling the $request
object’s getMethod
function matches POST
. If it doesn’t, then return a new \Laminas\Diactoros\Response\EmptyResponse
object, passing the integer 405 to its constructor.
Doing this will return an empty response body along with an HTTP status code of 405 (Method Not Allowed) if a request is made using an HTTP method other than POST.
Pass the result of calling $request
’s getParsedBody
method to $form
’s setData
method. This will attempt to initialize $form
with any POST variables retrieved from the request.
Once that’s done, check if the supplied form data is valid by calling $form
’s isValid
method. This will run the validators that you set in SMSData
’s Annotation docblocks.
If the form is valid, then it’s finally time to send the SMS. To do that, call the sendSMS
method from twilioService
by passing it the value of $form
’s sendTo
and body
properties. Use the method’s response to initialize a new variable, called $response
.
NOTE: We’re not going to handle any TwilioException
’s which might be thrown when calling sendSMS
.
Return a new \Laminas\Diactoros\Response\JsonResponse
object, passing it an associative array containing the following properties from $response
(from
, to
, body
, and status
). You don’t need to specify the second parameter, which is the response code, as it will return an HTTP 200 OK status code by default.
When finished, the if block should look like the following code:
If isValid
returns false, then you should be professional (and polite) and let your user know what went wrong. This doesn’t need to be exhaustive.
In an else
block, return a new \Laminas\Diactoros\Response\JsonResponse
object.
This time, however, pass it an array with just two keys: status
, which is set to unsuccessful
, and reason
which is set to the result of calling $form
’s getMessages
method.
This method returns an associative array containing a key for each form element that failed validation, where the values for those keys is an array, containing all the error messages showing where they failed to validate. In addition to the array, pass it a second parameter set to 400
indicating that it was an HTTP Bad Request.
Here’s what the code should look like.
Refactor how the route handler class is instantiated
As we’ve changed src/App/src/Handler/HomePageHandler.php
’s constructor, replacing the first argument with a TwilioService
object, we now need to refactor how the class is instantiated in src/App/src/Handler/HomePageHandlerFactory.php
.
It’s a standard convention in Mezzio to use factory classes to instantiate classes that have constructor dependencies. So in the class’ __invoke
magic method, after the initialization of $template
, write an if condition using the $container
’s has
method. This will check if a TwilioService
is available. If it’s not available, throw a new \\Laminas\ServiceManager\Exception\ServiceNotFoundException
, with the message “Twilio service not found.”.
If the service is available, use $container
’s get
method to retrieve it, initializing a new variable, called $twilioService
. Then, replace the original first argument to HomePageHandler
’s constructor with $twilioService
.
When finished, the __invoke
magic method definition should look like the following example:
Refactor the Route Configuration
Now that the handle
method has been refactored, you need to make one final change, which is to allow the route to receive POST requests. To do that, change the first method in config/routes.php from get
to post
, as in the example below.
Testing
The application is now finished. At this point, you can compare your code against the code I’ve based this article on which is available on GitLab.
Now it’s time to run the application so that we can test it. To do that, use the built-in Composer script serve
that comes with Mezzio, as in the example below.
This will launch PHP’s internal web server, setting it to listen on localhost, on port 8080
, and use the public
directory as the project’s directory root. The ampersand &
will put the command into the background so that you can run a curl request in the same terminal session.
You should see output similar to the following in your terminal:
Now that the application’s running let’s use it to send an SMS. To do that, send a POST request in Curl, as in the example below, changing the sendTo
and body
values as you prefer. I’ve used curl’s --silent
switch to suppress progress meters and error messages, as we’re only interested in the JSON response.
NOTE: To make the JSON response easier to read, the JSON response is piped to jq which by default, neatly formats it and adds coloration.
Once the message is sent successfully, you should see JSON output similar to the example below in your terminal.
In addition, you should receive an SMS on your phone, with the message that you sent, which you can see an example of below.
Figure 2. View the SMS that you sent from the application
Now, let’s do a small test that invalid phone numbers cannot be used to send an SMS.
This time, change the number to an invalid phone number and then send the same message as before, as seen in the Curl example below:
You should see an error response similar to the example below. You can see that the status is now marked unsuccessful
, along with an error message telling you that the phone number was invalid, followed by the acceptable phone number formats.
Conclusion
Well, congratulations! You have just created a Mezzio application that can send an SMS using Twilio. If you’ve enjoyed the process, then I strongly recommend extending the application so that more details are displayed in the JSON response, as well as refactoring the code to respond to SMS.
Matthew Setter is a consulting PHP developer and technical writer. He can be reached via:
- Email: matthew@matthewsetter.com
- Twitter: @settermjd
- GitHub:https://github.com/settermjd
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.