DEV Community

Bilal Haidar
Bilal Haidar

Posted on

🧩 Mastering the Manager Pattern in Laravel — Build Pluggable, Scalable Architectures

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');
Enter fullscreen mode Exit fullscreen mode

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');
    }
}

Enter fullscreen mode Exit fullscreen mode

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
    }
}

Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Now, you can easily switch between payment providers by editing your .env:

PAYMENT_DRIVER=paypal
Enter fullscreen mode Exit fullscreen mode

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)