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 Stripe
Manual 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:
Cache 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¶
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);
}