Module System
The module system provides automatic discovery and lazy loading of modules based on lifecycle events. Modules are self-contained units of functionality that can hook into the framework at specific points.
Overview
Traditional Laravel applications use service providers which are all loaded on every request. The Core module system:
- Auto-discovers modules by scanning directories
- Lazy-loads modules only when their events fire
- Caches module registry for performance
- Supports multiple module types (Mod, Plug, Website)
Creating a Module
Using Artisan
# Create a standard module
php artisan make:mod Blog
# Create a website module
php artisan make:website Marketing
# Create a plugin module
php artisan make:plug StripeManual Creation
Create a Boot.php file in your module directory:
<?php
namespace Mod\Blog;
use Core\Events\WebRoutesRegistering;
use Core\Events\AdminPanelBooting;
use Core\Events\ConsoleBooting;
class Boot
{
/**
* Events this module listens to
*/
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
AdminPanelBooting::class => 'onAdminPanel',
ConsoleBooting::class => 'onConsole',
];
/**
* Register public web routes
*/
public function onWebRoutes(WebRoutesRegistering $event): void
{
$event->views('blog', __DIR__.'/Views');
$event->routes(fn () => require __DIR__.'/Routes/web.php');
}
/**
* Register admin panel routes and menus
*/
public function onAdminPanel(AdminPanelBooting $event): void
{
$event->menu('blog', [
'label' => 'Blog',
'icon' => 'newspaper',
'route' => 'admin.blog.index',
'order' => 20,
]);
$event->routes(fn () => require __DIR__.'/Routes/admin.php');
}
/**
* Register console commands
*/
public function onConsole(ConsoleBooting $event): void
{
$event->commands([
Commands\PublishPostsCommand::class,
Commands\ImportPostsCommand::class,
]);
}
}Directory Structure
Mod/Blog/
├── Boot.php # Module bootstrap
├── Actions/ # Business logic
│ ├── CreatePost.php
│ ├── UpdatePost.php
│ └── DeletePost.php
├── Controllers/
│ ├── Web/
│ │ └── PostController.php
│ └── Admin/
│ └── PostController.php
├── Models/
│ ├── Post.php
│ └── Category.php
├── Routes/
│ ├── web.php
│ ├── admin.php
│ └── api.php
├── Views/
│ ├── web/
│ └── admin/
├── Database/
│ ├── Migrations/
│ ├── Factories/
│ └── Seeders/
├── Tests/
│ ├── Feature/
│ └── Unit/
└── Lang/
└── en_GB/Lifecycle Events
Modules can hook into these lifecycle events:
WebRoutesRegistering
Register public-facing web routes:
public function onWebRoutes(WebRoutesRegistering $event): void
{
// Register views
$event->views('blog', __DIR__.'/Views');
// Register translations
$event->lang('blog', __DIR__.'/Lang');
// Register routes
$event->routes(function () {
Route::get('/blog', [PostController::class, 'index']);
Route::get('/blog/{slug}', [PostController::class, 'show']);
});
}AdminPanelBooting
Register admin panel routes, menus, and widgets:
public function onAdminPanel(AdminPanelBooting $event): void
{
// Register admin menu
$event->menu('blog', [
'label' => 'Blog',
'icon' => 'newspaper',
'route' => 'admin.blog.index',
'order' => 20,
'children' => [
['label' => 'Posts', 'route' => 'admin.blog.posts'],
['label' => 'Categories', 'route' => 'admin.blog.categories'],
],
]);
// Register routes
$event->routes(fn () => require __DIR__.'/Routes/admin.php');
}ApiRoutesRegistering
Register REST API endpoints:
public function onApiRoutes(ApiRoutesRegistering $event): void
{
$event->routes(function () {
Route::get('/posts', [Api\PostController::class, 'index']);
Route::post('/posts', [Api\PostController::class, 'store']);
Route::get('/posts/{id}', [Api\PostController::class, 'show']);
});
}ClientRoutesRegistering
Register authenticated client routes:
public function onClientRoutes(ClientRoutesRegistering $event): void
{
$event->routes(function () {
Route::get('/dashboard/posts', [Client\PostController::class, 'index']);
Route::post('/dashboard/posts', [Client\PostController::class, 'store']);
});
}ConsoleBooting
Register Artisan commands:
public function onConsole(ConsoleBooting $event): void
{
$event->commands([
Commands\PublishPostsCommand::class,
Commands\GenerateSitemapCommand::class,
]);
$event->schedule(function (Schedule $schedule) {
$schedule->command('blog:publish-scheduled')
->everyFiveMinutes();
});
}McpToolsRegistering
Register MCP (Model Context Protocol) tools:
public function onMcpTools(McpToolsRegistering $event): void
{
$event->tool('blog:create-post', Tools\CreatePostTool::class);
$event->tool('blog:list-posts', Tools\ListPostsTool::class);
}FrameworkBooted
Late-stage initialization after all modules loaded:
public function onFrameworkBooted(FrameworkBooted $event): void
{
// Register macros, observers, policies, etc.
Post::observe(PostObserver::class);
Builder::macro('published', function () {
return $this->where('status', 'published')
->where('published_at', '<=', now());
});
}Module Discovery
The framework automatically scans these directories:
// config/core.php
'module_paths' => [
app_path('Core'), // Core modules
app_path('Mod'), // Standard modules
app_path('Website'), // Website modules
app_path('Plug'), // Plugin modules
],Custom Namespaces
Map custom paths to namespaces:
use Core\Module\ModuleScanner;
$scanner = app(ModuleScanner::class);
$scanner->setNamespaceMap([
'/Extensions' => 'Extensions\\',
'/Custom' => 'Custom\\Modules\\',
]);Lazy Loading
Modules are only instantiated when their events fire:
- Scan Phase -
ModuleScannerfinds allBoot.phpfiles - Registry Phase -
ModuleRegistrywires lazy listeners - Event Phase - Event fires,
LazyModuleListenerinstantiates module - Execution Phase - Module method is called
Performance Benefits:
- Modules not used in CLI don't load in CLI
- Admin modules don't load on public requests
- API modules don't load on web requests
Module Registry
View registered modules and their listeners:
use Core\Module\ModuleRegistry;
$registry = app(ModuleRegistry::class);
// Get all registered modules
$modules = $registry->all();
// Get modules for specific event
$webModules = $registry->forEvent(WebRoutesRegistering::class);Module Cache
Module discovery is cached for performance:
# Clear module cache
php artisan cache:clear
# Or specifically
php artisan optimize:clearCache Location: bootstrap/cache/modules.php
Module Dependencies
Modules can declare dependencies using service discovery:
use Core\Service\Contracts\ServiceDefinition;
use Core\Service\Contracts\ServiceDependency;
class Boot implements ServiceDefinition
{
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
];
public function getServiceName(): string
{
return 'blog';
}
public function getServiceVersion(): string
{
return '1.0.0';
}
public function getDependencies(): array
{
return [
new ServiceDependency('media', '>=1.0'),
new ServiceDependency('cdn', '>=2.0'),
];
}
}Testing Modules
Feature Tests
<?php
namespace Mod\Blog\Tests\Feature;
use Tests\TestCase;
use Mod\Blog\Actions\CreatePost;
class PostCreationTest extends TestCase
{
public function test_creates_post(): void
{
$post = CreatePost::run([
'title' => 'Test Post',
'content' => 'Content here',
]);
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
]);
$this->get("/blog/{$post->slug}")
->assertOk()
->assertSee('Test Post');
}
}Unit Tests
<?php
namespace Mod\Blog\Tests\Unit;
use Tests\TestCase;
use Mod\Blog\Boot;
use Core\Events\WebRoutesRegistering;
class BootTest extends TestCase
{
public function test_registers_web_routes(): void
{
$event = new WebRoutesRegistering();
$boot = new Boot();
$boot->onWebRoutes($event);
$this->assertTrue($event->hasRoutes());
}
}Best Practices
1. Keep Modules Focused
// ✅ Good - focused modules
Mod/Blog/
Mod/Comments/
Mod/Analytics/
// ❌ Bad - monolithic module
Mod/Everything/2. Use Proper Namespacing
// ✅ Good
namespace Mod\Blog\Controllers\Web;
// ❌ Bad
namespace App\Http\Controllers;3. Register Dependencies
// ✅ Good - declare dependencies
public function getDependencies(): array
{
return [
new ServiceDependency('media', '>=1.0'),
];
}4. Only Hook Necessary Events
// ✅ Good - only web routes
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
];
// ❌ Bad - hooks everything
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
AdminPanelBooting::class => 'onAdminPanel',
ApiRoutesRegistering::class => 'onApiRoutes',
// ... (when you don't need them all)
];5. Use Actions for Business Logic
// ✅ Good
$post = CreatePost::run($data);
// ❌ Bad - logic in controller
public function store(Request $request)
{
$post = Post::create($request->all());
event(new PostCreated($post));
Cache::forget('posts');
return redirect()->route('posts.show', $post);
}