Today I'll be talking about this topic that seems to be pretty hot lately in Laravel community. There are countless threads on Laracasts and reddit about this. What's that? Modules. Especially how to make your Laravel applications modular.
Think of modules as sections of your application that operate on their own. For example let's say you build an app where you organize events and sell tickets. Users can come and browse their local events, buy tickets, view their bought tickets, etc. Event organizers can publish and manage events and view all sold tickets. And you, as a developer, have access to "admin panel" where you can view all events, all users, log files -- so you can manually update a resource if an error happens without digging into the database. Each of these sections are so-called modules: user dashboard, organizer dashboard and admin panel. This approach to development is called Domain-Driven Design. Each module contains logic and code specific to its domain. Organizer's dashboard module will contain its own routes, models and controllers that only handle the requests made on those pages.
There are many packages that allow you to make your apps modular. Before importing a package, make sure you understand how the core works and how to make it yourself. Once you fully understand how it works, feel free to import any package to save time. Unless you're a beginner and just started using Laravel, you should know that everything except the
vendor directory can (and should) be customized to your own needs. No two apps are the same, right? So why design them the same way?
For example: you don't like the name
app for a directory? No problem, rename it to
composer.json to pick up on that, extend the
path() method and you're done. Now you're using the
src directory instead of app. The same thing is with directory structures. You should know that your Controllers don't have to be placed at
app/Http/Controllers and that routes don't have to be loaded from
routes/web.php file. If you examine default
RouteServiceProvider that comes with default Laravel installation, you'll see this nice piece of code:
protected $namespace = 'App\Http\Controllers'; // ... rest of the code Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php'));
Yeah, you can load your controllers and routes from wherever you want. This is essentially what these packages do. I don't really like using third party code unless I really have to, so let me explain how you can make your application modular in Laravel with no package.
There are many ways to structure modules for your app without any third party code, and I'll describe the two I really like: Namespaces (modules based on a directory structure), and extracting a module into Composer package.
You should be familiar with PSR-4 and understand PHP namespaces. PSR-4 is a standard for autoloading classes. So if I have a class located in
app/Blog/Controllers/PostsController, it's namespace should be:
App\Blog\Controllers. If it is, the class will be autoloaded and can be used. We can use that to structure our directories the way we want. We can put routes related to a Blog module in
app/Blog/routes.php, controllers into
app/Blog/Controllers/ and any additional classes the same way. Yeah, even your Form Requests don't have to be located in
app/Http/Requests. They're autoloaded, so we can place them into
app/Blog/Requests/, namespace properly and use them in controllers. Heck, you can even place views into
app/Blog/views/, namespace the view in custom service provider with
View::addNamespace('blog', app_path('Blog/views')); and use them like:
return view('blog::posts.index');. If you want to get really crazy, you can extend the core Application class and register a path for Blog module by adding a
blogPath($path) method, so all the files related to a Blog will be located at
app()->blogPath(). Don't get me wrong, ALL the code that's responsible for Blog can go into the Blog directory, not only controllers and routes.
I mentioned extending Application class, if you do that, you should tell Laravel about your Application class by replacing
Illuminate\Foundation\Application with your own in
bootstrap/app.php. This is a bit advanced, so do it if you know what you're doing.
Register the module
After all the code has been migrated to Blog directory, Laravel doesn't know about the module you just built. It doesn't know it should read routes from your own custom file, or register the Controllers. At this point, you should be familiar with Service Providers. I've talked about them in my Laravel Behind the Scenes: Lifecycle series so give it a look if you haven't. Service Provider is essentially a class that hooks into Laravel core and sets up any additional stuff it needs to. In our case, it needs to load and register routes. No problem, as we've seen before,
RouteServiceProvider does the same thing. So we can copy
RouteServiceProvider class into the
app/Blog/Providers/ directory and make the changes it needs:
Route::middleware('web') ->namespace('App\Blog\Controllers') ->group(app_path('Blog/routes.php'));
You can do the same if you need any custom config files in Blog directory. Note that all classes are autoloaded, so your Blog's controllers can use any models located in your directory, just make sure to import them. At this point, Laravel still doesn't know about the module. That's because we should add the provider to the
config/app.php file at the
providers array. Add your
App\Blog\Providers\RouteServiceProvider somewhere in the array, refresh the page and voila. You've got yourself an app where all the code related to Blog is located in the
app/Blog/ directory. Do this for all your modules and you can safely remove
app/Http/Controllers/. You can move some core logic such as HTTP/Console kernel,
AuthServiceProvier or whatever is left into a directory, for example
app/Core. Just make sure to catch up where these classes are used and replace them with correct namespace.
The other way people like to structure your app is by extracting modules into a Composer package and require them. Like so:
composer require my-cool-app/blog. That way, your Laravel installation will remain as clean as possible. I don't really recommend or use it, but you can do it.
The approach I described here looks like it takes a lot of time to structure, but trust me, after you've done this couple times, you'll understand how it works and you'll be splitting your apps into modules within minutes and most importantly, you will have a full control over the code and understand how it works. And basically, once you create first module, you can just copy the entire module into another folder, remove the logic code (controllers, routes) and you've got yourself another module.
You should realize that Laravel is REALLY customizable, you don't have to stick to default directory structure and files it comes with. For example, I really like to keep core models of my application within the
app/ directory. Meaning User model, Post model, Comment model will be kept within
app/ directory, but all the other models such as Vote, Like, Bookmark, UserSettings (entities that are not core of my app) go into separate folders, maybe
app/Support or something related to their domain. I've also been mixing Single-Action controllers with CRUD (default) Controllers for years and it works great for me (let me know if you want me to talk about it). These are just an examples to help you realize you don't have to stick to one design and go with it, you can completely tailor each application to your own needs.
If you have any questions or something is not clear to you, I'd be more than happy to go into more details, just hit me up on Twitter, email or comments.
Subscribe to Josip Crnković
Get the latest posts delivered right to your inbox