Laravel – AggregateServiceProvider affects the performance
Some time ago I started wondering about the long bootstrap time of an application based on Laravel. I’ve started debugging and have figured out that this problem was related to the lack of deferred providers. It was strange because we’ve used a lot of deferred providers. After more debugging, we’ve found that AggregateProviders doesn’t respect deferred providers.
Problem
Our application had over 60 AggregateServiceProviders similar to the following one:
use Illuminate\Support\AggregateServiceProvider;
final class SampleProvider extends AggregateServiceProvider
{
protected $providers = [
EventsProvider::class,
ImplementationsProvider::class,
RoutingProvider::class,
];
}
In that case, ImplementationsProvider implements \Illuminate\Contracts\Support\DeferrableProvider according to the documentation:
If your provider is only registering bindings in the service container, you may choose to defer its registration until one of the registered bindings is actually needed. Deferring the loading of such a provider will improve the performance of your application, since it is not loaded from the filesystem on every request.
https://laravel.com/docs/10.x/providers#deferred-providers
If we look at the method register in \Illuminate\Support\AggregateServiceProvider we can see the following implementation:
public function register()
{
$this->instances = [];
foreach ($this->providers as $provider) {
$this->instances[] = $this->app->register($provider);
}
}
It means that every provider added to AggregateServiceProvider will be registered immediately on every request because to register a deferred provider in the container there should be used the method
registerDeferredProvider($provider, $service = null);
Don’t use AggregateServiceProvider
So the conclusion is simple, don’t use AggregateServiceProvider. I recommend using something like that:
final class SampleProvider
{
/**
* @return string[]
*/
public static function providers(): array
{
return [
EventsProvider::class,
ImplementationsProvider::class,
RoutingProvider::class,
];
}
}
Then in the configuration, we can load providers like that:
'providers' => array_merge(
// ...
\App\Modules\Module\SampleProvider::providers(),
// ...
);
Test to verify the provides method
It’s easy to make a mistake during refactoring those providers, now we must have properly configured the provides method in all deferred providers because they will be loaded only when a particular dependency is needed and the container has to find the proper provider with the definition of this dependency. So it would be good to write a custom PHPStan rule or just a little tricky test in PHPUnit.
Results after partial refactoring
Doing a partial refactoring of this problem (2/3 AggregateServiceProviders) we were able to decrease the bootstrap time of the application by over 20%. It’s probably just a few milliseconds per request (I estimate a 5 ms profit), of course, not too much, but it makes a difference on every request. Furthermore, in the future when the application will grow it will be a bigger problem, so it’s better to figure out this problem and avoid affecting the performance in a such stupid way.
Subscribe and master unit testing with my FREE eBook (+60 pages)! 🚀
In these times, the benefits of writing unit tests are huge. I think that most of the recently started projects contain unit tests. In enterprise applications with a lot of business logic, unit tests are the most important tests, because they are fast and can us instantly assure that our implementation is correct. However, I often see a problem with good tests in projects, though these tests’ benefits are only huge when you have good unit tests. So in this ebook, I share many tips on what to do to write good unit tests.
2 Responses