Gateway Pattern
debt(d7/e7/b7/t3)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no', and while phpstan and deptrac are listed as tools, they cannot automatically detect the absence of a gateway pattern — they can only flag direct external calls if custom rules are configured. The code_pattern describes scattered Guzzle/cURL calls and duplicated error handling, which requires deliberate architectural review to identify rather than standard automated analysis.
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix describes creating a Gateway class as a single entry point, but the why_it_matters example (Stripe calls scattered throughout the codebase) and common_mistakes (scattered direct SDK calls, missing interfaces, fat gateways) indicate that remediation involves hunting down all direct external calls across multiple service classes, introducing interfaces, wrapping exceptions in domain exceptions, and restructuring — a cross-cutting refactor rather than a localised fix.
Closest to 'strong gravitational pull' (b7). The pattern applies across web, cli, and queue-worker contexts. Every interaction with an external system is shaped by whether a gateway exists — without it, every future feature touching that external system inherits the scattered-call problem. With it, the gateway becomes a load-bearing abstraction that every developer must be aware of and route through. The common_mistakes (one giant gateway, no interface, leaking exceptions) compound the structural weight if done poorly.
Closest to 'minor surprise (one edge case)' (t3). The misconception field identifies a real but relatively subtle confusion — developers conflate Gateway with Adapter. Both wrap external interfaces, but the distinction (Gateway = external system access; Adapter = interface translation) is a documented gotcha rather than a catastrophic misdirection. The common_mistakes (leaking provider exceptions, fat gateways) add minor surprises but are not deeply counterintuitive to a competent developer.
Also Known As
TL;DR
Explanation
A Gateway (Martin Fowler, PoEAA) isolates external dependencies behind an interface. PaymentGateway, EmailGateway, SMSGateway — each wraps one external system. Benefits: the application code never calls the HTTP client or SDK directly; the gateway can be mocked in tests; switching providers means only changing the gateway implementation. Similar to Adapter but typically wraps external systems rather than incompatible interfaces. Distinguished from Repository (data storage) by focusing on external service calls.
Common Misconception
Why It Matters
Common Mistakes
- Gateway that leaks provider-specific exceptions — wrap them in domain exceptions.
- Fat gateway with too much logic — gateways translate calls, not implement business rules.
- Not defining a gateway interface — without an interface, mocking in tests requires more work.
- One giant ExternalServiceGateway — one gateway per external system.
Code Examples
// No gateway — Stripe SDK scattered everywhere:
class OrderService {
public function charge(Order $order): void {
$stripe = new \Stripe\StripeClient(getenv('STRIPE_KEY'));
$charge = $stripe->charges->create([
'amount' => $order->total->cents(),
'currency' => 'gbp',
'source' => $order->paymentToken,
]);
// Stripe-specific code in domain service
}
}
// Gateway isolates the provider:
interface PaymentGateway {
public function charge(Money $amount, string $token): PaymentResult;
}
class StripeGateway implements PaymentGateway {
public function charge(Money $amount, string $token): PaymentResult {
$charge = $this->stripe->charges->create([...]);
return new PaymentResult($charge->id, $charge->status);
}
}
// Domain service depends on interface:
class OrderService {
public function __construct(private PaymentGateway $payments) {}
public function charge(Order $order): void {
$result = $this->payments->charge($order->total, $order->token);
}
}