Laravel is an amazing emerging, well-designed and well-developed PHP framework. It is currently under active development and has been among top Web development frameworks. For a simple development project, the structure is totally fine. Nevertheless, you might not want to mix up your source code and resources with Laravel’s except some really necessary configurations.
Moreover, you might also want to organise your project into submodules that are assigned to more than one team/person such that they can be developed silmutaneously. Thus, the need for complex submodule organisation is quite inevitable.
From my struggle to organise a Laravel-based project such that I can divide and work on individual submodules such as UserManagement
, Authentication
, Dashboard
, etc. whilst keeping the Laravel code base intact as much as possible (this could be convenient for upgrading Laravel) and keeping my code base separate from Laravel’s.
There are a number decent attempts on modalurasing Laravel projects for example here, here or here. Being inspired and learning from these articles, I decided to start a simple project on my own, namely, LaraMod
, for many reasons, but the biggest one is to dig deeper into Laravel 5 and PHP.
Here I only emphasize some major aspects of LaraMod. The rest, including code and extra improvements can be found at LaraMod’s repository.
Update 1 @ 2018-04-25: LaraMod
has been revised and updated to work with the most recent version of Laravel framework, 5.6.17
. Instead of a standalone git repos, LaraMod is from now on a fork of Laravel repos enhanced with better modularisation.
Update 2 @ 2018-11-30: LaraMod
has been upgraded and merged with Laravel master
in which the stable release version is 5.7.15
.
Modularising Laravel 5
A Simple Submodule Structure
Assuming that I want to divide my project into submodules of which each comprises own controllers, views, models (MVC) and others such as database migration, i18n, and routes. A simple structure of the submodule Authentication
is shown as following.
Authentication
├──Controllers
├──Lang
├──Migrations
├──Models
├──View
└──routes.php
Such a structure can be very useful for different teams/people working independently. In LaraMod, there is an artisan
command gen:module
provided in App\Console\Commands\GenModuleCommand
that can quickly create a submodule following that structure.
$ php artisan gen:module Authentication
How to Proceed
An easy approach is to rely on Laravel’s ServiceProvider
to load the submodules and register the necessary components of the submodules.
<?php
class XYZServiceProvider extends \Illuminate\Support\ServiceProvider {
public function boot() {}
public function register() {}
}
A ServiceProvider
contains two important methods register()
and boot()
. In this case, LaraMod mainly uses the method boot()
to load the corresponding submodule. LaraMod also adopts a simple, conventional method for defining a submodule. That is, all submodules will be subfolders of the folder /modules
and each module follows the conventional structure as mentioned above.
Automatically Loading Submodules
Loading a Submodule
First, we create a method loadModule()
to load the resources such as views, i18n, database migration, and routes of a submodule using the provided methods loadViewsFrom()
, loadTranslationsFrom()
, loadMigrationsFrom()
, loadRoutesFrom()
, respectively. As models and controllers are essential PHP classes, they can be loaded using PSR-4 autoloaders as shown in the next parts.
<?php
protected function loadModule($module)
{
if ($module) {
// load the submodule's routes
if (file_exists(__DIR__ . '/' . $module . '/routes.php')) {
$this->loadRoutesFrom(__DIR__ . '/' . $module . '/routes.php');
}
// load the submodule's views
if (is_dir(__DIR__ . '/' . $module . '/Views')) {
$this->loadViewsFrom(__DIR__ . '/' . $module . '/Views', $module);
}
// load the submodule's translation
if (is_dir(__DIR__ . '/' . $module . '/Lang')) {
$this->loadTranslationsFrom(__DIR__ . '/' . $module . '/Lang', $module);
}
// load the submodule's database migrations
if (is_dir(__DIR__ . '/' . $module . '/Migrations')) {
$this->loadMigrationsFrom(__DIR__ . '/' . $module . '/Migrations', $module);
}
}
}
Detecting and Loading Submodules
Then we can walk through the folder module
and load all submodules using the method loadModule()
created above.
<?php
protected function findAndLoadModules()
{
$modules = config("module.modules");
if (!$modules) {
$modules = $this->getModuleNamesFromCurrentPath();
}
if ($modules) {
array_walk($modules, function ($module) {
$this->loadModule($module);
});
}
}
Note that in this conventional approach, findAndLoadModules()
will look for submodule configurations either (1) explicitly in the file /config/module.php
with the following syntax or (2) implicitly as subfolders of /modules
(in case /config/module.php
does not exist).
// module.php
<?php
return [
'modules' => [
'Core',
'Login',
'Dashboard',
'Home',
'User',
]
];
Finally we invoke findAndLoadModules()
within boot()
.
<?php
namespace Laramod;
class ModulesServiceProvider extends \Illuminate\Support\ServiceProvider
{
public function boot()
{
$this->findAndLoadModules();
}
public function register()
{
}
}
Registering the Service Provider
The ModulesServiceProvider
must be registered in /config/app.php
in order to be loaded by Laravel.
<?php
return [
...
'providers' => [
...
\Laramod\ModulesServiceProvider::class,
],
...
];
Autoloading Submodules’ Classes
Apart from resources like views, i18n, database migration loaded in the previous steps, a submodule can also contain other PHP classes such as database ORM models and controllers.
One natural approach would be to leverage the PSR-4 autoloader autoloading supported by Laravel 5.5. This approach also nicely fits when we want to define separate namespaces for each submodule and especially submodule’s components.
For this, we can define the submodule’s namespace and manually specify the submodule’s classpaths to be loaded in composer.json
as following.
{
"psr-4": {
"App\\": "app/",
"Core\\": "modules/Core/",
"Dashboard\\": "modules/Dashboard/",
"Login\\": "modules/Login/",
"User\\": "modules/User/"
}
}
Another technique for autoloading classes in Laravel 5 is to dynamically add PSR4 classpaths in ModulesServiceProvider.php
.
// ModulesServiceProvider.php
<?php
...
$loader = require base_path() . '/vendor/autoload.php';
$loader->setPsr4("Core\\", __DIR__ . "./Core/");
$loader->setPsr4("Dashboard\\", __DIR__ . "./Dashboard/");
$loader->setPsr4("Login\\", __DIR__ . "./Login/");
$loader->setPsr4("User\\", __DIR__ . "./User/");
One problem, though, is that LaraMod must be able to detect or guess the namespace classpaths. To do this, we can either use the configuration file config/module.php
or, even nicer and smarter, scan the submodule’s folders to extract the necessary information. However, loading time then will be really significant when the project grows because Laravel loaders willl do the scanning and loading whenever any classes are referenced.
Accessing Submodule Resources
Given a submodule, for instance SubmoduleA
, most of its classes such as controllers and database mapping models, if configuring properly using PSR-4, will be automatically loaded and used straightforwardly. Nonetheless, acessing to resources such as views and i18n (languages) requires a slightly different syntax, for instance, SubmoduleA::blade_view_name
or SubmoduleA::messages.error
. If the SubmoduleA::
is missing, Laravel will look for views and language files in the default places.
Finale
The outcome of my very first PHP project, LaraMod, would be a reasonable skeleton for modularising software projects based on Laravel 5. That is, you can just easily create a new submodule with the predefined conventional structure and add the PSR-4 namespaces and classpaths, then good you go. Each submodule should then be developed independently. Sure there are some areas that need improvement. For example, all testing stuffs are still under /tests
and database seeders are still in database/seeds
. The first one requires intervention with PHPUnit/phpunit.xml
whilst the later asks for further changes in built-in Laravel artisan
commands.
For me—a new bie, it was not quite an all-pleasant journey to experience several Laravel aspects. Nevertheless, developing and customising LaraMod are extremely valuable as I could learn a lot about not only Laravel in particular but also PHP in general. Achieving reasonable modularisation for a complex software development project requires a lot of thoughtful design and hard work and even sometimes trade-offs and compromises. In sharing this, I hope LaraMod might become handy and helpful start so that your journeys with Laravel can be more pleasant and joyful. I look forward to hearing and learning from your experience, too.