Adapter Pattern in Laravel: A Practical Guide
When working on large Laravel applications, you’ll often face situations where two systems need to communicate but don’t share the same interface.
For example:
- You start with Stripe as your payment provider.
- Later, your business requires PayPal integration.
- Each has completely different SDKs and methods.
Without structure, this leads to messy if/else logic scattered across your codebase. Enter the Adapter Pattern — a clean way to make different systems “speak the same language.”
🔹 What is the Adapter Pattern?
The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a “translator” between two classes that otherwise can’t directly interact.
Think of it like a power adapter:
- Your laptop has a USB-C port.
- The projector has an HDMI cable.
- An adapter allows them to connect seamlessly.
In Laravel, this is particularly useful when you want to:
- Swap third-party services (payment, SMS, storage, etc.).
- Isolate vendor-specific logic.
- Write clean, testable, and maintainable code.
🔹 Advantages of Using Adapter Pattern in Laravel
- Decoupling — Your application code doesn’t need to know about the third-party API details.
- Flexibility — Swap implementations without changing your business logic.
- Testability — You can easily mock adapters in tests.
- Maintainability — Vendor-specific code stays in one place.
- Scalability — Easily add new integrations following the same interface.
🔹 Real-World Example
Imagine you are integrating two payment gateways in your Laravel app: Stripe and PayPal. Each has a different API, but your application only wants to call a generic method like charge($amount).
Without adapters, you’d end up with messy if/else or duplicated logic. With the Adapter Pattern, you can abstract this.
🔹 Step-by-Step Implementation in Laravel
- Define a Common Interface{
<?php
namespace App\Contracts;
interface PaymentGatewayInterface
{
public function charge(float $amount): string;
}2. Create Adapters for Each Gateway:
Stripe Adapter
<?php
namespace App\Adapters;
use App\Contracts\PaymentGatewayInterface;
use Stripe\StripeClient;
class StripeAdapter implements PaymentGatewayInterface
{
protected StripeClient $stripe;
public function __construct()
{
$this->stripe = new StripeClient(env('STRIPE_SECRET'));
}
public function charge(float $amount): string
{
// Example: Create a payment intent
$payment = $this->stripe->paymentIntents->create([
'amount' => $amount * 100,
'currency' => 'usd',
'payment_method_types' => ['card'],
]);
return "Stripe payment successful: " . $payment->id;
}
}PayPal Adapter
<?php
namespace App\Adapters;
use App\Contracts\PaymentGatewayInterface;
use PayPal\Api\Payment;
class PaypalAdapter implements PaymentGatewayInterface
{
public function charge(float $amount): string
{
// Example PayPal logic
return "PayPal payment successful: " . uniqid('paypal_');
}
}3. Use Adapters in a Service
<?php
namespace App\Services;
use App\Contracts\PaymentGatewayInterface;
class PaymentService
{
protected PaymentGatewayInterface $gateway;
public function __construct(PaymentGatewayInterface $gateway)
{
$this->gateway = $gateway;
}
public function process(float $amount): string
{
return $this->gateway->charge($amount);
}
}4. Bind the Adapter in a Service Provider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Contracts\PaymentGatewayInterface;
use App\Adapters\StripeAdapter;
use App\Adapters\PaypalAdapter;
class PaymentServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(PaymentGatewayInterface::class, function ($app) {
return match (config('payment.driver')) {
'stripe' => new StripeAdapter(),
'paypal' => new PaypalAdapter(),
default => new StripeAdapter(),
};
});
}
}5. Use in Controller
<?php
namespace App\Http\Controllers;
use App\Services\PaymentService;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
public function pay(Request $request, PaymentService $paymentService)
{
$amount = $request->input('amount', 100);
$result = $paymentService->process($amount);
return response()->json(['message' => $result]);
}
}6. Configurable Driver
Create a config file config/payment.php:
<?php
return [
'driver' => env('PAYMENT_DRIVER', 'stripe'),
];In .env:
PAYMENT_DRIVER=stripe // or paypal🔹 Visual Representation
Here’s a simple way to visualize it:
[ Controller ] → [ PaymentService ] → [ PaymentGatewayInterface ]
↙ ↘
[ StripeAdapter ] [ PaypalAdapter ]The controller doesn’t care which adapter is used — it only talks to the interface.
Where Can You Apply Adapter Pattern in Laravel?
Besides payments, you can use the Adapter Pattern in many real-world Laravel cases:
- SMS Services → Twilio, Nexmo, MSG91.
- File Storage → AWS S3, Google Cloud, Local.
- Notifications → Slack, Email, WhatsApp APIs.
- Search Engines → Elasticsearch, Algolia, Typesense.
- Geocoding APIs → Google Maps, OpenStreetMap.
🔹 Best Practices When Using Adapter Pattern
- Always start with a contract (interface) — this guarantees consistency.
- Keep adapters thin — they should only translate, not contain business logic.
- Use Service Providers to bind adapters dynamically.
- Combine with Laravel’s config system for flexibility.
- Write unit tests with mocks instead of hitting real APIs.
🔹 Conclusion
The Adapter Pattern in Laravel is like a universal translator for your application. It allows you to integrate multiple third-party services without creating messy conditional logic or vendor lock-in.
With this approach, your app becomes:
- More maintainable
- Easier to test
- Highly flexible when business requirements change
If you’re building scalable Laravel applications, the Adapter Pattern should definitely be part of your design toolkit.
✨ Pro tip: Pair the Adapter Pattern with Strategy Pattern for even more powerful abstractions (e.g., dynamically choosing gateways at runtime).
🙏 Thanks for reading! If you found this helpful, please like, share, and follow me on LinkedIn for more Laravel and software design insights.
