What is a Module?
A module in Laravel Modular is a self-contained package that follows Laravel’s conventions and PSR-4 autoloading standards. Each module:
- Lives in its own directory (default:
app-modules/)
- Has its own
composer.json file
- Uses PSR-4 autoloading
- Can contain any Laravel components (models, controllers, migrations, etc.)
Module Configuration
Each module is represented by a ModuleConfig object that contains metadata:
class ModuleConfig implements Arrayable
{
public function __construct(
public string $name, // Directory name (e.g., 'user-management')
public string $base_path, // Absolute path to module
public Collection $namespaces = new Collection(), // PSR-4 mappings
) {}
}
Creating from composer.json
Modules are automatically discovered by parsing their composer.json files:
public static function fromComposerFile(SplFileInfo $composer_file): self
{
$composer_config = json_decode($composer_file->getContents(), true, 16, JSON_THROW_ON_ERROR);
$base_path = rtrim(str_replace('\\', '/', $composer_file->getPath()), '/');
$name = basename($base_path);
$namespaces = Collection::make($composer_config['autoload']['psr-4'] ?? [])
->mapWithKeys(function($src, $namespace) use ($base_path) {
$path = $base_path.'/'.$src;
return [$path => $namespace];
});
return new static($name, $base_path, $namespaces);
}
The module name is automatically derived from the directory name, not from the composer.json name field.
Module Methods
The ModuleConfig class provides several helper methods:
Path Resolution
// Get absolute path to any file/directory in the module
public function path(string $to = ''): string
{
return rtrim($this->base_path.'/'.$to, '/');
}
// Usage
$module = Modules::module('user-management');
$module->path(); // /path/to/app-modules/user-management
$module->path('src/Models'); // /path/to/app-modules/user-management/src/Models
$module->path('routes/api.php'); // /path/to/app-modules/user-management/routes/api.php
Namespace Helpers
// Get the primary namespace
public function namespace(): string
{
return $this->namespaces->first();
}
// Qualify a class name with the module namespace
public function qualify(string $class_name): string
{
return $this->namespace().ltrim($class_name, '\\');
}
// Usage
$module->namespace(); // Modules\UserManagement\
$module->qualify('Models\User'); // Modules\UserManagement\Models\User
Path to Class Name Conversion
Convert a file path to a fully qualified class name:
public function pathToFullyQualifiedClassName(string $path): string
{
// Handle Windows-style paths
$path = str_replace('\\', '/', $path);
foreach ($this->namespaces as $namespace_path => $namespace) {
if (str_starts_with($path, $namespace_path)) {
$relative_path = Str::after($path, $namespace_path);
return $namespace.$this->formatPathAsNamespace($relative_path);
}
}
throw new RuntimeException("Unable to infer qualified class name for '{$path}'");
}
// Usage
$module->pathToFullyQualifiedClassName('/app-modules/user-management/src/Models/User.php');
// Returns: Modules\UserManagement\Models\User
The path must be absolute and match one of the PSR-4 namespace mappings, or a RuntimeException will be thrown.
Accessing Modules
There are multiple ways to access module information:
Using the Facade
use InterNACHI\Modular\Support\Facades\Modules;
// Get a specific module by name
$module = Modules::module('user-management');
// Get all modules
$modules = Modules::modules();
// Find module for a file path
$module = Modules::moduleForPath('/app-modules/user-management/src/Models/User.php');
// Find module for a class
$module = Modules::moduleForClass('Modules\\UserManagement\\Models\\User');
Using Dependency Injection
use InterNACHI\Modular\Support\ModuleRegistry;
class MyService
{
public function __construct(
protected ModuleRegistry $modules
) {}
public function process()
{
$module = $this->modules->module('user-management');
// ...
}
}
Using the Helper
// Get the modules path
$path = app(ModuleRegistry::class)->getModulesPath();
Module Lookup Methods
By Name
public function module(?string $name = null): ?ModuleConfig
{
// We want to allow for gracefully handling empty/null names
return $name ? $this->modules()->get($name) : null;
}
// Usage
$module = Modules::module('user-management');
$module = Modules::module(null); // Returns null
By Path
public function moduleForPath(string $path): ?ModuleConfig
{
return $this->module($this->extractModuleNameFromPath($path));
}
public function moduleForPathOrFail(string $path): ModuleConfig
{
if ($module = $this->moduleForPath($path)) {
return $module;
}
throw new CannotFindModuleForPathException($path);
}
// Usage
$module = Modules::moduleForPath('/app-modules/user-management/src/Models/User.php');
$module = Modules::moduleForPathOrFail('/invalid/path'); // Throws exception
By Class Name
public function moduleForClass(string $fqcn): ?ModuleConfig
{
return $this->modules()->first(function(ModuleConfig $module) use ($fqcn) {
foreach ($module->namespaces as $namespace) {
if (Str::startsWith($fqcn, $namespace)) {
return true;
}
}
return false;
});
}
// Usage
$module = Modules::moduleForClass('Modules\\UserManagement\\Models\\User');
Module Collection
All modules are stored in a Laravel Collection:
public function modules(): Collection
{
return $this->modules ??= call_user_func($this->modules_loader);
}
Iterating Over Modules
Modules::modules()->each(function(ModuleConfig $module) {
echo "Module: {$module->name}\n";
echo "Path: {$module->base_path}\n";
echo "Namespace: {$module->namespace()}\n";
});
Filtering Modules
// Find all modules with a specific namespace prefix
$adminModules = Modules::modules()->filter(function(ModuleConfig $module) {
return Str::startsWith($module->namespace(), 'Modules\\Admin\\');
});
// Get module names
$names = Modules::modules()->pluck('name');
Reloading Modules
Force a reload from disk (useful in development):
public function reload(): Collection
{
$this->modules = null;
return $this->modules();
}
// Usage
Modules::reload();
Reloading modules will bypass the cache and scan the filesystem. Use sparingly in production.
Module as Array
Modules can be serialized to arrays:
public function toArray(): array
{
return [
'name' => $this->name,
'base_path' => $this->base_path,
'namespaces' => $this->namespaces->toArray(),
];
}
// Usage
$data = $module->toArray();
/*
[
'name' => 'user-management',
'base_path' => '/path/to/app-modules/user-management',
'namespaces' => [
'/path/to/app-modules/user-management/src/' => 'Modules\\UserManagement\\',
'/path/to/app-modules/user-management/tests/' => 'Modules\\UserManagement\\Tests\\',
]
]
*/
Laravel Modular handles Windows and Unix paths correctly:
protected function extractModuleNameFromPath(string $path): string
{
// Handle Windows-style paths
$path = str_replace('\\', '/', $path);
// If the modules directory is symlinked, we may get two paths that are actually
// in the same directory, but have different prefixes. This helps resolve that.
if (Str::startsWith($path, $this->modules_path)) {
$path = trim(Str::after($path, $this->modules_path), '/');
} elseif (Str::startsWith($path, $modules_real_path = str_replace('\\', '/', realpath($this->modules_path)))) {
$path = trim(Str::after($path, $modules_real_path), '/');
}
return explode('/', $path)[0];
}
The framework automatically resolves symlinks and handles both forward and backward slashes.
Practical Examples
use InterNACHI\Modular\Support\Facades\Modules;
class ListModulesCommand extends Command
{
public function handle()
{
Modules::modules()->each(function($module) {
$this->info("Module: {$module->name}");
$this->line(" Path: {$module->base_path}");
$this->line(" Namespace: {$module->namespace()}");
});
}
}
Find Module for Current Class
class MyController extends Controller
{
public function index()
{
$module = Modules::moduleForClass(static::class);
if ($module) {
$viewPath = $module->path('resources/views');
// Use module-specific views
}
}
}
Load Module-Specific Configuration
foreach (Modules::modules() as $module) {
$configPath = $module->path('config/module.php');
if (file_exists($configPath)) {
config([$module->name => require $configPath]);
}
}