Laravel: The hidden Manager class

Create, extend, and enjoy any driver you want

Italo Baeza Cabrera
6 min readDec 23, 2019

When creating your application, sometimes you may find a class that accepts different implementations of something. For example, a delivery courier may need any transportation to reach your house with your package: bicycle, car, or even public transport.

Okay, you get the picture. Now you need a way to create these implementations depending on what the delivery courier needs to use to reach your house. And each of these implementations of transport have their own way to get created. I mean, creating a bus is very different from a bicycle, and totally apart from public transport which depends on taxis and buses, but each of these start from a location and reach a destination.

For these kind of problems, there is a nice class called Manager that Laravel ships with out of the box and eases most of the pain of managing multiple drivers.

The Manager class was briefly documented for Laravel 5.0, but subsequent releases stop including this may be because its usage became very limited.

The purpose of a Manager

The Manager class mixes some philosophies of the Abstract Factory pattern, allowing the developer to put the creation task of different classes into just one class, instead of having to create something from scratch everywhere. The additional bit is that these instances that are created are also stored inside the Manager class, so they are not instanced everytime, but retrieved.

The manager class is responsible for creating a particular driver implementation based on the application's configuration. For example, the CacheManager class can create APC, Memcached, File, and various other implementations of cache drivers.

Each of these managers includes an extend method which may be used to easily inject new driver resolution functionality into the manager at run time.

This means, the driver instanced will live in memory for as long it is needed.

Going back to our delivery courier example, the guy who has your package doesn’t need to know how to build a bicycle, nor teach him. He just need to ask his company’s factory one for the delivery, and the factory will build one for him if it wasn’t already done previously. If the guy get replaced for another, you won’t need to teach him how to build a bicycle again, the factory is in charge of that.

But the Manager class is more than just a pattern. Let’s see its insides:

Seems weird, but don’t sweat it. We are gonna make a quick tour on what this class offers.

Reusability

As you can see, the Manager works with "drivers”, which are the instances of classes you want to work with. These drivers can be declared using methods following the create{Name}Driver pattern:

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

Once instanced, these won’t be destroyed but rather live inside a pool where these can be reused again if needed.

For example, when you use the Database, the same “connection” gets reused multiple times. This saves on instancing, thus avoiding creating a database connection every time.

Once our courier guy stops using his bicycle and returns it to the factory, the factory will reuse the same bicycle for another delivery, instead of creating the same from scratch.

Extendability

The Manager is not bound to only the drivers you set up, but more can be added at runtime using the extend() method.

public function boot(TransportManager $manager)
{
$manager->extend('quad', function ($container) {
return $container->make(QuadMotorcycle::class);
});
}

The method accepts a callback, which receives the application container, so you can create a driver not offered by the Manager class aloing other services if needed. There is no need to edit or extend the class with another.

The courier guy can ask for a quad motorcycle. Since the factory doesn’t know how to create one, he will pass a document with instructions about how to create one and receive it. This will also allow others to reuse the same quad motorcycle if they want it.

Overridability

You can also override a driver by “extending” the manager with the driver name. An extended driver will take precedence over the original, so you can override the driver for whatever reason.

public function boot(TransportManager $manager)
{
$manager->extend('bicycle', function ($container) {
return $container->make(BicycleTransport::class)
->include(new Umbrella);
});
}

Our delivery courier guy can tell the factory for a bicycle, but pass a document with instructions on including an umbrella for the bicycle for rainy days. If it’s not raining, no instructions are passed and he will receive it’s old trusty bicycle.

Defaulting

The Manager will always spit out the default driver if there is not one specified. This means that everything that asks the Manager for a driver will get a default one, instead of having to specifying which.

You can call the manager default driver by just calling any driver method, without having to call driver(). The Manager will automatically instance the default driver and pass the call to it.

$manager->do('something')->and('celebrate');

In layman terms, our delivery courier guy will receive a car unless he says a otherwise.

Using the Manager

Well, the Manager is an abstract class, so we need to extend it depending on what to manage. In this example, we will create a TransportManager so our courier guys can use it to deliver the packages.

The class is very simple: we set some drivers to use using the pattern create{Name}Driver for each driver method, and we set the default, which in this case is bicycle. This means the TransportManager will call the createBicycleDriver() method by default if no driver is previously called.

In any part of your code, we can just simply use Dependency Injection to retrieve the manager and get a driver. For the sake of illustration, we will put this under a Controller in charge of starting the delivery process.

Check the line number 31: we start the delivery with the default driver in just one expressive line. No need to create a new transport or even deal with the same transport instance in other parts of the code.

And, if you want to use another transport for delivery, you are literally a few characters away:

$manager->driver('anything')
->send($package)
->to($request->input('address'));

Need drivers? No problem, add some to the Manager class. Additionally, you can override the driver() method to not save the instance in the pool, specially if you want to have more memory control on that. I mean, not everything must be saved into the container, like throwaway objects that only fill their purpose one time, or others need to be instantiated from scratch every time — in these cases, I would think twice about using the Manager class.

/**
* Get a driver instance.
*
* @param string $driver
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function driver($driver = null)
{
$driver = $this->createDriver(
$driver ?: $this->getDefaultDriver()
);
if ($driver) {
return $driver;
}
throw new InvalidArgumentException(sprintf(
'Unable to resolve NULL driver for [%s].', static::class
));
}

And that’s practically the basics of this class. Have fun adding your own drivers.

--

--

Italo Baeza Cabrera

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com