PHP Design Patterns — Adapter

Fabio Hiroki
3 min readNov 14, 2021
Photo by ready made from Pexels

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

Users feeling joy when using your application

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);
}
}
Classes trying to communicate

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

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;
}

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);
}
}

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

Seamless integration between classes

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');
}

You did it!

Much better now!

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.

--

--