--- title: Modules description: Creating and organising modules in Core PHP Framework applications updated: 2026-01-29 --- # Modules Modules are the building blocks of Core PHP Framework applications. Each module is a self-contained feature that registers itself via lifecycle events. ## Module Structure A typical module follows this structure: ``` app/Mod/Blog/ ├── Boot.php # Entry point - event listeners ├── Models/ │ └── Post.php # Eloquent models ├── Routes/ │ ├── web.php # Public routes │ └── api.php # API routes ├── Views/ │ ├── index.blade.php │ └── posts/ │ └── show.blade.php ├── Livewire/ │ ├── PostListPage.php │ └── PostShowPage.php ├── Actions/ │ └── CreatePost.php # Business logic ├── Services/ │ └── PostService.php ├── Migrations/ │ └── 2025_01_01_create_posts_table.php └── Tests/ ├── PostTest.php └── CreatePostTest.php ``` ## The Boot Class Every module requires a `Boot.php` file that declares its event listeners: ```php 'onWebRoutes', ApiRoutesRegistering::class => 'onApiRoutes', AdminPanelBooting::class => ['onAdminPanel', 10], // With priority ConsoleBooting::class => 'onConsole', ]; public function onWebRoutes(WebRoutesRegistering $event): void { // Register routes $event->routes(fn() => require __DIR__.'/Routes/web.php'); // Register view namespace (accessed as 'blog::view.name') $event->views('blog', __DIR__.'/Views'); } public function onApiRoutes(ApiRoutesRegistering $event): void { $event->routes(fn() => require __DIR__.'/Routes/api.php'); } public function onAdminPanel(AdminPanelBooting $event): void { // Register navigation item $event->navigation('Blog', 'blog.admin.index', 'newspaper'); // Register admin resources $event->resource('posts', PostResource::class); } public function onConsole(ConsoleBooting $event): void { // Register artisan commands $event->commands([ ImportPostsCommand::class, PublishScheduledPostsCommand::class, ]); // Register scheduled tasks $event->schedule(function ($schedule) { $schedule->command('blog:publish-scheduled')->hourly(); }); } } ``` ## Lifecycle Events ### WebRoutesRegistering Fired when web routes are being registered. Use for public-facing routes. ```php public function onWebRoutes(WebRoutesRegistering $event): void { // Register route file $event->routes(fn() => require __DIR__.'/Routes/web.php'); // Register view namespace $event->views('blog', __DIR__.'/Views'); // Register Blade components $event->components('blog', __DIR__.'/Views/Components'); // Register middleware $event->middleware('blog.auth', BlogAuthMiddleware::class); } ``` ### ApiRoutesRegistering Fired when API routes are being registered. Routes are automatically prefixed with `/api`. ```php public function onApiRoutes(ApiRoutesRegistering $event): void { $event->routes(fn() => require __DIR__.'/Routes/api.php'); // Register API resources $event->resource('posts', PostApiResource::class); } ``` ### AdminPanelBooting Fired when the admin panel is being set up (requires `core-admin` package). ```php public function onAdminPanel(AdminPanelBooting $event): void { // Navigation item with icon $event->navigation('Blog', 'blog.admin.index', 'newspaper'); // Navigation group with sub-items $event->navigationGroup('Blog', [ ['Posts', 'blog.admin.posts', 'file-text'], ['Categories', 'blog.admin.categories', 'folder'], ['Tags', 'blog.admin.tags', 'tag'], ], 'newspaper'); // Register admin resource $event->resource('posts', PostResource::class); // Register widget for dashboard $event->widget(RecentPostsWidget::class); } ``` ### ClientRoutesRegistering Fired for authenticated SaaS routes (dashboard, settings, etc.). ```php public function onClientRoutes(ClientRoutesRegistering $event): void { $event->routes(fn() => require __DIR__.'/Routes/client.php'); } ``` ### ConsoleBooting Fired when Artisan is bootstrapping. ```php public function onConsole(ConsoleBooting $event): void { // Register commands $event->commands([ ImportPostsCommand::class, ]); // Register scheduled tasks $event->schedule(function ($schedule) { $schedule->command('blog:publish-scheduled') ->hourly() ->withoutOverlapping(); }); } ``` ### McpToolsRegistering Fired when the MCP server is being set up (requires `core-mcp` package). ```php public function onMcpTools(McpToolsRegistering $event): void { $event->tool('create_post', CreatePostTool::class); $event->tool('list_posts', ListPostsTool::class); } ``` ## Event Priorities You can specify a priority for event listeners. Higher numbers execute first: ```php public static array $listens = [ AdminPanelBooting::class => ['onAdminPanel', 100], // High priority WebRoutesRegistering::class => ['onWebRoutes', 10], // Normal priority ]; ``` Priorities are useful when: - Your module needs to register before/after other modules - You need to override routes from other modules - You need to modify admin navigation order ## View Namespacing Views are namespaced by the identifier you provide: ```php $event->views('blog', __DIR__.'/Views'); ``` Access views using the namespace prefix: ```blade {{-- In controllers/components --}} return view('blog::posts.index'); {{-- In Blade templates --}} @include('blog::partials.sidebar') @extends('blog::layouts.main') ``` ## Route Files ### Web Routes (`Routes/web.php`) ```php name('blog.')->group(function () { Route::get('/', PostListPage::class)->name('index'); Route::get('/{post:slug}', PostShowPage::class)->name('show'); }); // With middleware Route::middleware(['auth'])->prefix('blog')->name('blog.')->group(function () { Route::get('/my-posts', MyPostsPage::class)->name('my-posts'); }); ``` ### API Routes (`Routes/api.php`) ```php name('api.blog.')->group(function () { Route::apiResource('posts', PostController::class); }); ``` ## Actions Pattern For complex business logic, use the Actions pattern: ```php $data['title'], 'slug' => Str::slug($data['title']), 'content' => $data['content'], 'published_at' => $data['publish_now'] ? now() : null, ]); } } ``` Usage: ```php $post = CreatePost::run([ 'title' => 'My Post', 'content' => 'Content here...', 'publish_now' => true, ]); ``` ## Module Discovery Modules are discovered automatically from paths configured in `config/core.php`: ```php 'module_paths' => [ app_path('Core'), // Framework overrides app_path('Mod'), // Application modules app_path('Website'), // Website-specific modules ], ``` ### Caching In production, module discovery is cached. Clear the cache when adding new modules: ```bash php artisan cache:clear ``` Or disable caching during development: ```env CORE_CACHE_DISCOVERY=false ``` ## Creating Modules with Artisan The `make:mod` command scaffolds a new module: ```bash # Full module with all features php artisan make:mod Blog --all # Web routes only php artisan make:mod Blog --web # API routes only php artisan make:mod Blog --api # Admin panel integration php artisan make:mod Blog --admin # Combination php artisan make:mod Blog --web --api --admin ``` ## Module Dependencies If your module depends on another module, check for its presence: ```php public function onWebRoutes(WebRoutesRegistering $event): void { // Check if core-tenant is available if (!class_exists(\Core\Tenant\Models\Workspace::class)) { return; } $event->routes(fn() => require __DIR__.'/Routes/web.php'); } ``` ## Best Practices ### Keep Modules Focused Each module should represent a single feature or domain: - `Blog` - Blog posts, categories, tags - `Shop` - Products, orders, cart - `Newsletter` - Subscribers, campaigns ### Use Clear Naming - Module name: PascalCase singular (`Blog`, not `Blogs`) - Namespace: `App\Mod\{ModuleName}` - View namespace: lowercase (`blog::`, `shop::`) ### Isolate Dependencies Keep inter-module dependencies minimal. If modules need to communicate: 1. Use events (preferred) 2. Use interfaces and dependency injection 3. Use shared services in `app/Services/` ### Test Modules in Isolation Write tests that don't depend on other modules being present: ```php it('creates a blog post', function () { $post = Post::create([ 'title' => 'Test', 'slug' => 'test', 'content' => 'Content', ]); expect($post)->toBeInstanceOf(Post::class); expect($post->title)->toBe('Test'); }); ```