PHP Design Patterns - Adapter

Fabio Hiroki - Nov 2 '21 - - Dev Community

Users are loving your application and you want to give them more joy. What could bring them more happiness than email notifications?

Someone feeling joy

Let's welcome our users!

We want our users to feel good after signing up in our application, so why not send an welcome email? You already know a reliable email service provider that has a SDK you can install using Composer, so everything seems lovely so far.

Unfortunately, when integrating the SDK you notice the classes doesn't fit very well. SignUpService only has the recipient email and the content, and ThirdPartyEmailClient expects configuration parameters.

Our hypotethical SDK has the following structure:

class ThirdPartyEmailClient
{
    public function __construct(
        private string $apiKey,
        private string $region,
    ) {
    }

    public function sendEmail(
        string $recipient,
        string $content,
    ): void {
        echo sprintf("Using apiKey %s and region %s", $this->apiKey, $this->region);
        echo sprintf("Sending email to %s and content %s", $recipient, $content);
    }
}
Enter fullscreen mode Exit fullscreen mode

Diagram of interaction between SignUpService and ThirdPartyEmailClient

Make it work

If ThirdPartyEmailClient is asking for apiKey and region, let's fullfil its wishes. We could simply do it by injecting both parameters on SignUpService, or even by hardcoding them. It wouldn't be a problem until you need to send an email after user updated his profile that is implemeted on ProfileService class.

Well, you're just figuring out how to make it work because you're a good professional and want to get things done, then you just copy and paste the hardcoded parameters from SignUpService. Good job!

Something feels wrong

Gif of someone with insomnia

That night you couldn't sleep because of that code duplication. You're also exposing the apiKey everywhere. How to solve this problem?

Make it right

Then you remember of this pattern called Adapter. It is supposed to make classes that have incompatible interfaces work together. First step is creating the interface expected by client:

interface EmailSenderAdapter
{
    public function sendEmail(string $recipient, string $content): void;
}
Enter fullscreen mode Exit fullscreen mode

Now you just need to wrap ThirdPartyEmailClient in a new class that implements the previous interface:

final class ThirdPartyEmailSenderAdapter implements EmailSenderAdapter
{
    public function __construct(private ThirdPartyEmailClient $emailClient)
    {
    }

    public function sendEmail(string $recipient, string $content): void
    {
        $this->emailClient->sendEmail($recipient, $content);
    }
}
Enter fullscreen mode Exit fullscreen mode

SignUpService is happy now because it has a friend that understands its language.

Diagram after the adapter implementation

And now to make it even better, you decide to add a unit test!

Testing

public function testThirdPartyEmailSenderAdapterAdapterUsesCorrectClient(): void
{
    $emailClient = $this->createMock(ThirdPartyEmailClient::class);

    $emailAdapter = new ThirdPartyEmailSenderAdapter($emailClient);

    $emailClient->expects($this->once())
        ->method('sendEmail')
        ->with('email@email.com', 'I love design patterns');

    $emailAdapter->sendEmail(
        'email@email.com',
        'I love design patterns');
}
Enter fullscreen mode Exit fullscreen mode

You did it!

Happy programmer

Your classes are seamless integrated, tested and if you need to change the email service provider you just need to wrap the new client to implement the EmailSenderAdapter and you can switch an old interface for the new one. Congratulations.

Final code on Github.

GitHub logo fabiothiroki / php-design-patterns

A collection of design patterns written in PHP

php-design-patterns

A collection of design patterns written in PHP

PHP Composer Coverage Status

Creational

Structural






. . . . . . . . . . . . . . . . . . . . . . . . . . . .