0

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): Implements MailerInterface, wraps ThirdPartyEmailer, 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 the Cache\Repository contract.

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.