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 Illuminate\Support\Manager::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 Illuminate\Foundation\Application::afterResolving()
method, when the Illuminate\Session\SessionManager
and any of its aliases are resolved, the provided callback
will be called, which will in turn register the custom driver.
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 Illuminate\Foundation\Application::afterResolving()
callback if it hasn't.
if ($this->app->resolved(SessionManager::class)) {
$manager = $this->app->make(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();
});
});
}
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 Illuminate\Foundation\Application::afterResolving()
.
public function afterResolving(
$abstract,
?Closure $callback = null,
bool $fireIfResolved = false
)