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:
- S - Single Responsibility Principle
- O - Open/Closed Principle
- L - Liskov Substitution Principle
- I - Interface Segregation Principle
- 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:
- PHP 7 development environment
- Global installation of Composer
- Twilio Account
- Postman (Or your preferred API development environment)
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