An Intro to Adapter Design Pattern in PHP

In a nutshell, the Adapter design pattern is like a translation layer between different components and it adjusts the interface of one class to match that of another. To make an example from the real-world, imagine that you want to connect a MacBook to a projector that has a VGA plug but the Mac does not. In situations like this, you need an adapter that transforms the connection from one format to the other. In this hands-on tutorial, you will see how to use the Adapter pattern in PHP the right way. 

According to Wikipedia, Adapter (aka Wrapper) is a design pattern that:

Allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.

In other words, this design pattern has to do with converting the interface of a class into another interface that clients expect and it allows classes to work together that couldn't otherwise because of incompatible interfaces.

To show what kind of problems this design pattern solve, first we create a class without using this design pattern and see what kind of problems we will face:

<?php
class Twitter
{
    public function sendMessage(string $msg): string
    {
        return $msg;
    }
}

$twitterObj = new Twitter();
echo $twitterObj->sendMessage('A new tweet');

We have created a class called Twitter which has just a method to send messages. Imagine in the weeks ahead, the developer decides to change the method name from sendMessage() to something else such as sendTweet() which in this case all users of this class need to make some (or a lot of) changes in their codebases and here is the place where the Adapter design pattern comes into play. To tackle the above problem, we can refactor it into something as follows:

<?php
class Twitter
{
    public function sendMessage(string $msg): string
    {
        return $msg;
    }
}

interface SocialMediaInterface
{
    public function send($message);
}

class TwitterAdapter implements SocialMediaInterface
{
    private $twitterInstance;

    public function __construct(Twitter $twitter)
    {
        $this->twitterInstance = $twitter;
    }

    public function send($message)
    {
        return $this->twitterInstance->sendMessage($message);
    }
}

$twitterObj = new Twitter();
$twitterAdapterObj = new TwitterAdapter($twitterObj);
echo $twitterAdapterObj->send('A new tweet');

I have kept the Twitter class intact but added some code that uses that class. First, I created an interface called SocialMediaInterface which defines which methods the next class called TwitterAdapter needs to have and we have just added a send() method in this interface for simplicity. Then I created a class called TwitterAdapter which implements SocialMediaInterface and in this class, there is a private property which is going to hold an instance of Twitter object then in send() method I called the sendMessage() of Twitter class. As you can see, in line 31 I've created an object out of TwitterAdapter class which needs an object of Twitter class as its argument and finally called the send() method.

Now let's change the name of sendMessage() method in Twitter class to something else:

<?php
class Twitter
{
    public function sendTweet(string $msg): string
    {
        return $msg;
    }
}

interface SocialMediaInterface
{
    public function send($message);
}

class TwitterAdapter implements SocialMediaInterface
{
    private $twitterInstance;

    public function __construct(Twitter $twitter)
    {
        $this->twitterInstance = $twitter;
    }

    public function send($message)
    {
        return $this->twitterInstance->sendTweet($message);
    }
}

$twitterObj = new Twitter();
$twitterAdapterObj = new TwitterAdapter($twitterObj);
echo $twitterAdapterObj->send('A new tweet');

In situations like this, the Adapter design pattern comes to our rescue so we just need to change line 26 and the client (or our API user) will have no idea what has happened behind the scene and s/he doesn't have to make any change in their source code.

A More Complicated Example on Adapter Pattern

The following example is derived from DesignPatternsPHP repository in GitHub which shows how the Adapter pattern works in a more real-world example:

<?php
interface BookInterface
{
    public function turnPage();
    public function open();
    public function getPage();
}

class Book implements BookInterface
{
    private $page;

    public function open()
    {
        $this->page = 1;
    }

    public function turnPage()
    {
        $this->page++;
    }

    public function getPage()
    {
        return $this->page;
    }
}

interface EBookInterface
{
    public function unlock();
    public function pressNext();
    public function getPage();
}

class EBookAdapter implements BookInterface
{
    protected $eBook;

    public function __construct(EBookInterface $eBook)
    {
        $this->eBook = $eBook;
    }

    public function open()
    {
        $this->eBook->unlock();
    }

    public function turnPage()
    {
        $this->eBook->pressNext();
    }

    public function getPage()
    {
        return $this->eBook->getPage();
    }
}

class Kindle implements EBookInterface
{
    private $page = 1;

    public function pressNext()
    {
        $this->page++;
    }

    public function unlock()
    {}

    public function getPage()
    {
        return $this->page;
    }
}

$book = new Book();
$book->open();
$book->turnPage();
echo $book->getPage();

Now let's explain what the above code block does. First, we have created a BookInterface which the Book and EBookAdapter classes will implement because we are going to convert the interface of EBookAdapter class into Book interface that clients expect and it allows classes to work together. Then we have created the EBookInterface interface which an object created from EBookAdapter needs to have an argument implemented from this interface.

In fact, the EBookAdapter class makes the unlock()pressNext() and getPage() methods compatible with open()turnPage() and getPage() methods respectively. Finally, we have created a Kindle class which implements EBookInterface.

To test how this example works, we create an object from Book class and by calling the open() method, the value of $page property becomes 1 and by calling the turnPage() method it's increased by one unit and to get the current page, we can call the getPage() method which in the output we will have:

2

Now let's add the following lines to the end of the file:

$kindle = new Kindle();
$book = new EBookAdapter($kindle);
$book->open();
$book->turnPage();
echo $book->getPage();

As you can see, we have created an object from Kindle class and passed it to EBookAdapter because the argument of this class needs to be of EBookInterface type and in the output, we will have:

2

Although we are dealing with an E-book, we can easily work with open()turnPage() and getPage() methods of Book class without worrying about what happens behind the scenes.

To recap, the reason we want to use this design pattern is to protect the client code from having to change and particularly the Adapter pattern can be used when your code depends on a third party API which is prone to change frequently.

by Behzad Moradi on 2019-08-11

Login to add your comment