Adapter — wrapping incompatible interfaces
Intermediate5 min read·eng-03-001
interviewlaravel-srccompare
Concept
Adapter pattern (also called Wrapper) makes incompatible interfaces work together. It wraps an existing class with a different interface — the class you want to use, adapted to the interface you need.
The problem: You have ThirdPartyEmailer with a sendEmail(to, from, subject, body) method. Your system expects MailerInterface with send(Message $message). You can't change ThirdPartyEmailer. The Adapter bridges this.
Structure:
- Target Interface (
MailerInterface): What your code depends on. - Adaptee (
ThirdPartyEmailer): The existing class you want to use. - Adapter (
ThirdPartyEmailerAdapter): ImplementsMailerInterface, wrapsThirdPartyEmailer, translates calls.
Object Adapter vs Class Adapter:
- Object Adapter: Holds an instance of the Adaptee as a dependency. Preferred — more flexible.
- Class Adapter: Extends the Adaptee AND implements the Target. Can't adapt multiple classes; requires inheritance. PHP supports this but it's less clean.
Real-world examples:
- Adapting a third-party payment gateway to your
PaymentGatewayInterface. - Adapting PSR-3 Logger to a legacy logger.
- Adapting an external API client to your internal interface.
- Laravel:
Cache::store('redis')adapts the Redis client to theCache\Repositorycontract.
Adapter vs Facade: Adapter translates one interface to another existing interface. Facade creates a simpler interface to a complex subsystem (not translating — simplifying).
Code Example
php
<?php
// Target interface — what your code depends on
interface MailerInterface
{
public function send(Message $message): void;
}
class Message
{
public function __construct(
public readonly string $to,
public readonly string $from,
public readonly string $subject,
public readonly string $body,
public readonly bool $isHtml = true,
) {}
}
// Adaptee — existing third-party class you can't modify
class SendGridClient
{
public function transmit(array $payload): bool
{
// Calls SendGrid API with a specific payload structure
return true;
}
}
class PostmarkClient
{
public function deliver(string $recipient, string $sender, string $subj, string $html): void
{
// Calls Postmark API
}
}
// Adapters — translate MailerInterface to each client's interface
class SendGridAdapter implements MailerInterface
{
public function __construct(private readonly SendGridClient $client) {}
public function send(Message $message): void
{
$this->client->transmit([
'to' => [['email' => $message->to]],
'from' => ['email' => $message->from],
'subject' => $message->subject,
'content' => [['type' => 'text/html', 'value' => $message->body]],
]);
}
}
class PostmarkAdapter implements MailerInterface
{
public function __construct(private readonly PostmarkClient $client) {}
public function send(Message $message): void
{
$this->client->deliver(
recipient: $message->to,
sender: $message->from,
subj: $message->subject,
html: $message->body
);
}
}
// Service uses only MailerInterface — doesn't know about SendGrid or Postmark
class WelcomeEmailService
{
public function __construct(private readonly MailerInterface $mailer) {}
public function sendWelcome(string $userEmail): void
{
$this->mailer->send(new Message(
to: $userEmail,
from: 'noreply@example.com',
subject: 'Welcome!',
body: '<h1>Welcome aboard!</h1>',
));
}
}
// Container binding — swap providers without touching service code
// $container->bind(MailerInterface::class, fn() => new SendGridAdapter(new SendGridClient()));
// vs:
// $container->bind(MailerInterface::class, fn() => new PostmarkAdapter(new PostmarkClient()));
## Interview Q&A
**Q: What's the difference between Adapter and Decorator?**
A: Adapter changes an interface — it makes an incompatible interface compatible with what the caller expects. Decorator preserves the same interface but adds behaviour (wraps and extends). With Adapter, caller sees a different interface than the adaptee has. With Decorator, caller sees the same interface as the decorated object.