๐Ÿงฉ Mastering the Manager Pattern in Laravel โ€” Build Pluggable, Scalable Architectures



This content originally appeared on DEV Community and was authored by Bilal Haidar

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.


This content originally appeared on DEV Community and was authored by Bilal Haidar