How to Reply to an SMS with PHP’s Mezzio Framework

How to Reply to an SMS with PHP’s Mezzio Framework
February 22, 2021
Written by
Reviewed by

In my previous tutorial on how to send an SMS with PHP’s Mezzio framework, we covered sending a text message in PHP. In this tutorial, we’ll expand on the application that we built so that it can also reply to an SMS when it is received.

Prerequisites

To follow along with this tutorial you should have the following:

To get started, clone version 1.0.0 of the repository that we created in the previous tutorial. Alternatively, follow the previous tutorial to create what we achieved.

Note: If you cloned the repository, make sure that you add your Twilio Account SID, Auth Token, and phone number to the .env file in the root directory of the project. You will also need to run composer install to install existing dependencies for the project.

Regardless of the approach that you choose, when you’re ready, let’s continue. As we achieved a lot in the previous article, this article is going to be comparatively short. That said, it still allows us to learn more about both Mezzio and Twilio’s SMS API.

Here’s a broad overview of how we’re going to extend our small Mezzio application. We’ll create a new endpoint that will receive an HTTP POST request from Twilio, sent after an SMS is sent to our Twilio phone number. Essentially, it’s a webhook.

After the request is received from Twilio, our application will send a response back telling Twilio to send a reply SMS to the sender. To do this, the request’s body will contain TwiML (the Twilio Markup Language).

If this is your first time hearing about TwiML, to quote the Twilio docs:

It is “an XML-based language that instructs Twilio on how to handle various events such as incoming and outgoing calls, SMS messages, and MMS messages.” Here’s an example of the response.

<Response>
    <Message>
        Thanks for sending your SMS. We'll be in touch with you shortly.
    </Message>
</Response>

Gladly, implementing that functionality won’t take too much work, yet will still be a fun journey of discovery all the same. Here are the steps that we’ll follow.

  1. Add a new method to TwilioService to generate the response’s content
  2. Create a new Handler class (and instantiate a factory class) to receive the request from Twilio and send the generated response
  3. Register a new route in the routing table so that the route can be used

After that’s done, we’ll then test that everything works by sending an SMS and seeing the response returned. If you’re keen, then let’s get started.

Update the TwilioService

The first thing we need to do is to add a new method, replyToSMS, to src/App/src/Service/TwilioService.php, which you can see above. If you remember from the previous article, TwilioService is the class that contains the core logic for interacting with Twilio’s API.

public function replyToSMS(string $replyMessage): MessagingResponse
{
    $message = new MessagingResponse();
    $message->message($replyMessage);
    return $message;
}

The method instantiates a new MessagingResponse object, sets the message to send in the response using the method’s $replyMessage parameter, and returns the newly instantiated object.

Note: make sure you add the required use statement to the top of the class: use Twilio\TwiML\MessagingResponse;

Create the SMS Reply Handler

Next, we need to create a new Handler class to receive the request and send back the response. To do that, we’ll use one of the pre-defined Composer scripts that comes with Mezzio projects: composer mezzio. This runs Mezzio’s command line tooling, akin to Laravel’s Artisan Console, saving us the time and effort of generating most of the new functionality.

Run the command below in the root directory of the project, and then I’ll explain what it does.

composer mezzio handler:create 'App\Handler\SMSReplyHandler'

The command does three things:

  1. Creates a new request handler class named SMSReplyHandler in src/App/src/Handler
  2. Creates a factory class to instantiate the request handler, named SMSReplyHandlerFactory, also in src/App/src/Handler 
  3. Registers the new class in the application’s DI (Dependency Injection) container configuration (in config/autoload/mezzio-tooling-factories.global.php).

It doesn’t register a route in the application’s routing table, but we’ll do that later on. Let’s start stepping through SMSReplyHandler.php.

 

Create the request handler

<?php

declare(strict_types=1);

namespace App\Handler;

use App\Service\TwilioService;
use Laminas\Diactoros\Response\XmlResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

It starts off with the namespace declaration and imports the required classes into that namespace.

class SMSReplyHandler implements RequestHandlerInterface
{
    const REPLY_MESSAGE = "Thanks for sending your SMS. We'll be in touch with you shortly.";
    private TwilioService $twilioService;

    public function __construct(TwilioService $twilioService)
    {
        $this->twilioService = $twilioService;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $messageResponse = $this
            ->twilioService
            ->replyToSMS(self::REPLY_MESSAGE);

        return new XmlResponse($messageResponse->__toString());
    }
}

After that, it initializes a class constant containing the message to be sent in the response and a TwilioService object, which is initialized in the class constructor.

In the handle method, as you’d expect, the core of the class’ work happens. In that method, the new replyToSMS method is called, passing in the class constant, initializing a new variable $messageResponse, with the returned Twilio\TwiML\MessagingResponse object. It then finishes up initializing and returning a new XmlResponse object with the result of calling $messageResponse’s __toString method.

XmlResponse classes, as the name implies, provide a simple way of sending XML responses. This will help us prepare our response in the format that the Twilio SMS API expects to receive. They:

  • Set the string or Stream object provided in the first constructor argument as the response’s body. This is why we called the $messageResponse’s __toString method. It’s less effort and overhead than instantiating a new Stream object.
  • Set the response code to 200, unless otherwise specified
  • Set the content-type header to application/xml; charset=utf-8

Create the request handler’s initialiser

Now that we’ve fleshed out the request handler, we have to refactor the generated factory class (src/App/src/Handler/SMSReplyHandlerFactory.php). Most of the class remains as it was, but we’re going to refactor the __invoke magic method, as you can see in the following example.

<?php

declare(strict_types=1);

namespace App\Handler;

use App\Service\TwilioService;
use Laminas\ServiceManager\Exception\ServiceNotFoundException;
use Psr\Container\ContainerInterface;
use Psr\Http\Server\RequestHandlerInterface;

class SMSReplyHandlerFactory
{
    public function __invoke(ContainerInterface $container): RequestHandlerInterface
    {
        if (!$container->has(TwilioService::class)) {
            throw new ServiceNotFoundException(
                'Twilio service not found.'
            );
        }
        $twilioService = $container->get(TwilioService::class);

        return new SMSReplyHandler($twilioService);
    }
}

Here, we’re initializing a new variable, $twilioService, by retrieving the TwilioService object from the DI container, if it’s registered in the container. If it isn’t registered, we throw a ServiceNotFoundException. We shouldn’t encounter this exception, as the service was registered in the previous tutorial. The new $twilioService is then used to initialize the request handler, before it’s returned.

Update the Routing Configuration

Now that the core functionality has been created, we need to update the routing table so that the request handler can be accessed. To do that, update config/routes.php by adding the following code to the list of available routes.

$app->post(
    '/sms/reply', 
    App\Handler\SMSReplyHandler::class, 
    'sms.reply'
);

Testing the Changes

At this point, the code’s ready to go, so let’s have some fun and see it in action! To do that, we first need to launch the application locally. Using Composer, run the following command in your terminal (or command line):

composer serve

Second, we need to start an ngrok tunnel so that Twilio can reach your development machine. To do that, run the command below, replacing <Your Twilio Phone Number> with your Twilio phone number (and updating the URI, if necessary).

Note: The Twilio CLI will need to be installed in order to complete these steps.

twilio phone-numbers:update "<Your Twilio Phone Number>" \
    --sms-url="http://localhost:8080/sms/reply"
» WARNING: Detected localhost URL.
» For convenience, we will automatically create an encrypted tunnel using the 3rd-party service https://ngrok.io
» While running, this will expose your computer to the internet.
» Please exit this command after testing.
? Do you want to proceed? Yes
twilio-cli configuration saved to "/Users/bigsetts/.twilio-cli/config.json"

If this is your first time running the command, you’ll likely see output similar to the below output to the console, because ngrok needs to be installed before it can be used.

» Installing ngrok ...
warning ngrok > request-promise-native@1.0.9: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
warning ngrok > request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
warning ngrok > request > har-validator@5.1.5: this library is no longer supported
SID Result SMS URL
PNb5375d1b4518e2ffe27c39bebe6e046e Success https://f39eba2cbbb5.ngrok.io/sms/reply
ngrok is running. Open http://127.0.0.1:4040 to view tunnel activity.
Press CTRL-C to exit.

Send an SMS and receive a reply

With those two steps completed, send an SMS to your Twilio phone number and wait a few seconds for a reply to be sent. Here’s what it looked like on my iPhone. Assuming that everything worked, you’re done.

iPhone test message sample screen

Debugging

Now, it’s always possible that something didn’t work, whether that’s because of a missing class, configuration, or some other issue. Well, if it does, then it’s handy to know how to debug the problem from Twilio’s end after you’ve done everything you can in your local development environment.

To do that, from the Twilio Console navigate to the Programmable Messaging Dashboard: (All Products & Services → Programmable Messaging).

Twilio Console Programmable Messaging Dashboard

There, you’ll see two charts: “Messages” and “Errors & Warnings”. In Messages, you can see how many SMS’ have been received by Twilio, and how many have been sent. That will give you a broad indication of where the problem might be. Errors & Warnings shows you all the issues that were encountered and allows you to drill-down deeper and investigate further.

Twilio Console Debugger Events

To do that, either click on one of the events or click "View all errors and warnings". In an event, you can see a range of properties, including the message, timestamp, issue description, and the request inspector. There, you can find all that you need to help you debug issues that you encounter.

If you encounter an HTTP 500 error, check whether development mode is enabled, by running the following command in the terminal:

compose development-status

It should echo the following to the console:

> laminas-development-mode status
Development mode is ENABLED

If it doesn’t, then run the following command to enable development mode:

compose development-enable

If development mode was disabled, then the application’s configuration was cached, so the changes that we made were not used. By enabling development mode, the cache is no longer used, so our new changes will take effect.

In conclusion

And that’s how to extend our Mezzio-based application so that it sends an SMS to people when they send an SMS to our Twilio phone number. It was a relatively short tutorial but still provided us the opportunity to learn more about Twilio’s SMS API and PHP’s Mezzio framework.

I hope that you’ve learned something and will continue exploring both the API and framework as and when you are able. If you have any questions, feel free to ask me on Twitter. I’m @settermjd.

Matthew Setter is a PHP Editor in the Twilio Voices team and — naturally — a PHP developer. He’s also the author of Mezzio Essentials. When he’s not writing PHP code, he’s editing great PHP articles here at Twilio.