Lifecycle Events
Core PHP Framework uses an event-driven architecture where modules declare interest in lifecycle events. This enables lazy loading and modular composition without tight coupling.
Overview
The lifecycle event system provides extension points throughout the framework's boot process. Modules register listeners for specific events, and are only instantiated when those events fire.
Application Boot
↓
LifecycleEventProvider fires events
↓
LazyModuleListener intercepts events
↓
Module instantiated on-demand
↓
Event handler executes
↓
Module collects requests (routes, menus, etc.)
↓
LifecycleEventProvider processes requestsCore Events
WebRoutesRegistering
Fired during: Web route registration (early boot)
Purpose: Register public-facing web routes and views
Use cases:
- Marketing pages
- Public blog
- Documentation site
- Landing pages
Example:
public function onWebRoutes(WebRoutesRegistering $event): void
{
// Register view namespace
$event->views('marketing', __DIR__.'/Views');
// Register routes
$event->routes(function () {
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/pricing', [PricingController::class, 'index'])->name('pricing');
Route::get('/contact', [ContactController::class, 'index'])->name('contact');
});
// Register middleware
$event->middleware(['web', 'track-visitor']);
}Available Methods:
views(string $namespace, string $path)- Register view namespaceroutes(Closure $callback)- Register routesmiddleware(array $middleware)- Apply middleware to routes
AdminPanelBooting
Fired during: Admin panel initialization
Purpose: Register admin routes, menus, and dashboard widgets
Use cases:
- Admin CRUD interfaces
- Dashboard widgets
- Settings pages
- Admin navigation
Example:
public function onAdmin(AdminPanelBooting $event): void
{
// Register admin routes
$event->routes(fn () => require __DIR__.'/Routes/admin.php');
// Register admin menu
$event->menu(new BlogMenuProvider());
// Register dashboard widget
$event->widget(new PostStatsWidget());
// Register settings page
$event->settings('blog', BlogSettingsPage::class);
}Available Methods:
routes(Closure $callback)- Register admin routesmenu(AdminMenuProvider $provider)- Register menu itemswidget(DashboardWidget $widget)- Register dashboard widgetsettings(string $key, string $class)- Register settings page
ApiRoutesRegistering
Fired during: API route registration
Purpose: Register REST API endpoints
Use cases:
- RESTful APIs
- Webhooks
- Third-party integrations
- Mobile app backends
Example:
public function onApiRoutes(ApiRoutesRegistering $event): void
{
$event->routes(function () {
Route::prefix('v1')->group(function () {
Route::apiResource('posts', PostApiController::class);
Route::get('posts/{post}/analytics', [PostApiController::class, 'analytics']);
});
});
// API-specific middleware
$event->middleware(['api', 'auth:sanctum', 'scope:blog:read']);
}Available Methods:
routes(Closure $callback)- Register API routesmiddleware(array $middleware)- Apply middlewareversion(string $version)- Set API version prefix
ClientRoutesRegistering
Fired during: Client route registration
Purpose: Register authenticated client/dashboard routes
Use cases:
- User dashboards
- Account settings
- Client portals
- Authenticated SPA routes
Example:
public function onClientRoutes(ClientRoutesRegistering $event): void
{
$event->views('dashboard', __DIR__.'/Views/Client');
$event->routes(function () {
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/account', [AccountController::class, 'show'])->name('account');
Route::post('/account', [AccountController::class, 'update']);
});
});
}Available Methods:
views(string $namespace, string $path)- Register view namespaceroutes(Closure $callback)- Register routesmiddleware(array $middleware)- Apply middleware
ConsoleBooting
Fired during: Console kernel initialization
Purpose: Register Artisan commands
Use cases:
- Custom commands
- Scheduled tasks
- Maintenance scripts
- Data migrations
Example:
public function onConsole(ConsoleBooting $event): void
{
// Register commands
$event->commands([
PublishPostCommand::class,
ImportPostsCommand::class,
GenerateSitemapCommand::class,
]);
// Register scheduled tasks
$event->schedule(function (Schedule $schedule) {
$schedule->command(PublishScheduledPostsCommand::class)
->hourly()
->withoutOverlapping();
$schedule->command(GenerateSitemapCommand::class)
->daily()
->at('01:00');
});
}Available Methods:
commands(array $commands)- Register commandsschedule(Closure $callback)- Define scheduled tasks
McpToolsRegistering
Fired during: MCP server initialization
Purpose: Register MCP (Model Context Protocol) tools for AI integrations
Use cases:
- AI-powered features
- LLM tool integrations
- Automated workflows
- AI assistants
Example:
public function onMcpTools(McpToolsRegistering $event): void
{
$event->tools([
GetPostTool::class,
CreatePostTool::class,
UpdatePostTool::class,
SearchPostsTool::class,
]);
// Register prompts
$event->prompts([
GenerateBlogPostPrompt::class,
]);
// Register resources
$event->resources([
BlogPostResource::class,
]);
}Available Methods:
tools(array $tools)- Register MCP toolsprompts(array $prompts)- Register prompt templatesresources(array $resources)- Register resources
FrameworkBooted
Fired after: All other lifecycle events have completed
Purpose: Late-stage initialization and cross-module setup
Use cases:
- Service registration
- Event listeners
- Observer registration
- Cache warming
Example:
public function onFrameworkBooted(FrameworkBooted $event): void
{
// Register event listeners
Event::listen(PostPublished::class, SendPostNotification::class);
Event::listen(PostViewed::class, IncrementViewCount::class);
// Register model observers
Post::observe(PostObserver::class);
// Register service
app()->singleton(BlogService::class, function ($app) {
return new BlogService(
$app->make(PostRepository::class),
$app->make(CategoryRepository::class)
);
});
// Register policies
Gate::policy(Post::class, PostPolicy::class);
}Available Methods:
service(string $abstract, Closure $factory)- Register servicesingleton(string $abstract, Closure $factory)- Register singletonlistener(string $event, string $listener)- Register event listener
Event Declaration
Modules declare event listeners via the $listens property in Boot.php:
<?php
namespace Mod\Blog;
use Core\Events\WebRoutesRegistering;
use Core\Events\AdminPanelBooting;
use Core\Events\ApiRoutesRegistering;
class Boot
{
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
AdminPanelBooting::class => 'onAdmin',
ApiRoutesRegistering::class => 'onApiRoutes',
];
public function onWebRoutes(WebRoutesRegistering $event): void { }
public function onAdmin(AdminPanelBooting $event): void { }
public function onApiRoutes(ApiRoutesRegistering $event): void { }
}Lazy Loading
Modules are not instantiated until an event they listen to is fired:
// Web request → Only WebRoutesRegistering listeners loaded
// API request → Only ApiRoutesRegistering listeners loaded
// Admin request → Only AdminPanelBooting listeners loaded
// Console command → Only ConsoleBooting listeners loadedThis dramatically reduces bootstrap time and memory usage.
Event Flow
1. Module Discovery
ModuleScanner scans configured paths for Boot.php files:
$scanner = new ModuleScanner();
$modules = $scanner->scan([
app_path('Core'),
app_path('Mod'),
app_path('Plug'),
]);2. Listener Registration
ModuleRegistry wires lazy listeners:
$registry = new ModuleRegistry();
$registry->registerModules($modules);
// Creates LazyModuleListener for each event-module pair
Event::listen(WebRoutesRegistering::class, LazyModuleListener::class);3. Event Firing
LifecycleEventProvider fires events at appropriate times:
// During route registration
$event = new WebRoutesRegistering();
event($event);4. Module Loading
LazyModuleListener instantiates module on-demand:
public function handle($event): void
{
$module = new $this->moduleClass(); // Module instantiated HERE
$module->{$this->method}($event);
}5. Request Collection
Modules collect requests during event handling:
public function onWebRoutes(WebRoutesRegistering $event): void
{
// Stored in $event->routeRequests
$event->routes(fn () => require __DIR__.'/Routes/web.php');
// Stored in $event->viewRequests
$event->views('blog', __DIR__.'/Views');
}6. Request Processing
LifecycleEventProvider processes collected requests:
foreach ($event->routeRequests as $request) {
Route::middleware($request['middleware'])
->group($request['callback']);
}Custom Lifecycle Events
You can create custom lifecycle events by extending LifecycleEvent:
<?php
namespace Mod\Commerce\Events;
use Core\Events\LifecycleEvent;
class PaymentProvidersRegistering extends LifecycleEvent
{
protected array $providers = [];
public function provider(string $name, string $class): void
{
$this->providers[$name] = $class;
}
public function getProviders(): array
{
return $this->providers;
}
}Fire the event in your service provider:
$event = new PaymentProvidersRegistering();
event($event);
foreach ($event->getProviders() as $name => $class) {
PaymentGateway::register($name, $class);
}Modules can listen to your custom event:
public static array $listens = [
PaymentProvidersRegistering::class => 'onPaymentProviders',
];
public function onPaymentProviders(PaymentProvidersRegistering $event): void
{
$event->provider('stripe', StripeProvider::class);
}Event Priorities
Control event listener execution order:
Event::listen(WebRoutesRegistering::class, FirstModule::class, 100);
Event::listen(WebRoutesRegistering::class, SecondModule::class, 50);
Event::listen(WebRoutesRegistering::class, ThirdModule::class, 10);
// Execution order: FirstModule → SecondModule → ThirdModuleTesting Lifecycle Events
Test that modules respond to events correctly:
<?php
namespace Tests\Feature\Mod\Blog;
use Tests\TestCase;
use Core\Events\WebRoutesRegistering;
use Mod\Blog\Boot;
class BlogBootTest extends TestCase
{
public function test_registers_web_routes(): void
{
$event = new WebRoutesRegistering();
$boot = new Boot();
$boot->onWebRoutes($event);
$this->assertNotEmpty($event->routeRequests);
$this->assertNotEmpty($event->viewRequests);
}
public function test_registers_admin_menu(): void
{
$event = new AdminPanelBooting();
$boot = new Boot();
$boot->onAdmin($event);
$this->assertNotEmpty($event->menuProviders);
}
}Best Practices
1. Keep Event Handlers Focused
Each event handler should only register resources related to that lifecycle phase:
// ✅ Good
public function onWebRoutes(WebRoutesRegistering $event): void
{
$event->views('blog', __DIR__.'/Views');
$event->routes(fn () => require __DIR__.'/Routes/web.php');
}
// ❌ Bad - service registration belongs in FrameworkBooted
public function onWebRoutes(WebRoutesRegistering $event): void
{
app()->singleton(BlogService::class, ...);
$event->routes(fn () => require __DIR__.'/Routes/web.php');
}2. Use Dependency Injection
Event handlers receive the event object - use it instead of facades:
// ✅ Good
public function onWebRoutes(WebRoutesRegistering $event): void
{
$event->routes(function () {
Route::get('/blog', ...);
});
}
// ❌ Bad - bypasses event system
public function onWebRoutes(WebRoutesRegistering $event): void
{
Route::get('/blog', ...);
}3. Only Listen to Needed Events
Don't register listeners for events you don't need:
// ✅ Good - API-only module
public static array $listens = [
ApiRoutesRegistering::class => 'onApiRoutes',
];
// ❌ Bad - unnecessary listeners
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
AdminPanelBooting::class => 'onAdmin',
ApiRoutesRegistering::class => 'onApiRoutes',
];4. Keep Boot.php Lightweight
Boot.php should only coordinate - extract complex logic to dedicated classes:
// ✅ Good
public function onAdmin(AdminPanelBooting $event): void
{
$event->menu(new BlogMenuProvider());
$event->routes(fn () => require __DIR__.'/Routes/admin.php');
}
// ❌ Bad - too much inline logic
public function onAdmin(AdminPanelBooting $event): void
{
$event->menu([
'label' => 'Blog',
'icon' => 'newspaper',
'children' => [
// ... 50 lines of menu configuration
],
]);
}