Adapter Pattern
debt(d6/e5/b5/t5)
Closest to 'specialist tool catches it' (d5), +1. Tools like phpstan and deptrac can detect direct usage of third-party classes in domain code (e.g., vendor namespace imports in business logic layers), but this requires custom rulesets and architectural boundary configuration. It's not a default check — it requires deliberate setup. Without such configuration, detecting missing adapters falls to careful code review, pushing it slightly above d5 toward d7.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests creating an adapter class, but in practice when the pattern is missing, a third-party library is likely called directly in multiple places across a component or even multiple components. Introducing the adapter means creating the adapter class, defining the target interface, and updating all call sites to use the interface instead. This is a significant refactor but typically within one bounded area of the codebase.
Closest to 'persistent productivity tax' (b5). The adapter pattern applies across all contexts (web, cli, queue-worker) and is a structural design pattern. When missing, it creates coupling that slows down many work streams — switching libraries becomes painful, testing is harder, and third-party changes ripple through business logic. When present, it's a load-bearing abstraction that shapes how integrations are done across the system, but it doesn't quite define the system's entire shape.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception that Adapter and Facade are interchangeable is a well-documented confusion — both wrap other code, but for different reasons. Common mistakes like adapters that also transform data, leaking underlying exceptions, or creating one adapter per class instead of per entry point are real traps that competent developers fall into. These aren't catastrophic but are notable pitfalls that most developers learn about only through experience.
Also Known As
TL;DR
Explanation
The Adapter (Wrapper) pattern bridges the gap between an existing class with an incompatible interface and the interface expected by client code. It wraps the adaptee and implements the target interface, translating calls. This is essential when integrating third-party libraries or legacy code without modifying either. In PHP, adapting a payment gateway SDK to a standardised PaymentGatewayInterface, or wrapping a legacy Logger to a PSR-3 LoggerInterface, are common use cases. Adapters also facilitate testability by wrapping external dependencies.
Diagram
flowchart LR
CLIENT[Client Code] -->|expects| TARGET[Target Interface<br/>send email]
TARGET -.->|implemented by| ADAPTER[SendGridAdapter]
ADAPTER -->|translates to| VENDOR[SendGrid SDK]
ADAPTER2[MailgunAdapter] -->|translates to| VENDOR2[Mailgun SDK]
CLIENT -.->|can swap| ADAPTER2
INFO[Client never changes<br/>only swap the adapter]
style CLIENT fill:#1f6feb,color:#fff
style ADAPTER fill:#238636,color:#fff
style ADAPTER2 fill:#238636,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Adapters that transform data as well as interface — separate the data mapping from the interface adaptation.
- Not programming to the target interface — the adapter is only useful if callers depend on the interface, not the adapter.
- Creating an adapter for every third-party class rather than a single adapter for the library's entry point.
- Adapters that leak the underlying library's exceptions — wrap them in domain exceptions.
Avoid When
- The interface mismatch is trivial — a one-line wrapper does not need a full adapter class.
- You control both interfaces and can change them — adapt the source instead of wrapping it.
- Overusing adapters hides poor API design that should be fixed at the source.
When To Use
- Integrating a third-party library whose interface does not match your domain's expectations.
- Making legacy code work with a new interface without modifying the legacy code.
- Enabling multiple implementations behind a single interface for swappability.
- Wrapping external APIs so your domain is insulated from their changes.
Code Examples
// Direct dependency on third-party Stripe library everywhere:
class PaymentService {
public function charge(float $amount): void {
\Stripe\Charge::create(['amount' => $amount * 100, 'currency' => 'usd']);
// Stripe leak throughout codebase — impossible to swap
}
}
// Better: StripePaymentAdapter implements PaymentGatewayInterface
// Your app expects this interface
interface Logger {
public function log(string $level, string $message): void;
}
// Third-party library uses a different API
class MonologAdapter implements Logger {
public function __construct(private \Monolog\Logger $monolog) {}
public function log(string $level, string $message): void {
$this->monolog->$level($message); // adapts your interface to Monolog's
}
}
// Your code depends on Logger — not Monolog directly
$logger = new MonologAdapter(new \Monolog\Logger('app'));
$service = new OrderService($logger);