LaraMod - Modularised Laravel 5

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.

Related Articles

comments powered by Disqus