Service Provider


During reading documentations of Laravel, you will find AppServiceProvider everywhere. Once you need set something globally, they teach you to add code into this class:

public function boot() { View::share('key', 'value'); Route::resourceVerbs([ 'create' => 'crear', 'edit' => 'editar', ]); Validator::extend('foo', function ($attribute, $value, $parameters, $validator) { return $value == 'foo'; }); Blade::component('components.alert', 'alert'); Blade::withoutDoubleEncoding(); Blade::directive('datetime', function ($expression) { return "<?php echo ($expression)->format('m/d/Y H:i'); ?>"; }); Blade::if('env', function ($environment) { return app()->environment($environment); }); Queue::failing(function (JobFailed $event) { // $event->connectionName // $event->job // $event->exception }); DB::listen(function ($query) { // $query->sql // $query->bindings // $query->time }); Paginator::defaultView('view-name'); Paginator::defaultSimpleView('view-name'); Schema::defaultStringLength(191); User::observe(UserObserver::class); Relation::morphMap([ 'posts' => 'App\Post', 'videos' => 'App\Video', ]); Resource::withoutWrapping(); Carbon::serializeUsing(function ($carbon) { return $carbon->format('U'); }); // ... }

And if you use a package for Laravel, you may need to register some service provider class into file config/app.php. And over there, we can find over two dozens of service providers, include App\Providers\AppServiceProvider::class too. So we see, here is where we configure service providers. If we remove one from here, it will go out of business. Now, let's check where the configuration works, by searching app.providers globally, we find it in Illuminate\Foundation\Application.

public function registerConfiguredProviders() { $providers = Collection::make($this->config['app.providers']) ->partition(function ($provider) { return Str::startsWith($provider, 'Illuminate\\'); }); $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]); (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load($providers->collapse()->toArray()); }

Um, what's an unwonted unreadable function in Laravel source code! Don't worry, let's just ignore the details and see what's the result:

(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load(dd($providers->collapse()->toArray()));

Do some testing, we can find what's the noodles do: move Illuminate service providers to the front of list, and insert package service providers between them and other configured service providers. Here is a readable version:

public function registerConfiguredProviders() { $providers = $this->config['app.providers']; $illuminateProviders = array_filter($providers, function ($provider) { return Str::startsWith($provider, 'Illuminate\\'); }); $customProviders = array_filter($providers, function ($provider) { return ! Str::startsWith($provider, 'Illuminate\\'); }); $packageProviders = $this->make(PackageManifest::class)->providers(); $providers = array_merge($illuminateProviders, $packageProviders, $customProviders); $providerRepository = new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()); $providerRepository->load($providers); }

Next, we go to the ProviderRepository::load:

// the copy of code is modified to focus on the topical public function load(array $providers) { $eagerProviders = array_filter($providers, function ($provider) { return ! ((new $provider($this->app))->isDeferred()); }); $deferredProviders = array_filter($providers, function ($provider) { return (new $provider($this->app))->isDeferred(); }); foreach ($eagerProviders as $provider) { $this->app->register($provider); } $deferred = []; foreach ($deferredProviders as $provider) { foreach ((new $provider($this->app))->provides() as $service) { $deferred[$service] = $provider; } } $this->app->addDeferredServices($deferred); }

For normal (or eager) service providers, Laravel register it one by one. For deferred providers, Laravel collect the service => provider map and add to $app. Only when the service need be resolve, the service provider will be registered.

Let's go to Illuminate\Foundation\Application::register()

// the copy of code is modified to focus on the topical public function register($provider, $force = false) { if (is_string($provider)) { $provider = new $provider($this); } $provider->register(); if (property_exists($provider, 'bindings')) { foreach ($provider->bindings as $key => $value) { $this->bind($key, $value); } } if (property_exists($provider, 'singletons')) { foreach ($provider->singletons as $key => $value) { $this->singleton($key, $value); } } if ($this->isBooted()) { if (method_exists($provider, 'boot')) { return $this->call([$provider, 'boot']); } } return $provider; }

They instantiate the service provider, then run method register(), then bind services to container, and run method boot() after the $app bootstrapped with dependency injection. So a typical service provider is like this:

class FooServiceProvider extends ServiceProvider { public $singletons = [ ConnectionInterface::class => Connection::class, ]; public $bindings = [ HelloInterface::class => Hello::class, ]; public function register() { Connection::bootstrap(); } public function boot(ConnectionInterface $connection, Hello $hello) { $connection->connect(config('foo.host')); $connection->send($hello); } }

Declare for container in $bindings and $singletons, run static initialization on register(), and run non-static initialization in boot().

In this example, we will connect the connection for each process. That's expensive. So we can use deferred service provider:

class FooServiceProvider extends ServiceProvider implements DeferrableProvider { // ... public function provides() { return [ ConnectionInterface::class, HelloInterface::class, ]; } }

Now let's back to the first code block of this article, what's a dish of noodles. We can slice them into several service providers. And make most of them deferred. Here is a example:

class ViewSharingServiceProvider extends ServiceProvider implements DeferrableProvider { public function boot(\Illuminate\Contracts\View\Factory $view) { $view->share('key', 'value'); } public function provides() { return [ 'view', ]; } }

We can use dependency injection in boot(), and deferred this with 'view' service. So that this service provider will only work for web requests but not API requests. In fact, some negligent service providers will access view service even in API requests, they make your ViewSharingServiceProvider run unnecessarily. You can make them or partition of them deferred too. They are:

Illuminate\Notifications\NotificationServiceProvider::class, Illuminate\Pagination\PaginationServiceProvider::class, // may be some third-part service providers

You can find a example of extending this two building in this project. And there is another example in this project: App\Providers\SubIndentBladeServiceProvider, it register custom directive for Blade template engine. So we make the provides() returns blade.compiler. If the view not end with .blade.php this provider will not be registered (but some third-part package will break this, such as facade/ignition).

If you are trying to modify a service provider into deferred or edit the method providers(), don't forget to delete the cache file bootstrap/cache/services.php.

The official documentations says the deferred service provider is for only registering bindings in the service container. This isn't a good guide. Our ViewSharingServiceProvider uses view service rather registers anything, it's just okay and worthy to be a best practice.

Summing up

We've explored how and when the service providers and their members work, understood how to write a good service provider. We also learned when and how to use deferred providers to optimize our application. And don't write a AppServiceProvider filled with noodles.