Implementing the Open-Closed Principle in Laravel PHP with Twilio SMS

August 27, 2019
Written by
Charles Oduk
Contributor
Opinions expressed by Twilio contributors are their own

Implementing the Open-Closed Principle in Laravel with Twilio SMS .png

Introduction

The SOLID design principles by Robert C. Martin, popularly known as Uncle Bob, are a set of principles that help a developer write clean, reusable, and maintainable code. The five principles that make up the acronym SOLID are:

  1. S - Single Responsibility Principle
  2. O - Open/Closed Principle
  3. L - Liskov Substitution Principle
  4. I - Interface Segregation Principle
  5. D - Dependency Inversion Principle

In this tutorial, we are going to look at the Open/Closed Principle. It states that entities should be open for extension but closed for modification. Essentially, it should be simple to change the behavior of a particular entity without modifying the original source code. New developers may find this principle confusing at first. It is the goal of this tutorial to help developers understand Object Oriented Programming by demonstrating the use of the Twilio SDK for PHP in a Laravel application.

NOTE: We are focusing on the Open/Closed principle, but our code will also adhere to the other SOLID principles.

Tutorial Requirements

This tutorial will require the following tools:

Create a new Laravel Project

To create a new project, run the following command wherever your projects are stored:

$ composer create-project --prefer-dist laravel/laravel open-closed-principle "5.7.*"

Change directories into open-closed-principle and install the Twilio SDK for PHP by running the command:

$ composer require twilio/sdk

NOTE: The Twilio SDK provides an easy-to-use wrapper for interacting with the Twilio API.

Get your Twilio Credentials

You will need a Twilio account to test sending SMS through your application. If you don’t have one, navigate here to sign up.

Once your account has been created, the dashboard will provide you with the credentials needed to authenticate your requests via the SDK. Open the .env file in your preferred IDE and add the following credentials:

TWILIO_SID="INSERT YOUR TWILIO SID HERE"
TWILIO_TOKEN="INSERT YOUR TWILIO TOKEN HERE"
TWILIO_NUMBER="INSERT YOUR TWILIO NUMBER IN E.164 FORMAT"

NOTE: The .env file is found in the root directory.

Create an SMS Controller

In the directory app/Http/Controllers create a new file called SmsController.php and paste the following code in it.

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;

use Twilio\Rest\Client as TwilioClient;

class SmsController extends Controller
{
 /**
 * Create a new instance
 *
 * @return void
 */
  public function __construct()
  {
      $twilioAccountSid   = getenv("TWILIO_SID");
      $twilioAuthToken    = getenv("TWILIO_TOKEN");

      $this->twilioClient = new TwilioClient($twilioAccountSid, $twilioAuthToken);
  }
   public function sendSms(Request $request)
   {
      $myTwilioNumber = getenv("TWILIO_NUMBER");

       $message = $this->twilioClient->messages->create(
           $request->input('to'),
           array(
               "from" => $myTwilioNumber,
               "body" => $request->input('text_message')
           )
       );

      return response()->json(['message_id' => $message->sid]);

   }
}

In order to access this controller via HTTP, let’s add a route in the routes/web.php directory:

Route::post('send-sms', 'SmsController@sendSms');

The next step is to start our server so that we can prepare for testing our controller. Run this command in your terminal:

$ php -S localhost:3000 -t public

We’re just about ready to test our controller. In the app/Middleware/VerifyCsrfToken.php directory, exclude this URI http://localhost:3000/send-sms. Your file should look like this:

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
   /**
    * Indicates whether the XSRF-TOKEN cookie should be set on the response.
    *
    * @var bool
    */
   protected $addHttpCookie = true;

   /**
    * The URIs that should be excluded from CSRF verification.
    *
    * @var array
    */
   protected $except = [
       '/send-sms'
   ];
}

Let us now test it out. In Postman, make a POST request to the address ‘http://localhost:3000/send-sms’. The body should have this data:

{
    "to": "YOUR VERIFIED NUMBER",
    "text_message": "We can't wait to see what you build"
}

Great! We have successfully sent a Twilio SMS.

The Problem

Although we have successfully sent an SMS, there are certain problems within our controller. It is tightly coupled with the Twilio SDK. The controller shouldn’t be concerned with the messaging service we are using. Its primary function is to send an SMS (Single Responsibility). In the event that we want another SMS provider, we would have to modify this class and that goes against the open/closed principle (entities should be open for extension but closed for modification).

The Solution

The solution is to separate extensible behavior behind an interface and flip the dependencies. The extensible behavior in our case is sending a text message.

Let’s start by creating a directory in the app folder called Sms. In it, we will add an SmsInterface.php file:

<?php

namespace App\Sms;

interface SmsInterface
{
 public function sendSms($to, $from, $message);
}

An interface is basically a contract that defines what methods all classes must implement. It defines what a class should do, not how it should do it. In our case, we have a very simple interface. All Sms classes should have a method called sendSms that takes three arguments (to, from, and message).

In the same directory let's create a file called TwilioSms.php and add the following code to it:

<?php

namespace App\Sms;
use Twilio\Rest\Client;

class TwilioSms implements SmsInterface
{
   protected $client;

   public function __construct($sid, $authToken)
   {
     $this->client = new Client($sid, $authToken);
   }

   public function sendSms($to, $from , $message)
   {
       $message = $this->client->messages->create($to, [
           'from' => $from,
           'body' => $message,
       ]);
       return $message->sid;
   }
}

The TwilioSms class implements the SmsInterface. This means it has to adhere to the contract declared in the interface.

Configure and Register the Twilio service

In config/services.php, let’s configure the credentials by appending the following piece of code in the array:

  'twilio' => [
       'account_sid' => env('TWILIO_SID'),
       'auth_token' => env('TWILIO_TOKEN'),
   ],

In app/Providers/AppServiceProvider.php, register the Twilio service by copying this code in the register method:

       $this->app->bind(TwilioSms::class, function () {

           $config = config('services.twilio');

           return new TwilioSms($config['account_sid'], $config['auth_token']);

       });

       $this->app->bind(SmsInterface::class, TwilioSms::class);

Lastly, include the namespaces for SmsInterface and TwilioSms outside of the class declaration.

use App\Sms\SmsInterface;
use App\Sms\TwilioSms;

class AppServiceProvider extends ServiceProvider;

Modify the Sms Controller

Having made those changes, we need to modify our Sms Controller. Copy the following code into SmsController.php:

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Sms\SmsInterface;

class SmsController extends Controller
{
 /**
 * Create a new instance
 *
 * @return void
 */
 public function __construct(SmsInterface $smsProvider)
 {
     $this->smsProvider = $smsProvider;
 }

 public function sendSms(Request $request)
 {
   $messageId = $this->smsProvider->sendSms(
       $request->input('to'),
       $request->input('from'),
       $request->input('text_message')
   );

   return response()->json(['message_id' => $messageId]);

 }
}

Now that we have implemented the SmsInterface, our controller isn’t concerned about what service we use for the SMS provider. All it knows is that any SMS provider will have the sendSms method and it will accept three arguments. We never need to modify this class but we can extend its functionality by changing the Sms Provider.

Test

If you still have your server running, use Postman to make an API call to the same URL http:localhost:3000/send-sms. It works! Our code is now cleaner.

NOTE: To start your server run php -S localhost:3000 -t public

Conclusion

Congratulations! You have just learned how to send an SMS in Laravel while following the open/closed principle. It is good practice to write code that is clean and extensible. Do you think you could go further and create a messaging controller that will send either emails or SMS texts?

I look forward to seeing what you build. You can reach me via:

Email: odukjr@gmail.com
Twitter: @charlieoduk
Github: charlieoduk