Laravel allows you to add custom drivers for a whole host of its features, such as sessions, database connections, authentication, caching, and so on. In almost all of these cases, the driver is provided as a name, and with a callback to resolve it, to a method called extend that exists on the primary manager for the feature. This method isn't static, so it needs to be called on an instance of the manager class. You can do this either by requesting it directly from the container, or by using a facade.

The problem is, when doing this, you're eager loading the entire feature, on every request, even ones where it isn't needed. On top of that, it limits where you can call this method, as you'll want to make sure that everything that's needed is already available. This is why the general rule of this is to do it in the boot method of a service provider, though that only solves half the problem.

The best way to handle custom driver registration, without eager loading an entire feature, is to defer the registration until the feature is used. Because of Laravel's dependency injection, it's safe to assume that when the feature is used, it'll be resolved in the container, so you can make use of its resolution events, specifically, the after event.

$this->app->afterResolving(SessionManager::class, function (SessionManager $manager) {
    $manager->extend('custom', function (Application $app) {
        return new CustomSessionHandler();
    });
});

Using the afterResolving method, when the SessionManager and any of its aliases are resolved, the provided callback will be called, which will in turn register the custom driver.

Warning

The provided callback will only be called when resolving the class provided, or its aliases. This does not include separate bindings that happen to use the provided class as a concrete.

Occasionally, the manager will have already been resolved, and as they're typically bound as shared singletons, the callback won't be used, as it's already resolved. To get around this, you'll want to check if it's already been resolved, and register normally if it has, or fallback to the afterResolving callback if it hasn't.

if ($this->app->resolved(SessionManager::class)) {
    $manager->extend('custom', function (Application $app) {
        return new CustomSessionHandler();
    });
} else {
    $this->app->afterResolving(SessionManager::class, function (SessionManager $manager) {
        $manager->extend('custom', function (Application $app) {
            return new CustomSessionHandler();
        });
    });
}

Info

Unfortunately, there's not currently a way to do this using a single method, though I am considering creating a PR to allow it, possibly by adding an extra parameter to afterResolving.

public function afterResolving(
    $abstract,
    ?Closure $callback = null,
    bool $fireIfResolved = false
)

I'm Ollie Read—a software architect, Laravel and PHP expert, and lifelong builder. I write to share what I learn, explore how things work, and help others.

Got thoughts, questions, or just want to chat? I'm always up for a good conversation— get in touch, or find me on BlueSky, X or Discord

Method
afterResolving()
Class
Illuminate\Container\Container
Parameters
\Closure|string $abstract - The name of class/dependency, or the callback if it's global.
\Closure|null $callback - The callback to be called when the class is resolved. Defaults to null
Returns
void - Returns nothing.
Description

Registers a callback to be called after a class is resolved. Supports both callbacks for specific classes, or global callbacks for every resolution.

Links
Class
Route
Namespace
Illuminate\Routing
Description

This class represents both the registered route, and the current route state within Laravel.

Method
forgetParameter()
Class
Illuminate\Routing\Route
Parameters
string $name - The name of the parameter to forget.
Description

Unset a parameter on the route if it is set.

Links