Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

The Manager Pattern in Laravel

--  redirect("/blog")
-- by Newton Job
-- Apr 14, 2025

I recently worked on an application for making hotel reservations. A customer books a room, and we charge them using one of our payment gateways. However, I needed to support multiple payment gateways, such as Stripe, Paddle and Paypal.

This adds complexity, but I wanted to keep the code as easy as possible to manage and change. I wondered if I could implement this in such a way that would allow us to easily switch gateways.

My first thought was: "Ideally, this should work the same as many of Laravel's built in components."

Think about it: Laravel provides many similar features, like Storage, Cache, Queue, etc. Each of these components support multiple drivers, allowing you to switch drivers without having to update your code. The API is identical!

Let's consider the Storage facade as an example:

Storage::put('image.jpg', $contents);

By default, Laravel will use the default storage driver as defined in your FILESYSTEM_DISK environment variable. Of course, you can also explicitly specify a driver by calling the disk method.

Storage::disk('local')->put('image.jpg', $contents);

Storage::disk('s3')->put('image.jpg', $contents);

These APIs are unified, allowing you to switch drivers without touching your code. Other components such as Cache and Queue are implemented using the same concept. This technique is referred to as "The Manager Pattern". It's exactly what I needed for my hotel reservations project.

In my mind, without thinking about the implementation details, the API should look like this:

// Charge the user using the default payment gateway.
Payment::charge($user, 200);

// Charge the user using a specific payment gateway
Payment::gateway('stripe')->charge($user, 200); 
Payment::gateway('paypal')->charge($user, 200);

Creating a Manager Class

Ok, let's get to work. How might we achieve an API like the above example? Well, thankfully, Laravel ships with the Illuminate\Support\Manager class. This class does the heavy lifting, as it contains the base logic that'll help us create our Manager class very quickly. You can take a look at the class on GitHub. All we have to do is extend it and add the supported drivers:

namespace App\Support\Payment;

use Illuminate\Support\Manager;

class Payment extends Manager
{
    protected function createStripeDriver(): StripeDriver
    {
    	return $this->container->make(StripeDriver::class);
    }

    protected function createPaypalDriver(): PaypalDriver
    {
    	return $this->container->make(PaypalDriver::class);
    }

    public function getDefaultDriver(): string
    {
        return config('app.payment.driver', default: 'stripe');
    }
}

Our Manager class is all set. As you can see, the class quite simple! You just add methods that create specific driver classes.

Notice the naming convention here: create{$driver}Driver, where $driver is the PascalCase representation of the driver name.

The getDefaultDriver method only needs to return the default driver "name" that'll be used whenever we don't explicitly specify one. In our example, we are trying to pull it from a custom config app.payment.driver. This means we can easily pull that from an environment variable. You get the idea.

How you implement the various driver classes is completely up to you. When it's simple enough, I typically just create an AbstractDriver class with abstract methods from which other drivers extend, but you may also choose to create an interface that they implement. The ultimate goal here is that each driver class contains the same set of public methods that match your preferred interface.

Now, supporting a new payment gateway in the future is just a matter of creating a new Driver class and adding the appropriate method to the Manager class.

class Payment extends Manager
{
    // ...

    protected function createPaddleDriver(): PaddleGateway
    {
        return $this->container->make(PaddleGateway::class);
    }
}

Using the Manager

We've mostly focused on creating the Manager class. Let's now look at how we can actually use it.

Notice that we created the Payment Manager under the App\Support namespace. There's nothing special about that namespace. You're free to create your classes anywhere you wish. I like to keep classes that don't have a special place in the default Laravel directory structure under App\Support. This is in line with the flow of classes that live under Laravel's Illuminate\Support namespace.

To use our Manager class, we need to resolve it from the Laravel container. There is no shortage of ways to do this, so you pick! :)

use App\Support\Payment;

$payment = app(Payment::class);
$payment = resolve(Payment::class);
$payment = app()->make(Payment::class);
// And many more...

$payment->charge($user, 200);

The focus here is not on how to resolve the Manager class from the container. I love real-time facades, they're a truly magical feature in Laravel. It affords us an elegant way to resolve our Payment Manager class and use it like a facade. We only have to prefix the namespace with Facades\ and off we go!

use Facades\App\Support\Payment;

Payment::charge($user, 200);

// Charge the user using Stripe
Payment::driver('stripe')->charge($user, 200);

Wrapping Up

  • Remember that the charge method exists on each Driver class. Of course, you can add other methods there as well depending on what other payment operations you typically perform.

  • The Manager class only contains methods that handle driver creation, nothing else. The Driver classes perform the actual work.

  • Of course, you can add other helper methods to the Manager class to bring it closer to your business domain. For example, we can define a gateway method as an alias for the driver method from the base Manager class.

    class Payment extends Manager
    {
    	// ...
    
       public function gateway($gateway): AbstractDriver
    	{
    	    return $this->driver($gateway);
    	}
    }
    
    // Payment::gateway('stripe')->charge($user, 200);
    

Now that's the exact same API we imagined at the start, right?

Conclusion

Hopefully, you now have a good, practical understanding of what the "Manager Pattern" is, and how you can take advantage of it in your Laravel projects. That's it, have fun!