One of the most powerful, yet least discussed, features in Laravel’s design is its Manager pattern.
It’s what gives Laravel its flexibility to easily switch between different drivers like Redis, File, S3, Mailgun, or Database queues — all through configuration.
Let’s break down how it works, why it’s elegant, and how you can use it in your own applications.
🧠What’s a Manager?
A Manager is a class that knows how to build and manage multiple “drivers”, each implementing a common interface but doing things differently.
Example from Laravel’s core:
CacheManager handles file, redis, database
MailManager handles smtp, mailgun, sendmail
QueueManager handles sync, database, redis
All of these extend Illuminate\Support\Manager, which gives them methods like:
$manager->driver('redis');
$manager->driver('file');
The framework caches drivers, manages dependencies, and lets you easily switch by changing .env — no code changes needed.
🧩 Real Example — PaymentManager
Imagine your app supports multiple payment gateways — Stripe, PayPal, and AppSumo.
Instead of cluttering your code with conditionals, create a clean PaymentManager to handle them.
Step 1: Create your PaymentManager
namespace App\Managers;
use Illuminate\Support\Manager;
class PaymentManager extends Manager
{
protected function createStripeDriver()
{
return new \App\Services\Payments\StripeService();
}
protected function createPaypalDriver()
{
return new \App\Services\Payments\PaypalService();
}
protected function createAppsumoDriver()
{
return new \App\Services\Payments\AppsumoService();
}
public function getDefaultDriver()
{
return config('app.payment_driver', 'stripe');
}
}
Each createXDriver() method builds a specific driver.
Laravel will call the correct one automatically based on the name you pass to driver().
Step 2: Example Drivers
namespace App\Services\Payments;
interface PaymentContract
{
public function charge($user, $amount);
}
class StripeService implements PaymentContract
{
public function charge($user, $amount)
{
// Stripe API logic
}
}
class PaypalService implements PaymentContract
{
public function charge($user, $amount)
{
// PayPal API logic
}
}
Each driver follows the same interface (PaymentContract).
Step 3: Using the Manager
$paymentManager = app(App\Managers\PaymentManager::class);
$payment = $paymentManager->driver(config('app.payment_driver'));
$payment->charge($user, 100);
Now, you can easily switch between payment providers by editing your .env:
PAYMENT_DRIVER=paypal
No code changes required. Your architecture remains clean, scalable, and maintainable.
⚙️ Why This Pattern Rocks
âś… Config-driven: No conditionals, just configuration
âś… Extensible: Add new drivers anytime
âś… Testable: Mock a driver in your tests easily
✅ Proven: It's used across Laravel’s core services
🧩 Bonus — When to Use a Manager
Use this pattern when you have:
- Multiple implementations of the same interface (e.g., notifications, analytics, storage)
- Dynamic runtime switching (e.g., per-tenant configuration)
- Need for test isolation and maintainable abstractions
đź§ Final Thoughts
The Manager pattern is part of what makes Laravel both beautiful and enterprise-ready.
Once you understand it, you can architect systems that are modular, testable, and scalable, just like Laravel itself.
Top comments (0)