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
)