I touched on service providers in my previous post about taking a "Sorta-kinda Domain-Driven Structure in Laravel" and I wanted to expand a little further on how I see the best way to use them as well as sharing some code that works as a base class for all your future module service provider needs!

So building on from my previous post, I'm assuming you are taking this domain-driven structure fairly seriously, so your app folder is full of modules. These modules contain controllers, events, listeners, service classes, the whole shebang! But how do you tell Laravel about all this goodness?

By default you'd sprinkle some things in your AppServiceProvider to register some bindings, you'd then add some listeners in your EventServiceProvider, or putting all your routes into one app.php, etc, until you end up in a bundle of unorganized chaos!

Fear not, brave warrior, there is a better way! Almost every module can have a service provider that lives inside the module. That service provider will be responsible for registering everything specific only for that module.

Module service provider overview

I've put a Gist together to show you the abstract base provider that you can copy-paste as well as an implementation to help see all the amazing things you can do with it!

The Gist might make it look intimidating but after extending the base service provider your module service providers are going to be really organised and only include the parts they are concerned about. No bindings, no problem. No commands, don't include them. No routes, no method. Simple. Every method or property in the concrete service provider is optional! 🤩

Routes

Using the routes() method you can define all the routes for your module, in much the same way as you would in the default routes.php file, including adding middleware, groups, name, etc. This method provides you with the underlying router object so you don't even need to break out the facades.

Bindings

Your module's container bindings (concrete -> interface) are all handled in the bindings() method. This method provides you with the underlying application class as the passed argument so you can call the bind and singleton methods directly.

Commands, Listeners, and Subscribers

Commands, listeners and subscribers are all registered using the corresponding class properties. Add your FQNS classes to the arrays and they will be registered when the app is booted. The only exception to this is the 'listeners' property, this follows the same pattern as in Laravel's default EventServiceProvider, with a Event::class => [Listener::class] approach.

Registering the module service provider

You can register these service providers in the same way you would a regular service provider by adding it to the array in config/app.php.

Final thoughts 🧠

Although part of a larger structural change I think there is plenty that can be taken from these service providers, if not only keeping your service providers a little cleaner, as opposed to shoving everything into a boot method or separating related code into many different service providers.

Taking this approach both to service providers and the general domain-driven structure is just part of a larger take on how I am building my Laravel apps currently. Finding these simpler ways to navigate complexity helps me enjoy product development without sweating the details.