PHP Design Patterns: Adapter

The Adapter design pattern is, in my opinion, one of the most important design patterns. It’s crucial to understand when applying a thought out architecture in your software system. This design pattern is not used as much as it should.

We all know that it’s easy to rely on external packages and frameworks. And then just apply their interfaces. Making them decide how we develop our code.  As a result, we become coupled to them.

It’s also important to note that a lot of packages provide these ‘example’ interfaces for you to use as an example, and not to always blindly implement them in your system.

The idea here is to give you a quick overview of how I see this design pattern and how I apply it in everyday life. I’m not going to go deep into the theoretical explanations of this design pattern here.

What does it do?

With the adapter design pattern, we create our own interface to handle a problem where we rely on an external package or framework. Thus we transform an interface that someone else created to the interface that we need. An interface to solve our specific problem.

As a result, you are also able to easily switch out these external packages. This means that we are decoupled and have a minimal amount of technical debt. But this all depends on how strictly we implement and use our adapters.

Example

Let’s imagine we need to send order information in the background to an external payment system. Let’s call this YoloPayment. They provide us with a PHP API that we need to use.

To send a payment we need to do this:

class YoloPayment
{
    public function engagePaymentRequest($apiKey, $type)
    {

    }

    public function sendPaymentNowPlease($type, $total, $street, $houseNumber, $postcode, $country)
    {

    }
}

But we don’t want to rely on this interface. If we directly use this class in our code, we are coupled with YoloPayment. Also, our existing code has to be changed because we did not know upfront that we were going to use YoloPayment.

To solve all these problems we should create a Payment interface and then an adapter to allow YoloPayment to be used according to our interface. We create an adapter.

interface PaymentInterface
{
    public function sendPayment(Price $totalPrice, Address $address): void;
}

Everywhere in our code, we use this interface to send payments to. It is according to the rules of our system. Using an Address and Price value object.

Now to be able to use YoloPayment, we just create an adapter to make the translation between their and our interface.

final class YoloPaymentAdapter implements PaymentInterface
{
    /**
     * @var string
     */
    private $apiKey;

    /**
     * @var YoloPayment
     */
    private $yoloPayment;

    public function __construct(string $apiKey, YoloPayment $yoloPayment)
    {
        $this->apiKey = $apiKey;
        $this->yoloPayment = $yoloPayment;
    }

    public function sendPayment(Price $totalPrice, Address $address): void
    {
        $this->yoloPayment->engagePaymentRequest($this->apiKey);
        $this->yoloPayment->sendPaymentNowPlease('default', $totalPrice, $address->street(), $address->houseNumber(), $address->postcode(), $address->country());
    }
}

Now, in case we switch payment providers. We only have to create a new adapter to make the required translations. And no other code has to be changed.

Conclusion

If there is only 1 thing you need to remember about the adapter design pattern. Then it is that this will decouple your code from external packages. We adapt another interface to an interface that we can safely use in our application.

As always you should understand the idea behind this design pattern and how to implement it. That does not mean to always blindly apply design patterns, just because. But use them as you see fit, in a variation that you need, to solve your problem.

Now next time you are using an external package. Think twice before blindly integrating it into your system. Think before you code! And maybe use adapters?