Module System API

Module discovery, registration, lifecycle management, and dependency resolution. The Module System provides a powerful plugin architecture that allows you to extend the framework's functionality through modular components.

Module Attribute

PHP 8 attribute that marks a class as a Forge Engine module. Modules use attributes for configuration rather than extending a base class, providing a clean and modern approach to module definition.

Module Structure

Basic Module Definition

Create a custom module using PHP 8 attributes to mark the class as a Forge Engine module.

<?php

declare(strict_types=1);

namespace App\Modules\Blog;

use Forge\Core\DI\Container;
use Forge\Core\Module\Attributes\Module;
use Forge\Core\Module\Attributes\LifecycleHook;
use Forge\Core\Module\Attributes\Compatibility;
use Forge\Core\Module\Attributes\Provides;
use Forge\Core\Module\Attributes\Requires;
use Forge\Core\Module\Attributes\Repository;
use Forge\Core\Module\LifecycleHookName;

#[
    Module(
        name: "Blog",
        description: "Blog functionality for the application",
        version: "1.0.0",
        order: 100,
        core: false,
        isCli: false
    ),
    Compatibility(framework: ">=0.1.0", php: ">=8.3"),
    Repository(type: "git", url: "https://github.com/forge-engine/modules"),
    Provides(interface: BlogServiceInterface::class, version: "1.0.0")
]
#[Requires]
final class BlogModule
{
    public function register(Container $container): void
    {
        // Register services early in the application lifecycle
        $container->bind(BlogService::class, BlogService::class);
        $container->bind(PostRepository::class, PostRepository::class);
    }
    
    #[LifecycleHook(hook: LifecycleHookName::AFTER_MODULE_REGISTER)]
    public function onAfterModuleRegister(): void
    {
        // Module initialization logic after registration
    }
}

Module Attribute Properties

Module Attribute Parameters

Properties available in the Module attribute constructor.

#[Module(
    name: "Blog",                    // Module identifier (required)
    version: "1.0.0",                // Semantic version (optional)
    description: "Blog functionality", // Human-readable description (optional)
    order: 100,                      // Loading order, higher loads first (optional, default: PHP_INT_MAX)
    core: false,                     // Whether this is a core module (optional, default: false)
    isCli: false                     // Whether module provides CLI commands (optional, default: false)
)]

Additional Module Attributes

Other attributes used to configure modules.

#[Compatibility(framework: ">=0.1.0", php: ">=8.3")]
#[Repository(type: "git", url: "https://github.com/forge-engine/modules")]
#[Provides(interface: BlogServiceInterface::class, version: "1.0.0")]
#[Requires] // Can specify required modules or interfaces

Module Lifecycle Hooks

register(Container $container): void

Register services early in the application lifecycle. This method is called for all modules before any lifecycle hooks.

public function register(Container $container): void
{
    // Register services that other modules might depend on
    $container->bind(BlogService::class, BlogService::class);
    
    // Register configuration
    $container->bind('blog.config', function() {
        return new Config([
            'posts_per_page' => 10,
            'allow_comments' => true,
            'moderation_required' => true,
            'seo_enabled' => true
        ]);
    });
    
    // Register repositories
    $container->bind(PostRepository::class, PostRepository::class);
}

Lifecycle Hook Methods

Use attributes to mark methods that should be called at specific points in the application lifecycle.

use Forge\Core\Module\Attributes\LifecycleHook;
use Forge\Core\Module\LifecycleHookName;

#[LifecycleHook(hook: LifecycleHookName::AFTER_MODULE_REGISTER)]
public function onAfterModuleRegister(): void
{
    // Called after all modules have been registered
    // Safe to interact with other modules here
}

#[LifecycleHook(hook: LifecycleHookName::AFTER_BOOT)]
public function onAfterBoot(): void
{
    // Called after the application has booted
    // All services are available
}

#[LifecycleHook(hook: LifecycleHookName::BEFORE_REQUEST)]
public function onBeforeRequest(): void
{
    // Called before each request is processed
    // Useful for request-specific initialization
}

#[LifecycleHook(hook: LifecycleHookName::AFTER_REQUEST)]
public function onAfterRequest(): void
{
    // Called after each request is processed
    // Useful for cleanup or logging
}

Available Lifecycle Hooks

All available lifecycle hook names from the LifecycleHookName enum.

enum LifecycleHookName: string
{
    case AFTER_BOOT = 'afterBoot';                    // After application boot
    case AFTER_MODULE_LOAD = 'afterModuleLoad';      // After modules are loaded
    case AFTER_MODULE_REGISTER = 'afterModuleRegister'; // After all modules registered
    case AFTER_CONFIG_LOADED = 'afterConfigLoaded';  // After configuration loaded
    case APP_BOOTED = 'appBooted';                    // After application fully booted
    case BEFORE_REQUEST = 'beforeRequest';          // Before each request
    case AFTER_REQUEST = 'afterRequest';             // After each request
    case AFTER_RESPONSE = 'afterResponse';           // After response sent
}

Module Attributes Reference

Complete reference of all available PHP 8 attributes for defining and configuring modules in Forge Engine. These attributes provide a declarative way to configure module metadata, dependencies, compatibility, and behavior.

Core Module Attributes

#[Module] - Primary Module Definition

Required attribute that marks a class as a Forge Engine module and defines its basic properties.

use Forge\Core\Module\Attributes\Module;

#[Module(
    name: "Blog",                    // Required: Module identifier
    version: "1.0.0",                // Optional: Semantic version
    description: "Blog functionality", // Optional: Human-readable description
    order: 100,                      // Optional: Loading order (higher loads first)
    core: false,                     // Optional: Whether this is a core module
    isCli: false                     // Optional: Whether module provides CLI commands
)]
final class BlogModule
{
    // Module implementation
}

#[Compatibility] - Framework & PHP Version Requirements

Defines compatibility requirements for Forge Engine framework and PHP versions.

use Forge\Core\Module\Attributes\Compatibility;

#[Compatibility(
    framework: ">=0.1.0",            // Forge Engine version constraint
    php: ">=8.3"                    // PHP version constraint
)]
final class BlogModule
{
    // Module implementation
}

#[Repository] - Source Code Repository

Specifies the source code repository for the module, used by the PackageManager.

use Forge\Core\Module\Attributes\Repository;

#[Repository(
    type: "git",                     // Repository type (git, svn, etc.)
    url: "https://github.com/forge-engine/forge-blog.git" // Repository URL
)]
final class BlogModule
{
    // Module implementation
}

Dependency & Service Attributes

#[Provides] - Service Interface Provision

Declares that this module provides implementations for specific interfaces. Can be repeated multiple times.

use Forge\Core\Module\Attributes\Provides;

#[Provides(interface: BlogServiceInterface::class, version: "1.0.0")]
#[Provides(interface: PostRepositoryInterface::class, version: "1.0.0")]
#[Provides(interface: CommentServiceInterface::class, version: "1.0.0")]
final class BlogModule
{
    // Module provides implementations for these interfaces
}

#[Requires] - Dependency Declaration

Declares dependencies on other modules or interfaces. Can be used on the class or specific methods.

use Forge\Core\Module\Attributes\Requires;

#[Requires] // Class-level dependency (module requires other modules)
final class BlogModule
{
    #[Requires] // Method-level dependency
    public function register(Container $container): void
    {
        // This method requires other modules to be available
    }
}

Configuration & CLI Attributes

#[ConfigDefaults] - Default Configuration Values

Defines default configuration values for the module. These values are merged with user configuration.

use Forge\Core\Module\Attributes\ConfigDefaults;

#[ConfigDefaults([
    'posts_per_page' => 10,
    'allow_comments' => true,
    'moderation_required' => true,
    'seo_enabled' => true,
    'cache_ttl' => 3600
])]
final class BlogModule
{
    // Module with default configuration
}

Example from ForgePackageManager module:

#[ConfigDefaults([
    'repositories' => [
        'packagist' => [
            'type' => 'composer',
            'url' => 'https://repo.packagist.org'
        ]
    ],
    'cache_ttl' => 3600,
    'auto_update' => false
])]
final class ForgePackageManager
{
    // Package manager with default repository configuration
}

#[CLICommand] - CLI Command Definition

Defines a CLI command that this module provides. Used to register commands with the CLI system.

use Forge\Core\Module\Attributes\CLICommand;

#[CLICommand(
    name: "blog:install",            // Command name
    description: "Install the blog module" // Command description
)]
final class InstallCommand
{
    // CLI command implementation
}

UI & Navigation Attributes

#[HubItem] - Admin Hub Navigation Item

Defines navigation items for the admin hub interface. Can be repeated multiple times for multiple menu items.

use Forge\Core\Module\Attributes\HubItem;
use Forge\Core\Module\ForgeIcon;

#[HubItem(
    label: "Blog Posts",             // Menu item label
    route: "admin.blog.posts",       // Route name
    icon: ForgeIcon::POSTS,          // Icon constant
    order: 10,                       // Menu order (lower appears first)
    permissions: ["blog.manage"]       // Required permissions
)]
#[HubItem(
    label: "Categories",
    route: "admin.blog.categories",
    icon: ForgeIcon::CATEGORIES,
    order: 20,
    permissions: ["blog.manage"]
)]
final class BlogModule
{
    // Module adds navigation items to admin hub
}

Lifecycle Hook Attributes

#[LifecycleHook] - Lifecycle Method Marker

Marks a method to be called at specific points in the application lifecycle.

use Forge\Core\Module\Attributes\LifecycleHook;
use Forge\Core\Module\LifecycleHookName;

#[LifecycleHook(hook: LifecycleHookName::AFTER_MODULE_REGISTER)]
public function onAfterModuleRegister(): void
{
    // Called after all modules have been registered
}

#[LifecycleHook(hook: LifecycleHookName::AFTER_BOOT)]
public function onAfterBoot(): void
{
    // Called after the application has fully booted
}

#[LifecycleHook(hook: LifecycleHookName::BEFORE_REQUEST)]
public function onBeforeRequest(): void
{
    // Called before each HTTP request
}

ModuleManager

Central manager for module discovery, loading, and lifecycle management. The ModuleManager handles dependency resolution, module ordering, and provides methods for enabling, disabling, and updating modules.

Module Loading System

Forge Engine uses a ModuleLoader to discover, load, and manage modules throughout the application lifecycle. Modules are loaded based on their forge.json configuration files.

Module Discovery

Modules are automatically discovered by scanning the /modules directory for forge.json files.

// The ModuleLoader automatically discovers modules in the modules directory
$loader = new ModuleLoader($this->container);

// Modules are discovered based on forge.json files
// Example forge.json structure:
{
    "$schema": "engine/Core/Schema/module-schema.json",
    "name": "ForgeBlog",
    "version": "1.0.0",
    "description": "Blog functionality for Forge Engine",
    "type": "module",
    "order": 10,
    "provides": ["blog", "posts"],
    "requires": {
        "ForgeAuth": "^1.0.0",
        "ForgeUI": "^1.0.0"
    },
    "lifecycleHooks": {
        "register": "register",
        "afterModuleRegister": "onAfterModuleRegister"
    },
    "class": "Modules\\ForgeBlog\\ForgeBlogModule",
    "cli": {
        "commands": {
            "blog:install": "Modules\\ForgeBlog\\Commands\\InstallCommand"
        }
    },
    "compatibility": {
        "engine": "^3.0.0"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/forge-engine/forge-blog.git"
    }
}

Module Loading Process

The ModuleLoader follows a specific process to load and register modules.

// In ModuleSetup.php - the bootstrap process
public function loadModules(): void
{
    $loader = new ModuleLoader($this->container);
    
    // 1. Discover all modules by scanning forge.json files
    $modules = $loader->discoverModules();
    
    // 2. Sort modules based on order and dependencies
    $sortedModules = $loader->sortModulesByDependencies($modules);
    
    // 3. Load each module class
    foreach ($sortedModules as $moduleConfig) {
        $moduleClass = $moduleConfig['class'];
        $module = new $moduleClass();
        
        // 4. Call register() method on each module
        $module->register($this->container);
        
        // 5. Store module instance for later use
        $this->modules[$moduleConfig['name']] = $module;
    }
    
    // 6. Trigger AFTER_MODULE_LOAD lifecycle hook
    $this->eventDispatcher->dispatch(LifecycleHookName::AFTER_MODULE_LOAD);
    
    // 7. Call lifecycle hook methods on each module
    foreach ($this->modules as $module) {
        $this->callLifecycleHooks($module);
    }
}

Dependency Resolution

Modules are loaded in the correct order based on their dependencies.

// Example dependency resolution
$modules = [
    'ForgeAuth' => ['order' => 1, 'provides' => ['auth']],
    'ForgeUI' => ['order' => 2, 'provides' => ['ui', 'templates']],
    'ForgeBlog' => ['order' => 10, 'requires' => ['auth', 'ui']]
];

// The loader will automatically:
// 1. Load ForgeAuth first (order 1, no dependencies)
// 2. Load ForgeUI second (order 2, no dependencies)
// 3. Load ForgeBlog last (order 10, depends on auth and ui)

// If circular dependencies are detected, an exception is thrown
// If required modules are missing, an exception is thrown

Module Management

Module management in Forge Engine is handled through the PackageManager module and CLI commands, not through a programmatic API.

CLI Commands

Use the Forge CLI to manage modules from the command line.

# List all available modules
php forge module:list

# Install a module
php forge module:install ForgeBlog

# Uninstall a module
php forge module:uninstall ForgeBlog

# Enable a module
php forge module:enable ForgeBlog

# Disable a module
php forge module:disable ForgeBlog

# Update a module
php forge module:update ForgeBlog

# Check module status
php forge module:status ForgeBlog

PackageManager Service

For programmatic module management, use the PackageManager service.

use Forge\Core\PackageManager\Services\PackageManagerService;

$packageManager = $container->make(PackageManagerService::class);

// Get all installed modules
$modules = $packageManager->getInstalledModules();

// Install a module from repository
$result = $packageManager->installModule('ForgeBlog', '1.0.0');

// Uninstall a module
$result = $packageManager->uninstallModule('ForgeBlog');

// Update a module
$result = $packageManager->updateModule('ForgeBlog', '2.0.0');

// Check if module is installed
$isInstalled = $packageManager->isModuleInstalled('ForgeBlog');

// Get module information
$moduleInfo = $packageManager->getModuleInfo('ForgeBlog');

Module Configuration

Module configuration is managed through the forge.json file and can be accessed programmatically.

// Access module configuration from within a module
public function register(Container $container): void
{
    // Get the current module's configuration
    $moduleConfig = $this->getModuleConfig();
    
    // Access specific configuration values
    $postsPerPage = $moduleConfig['posts_per_page'] ?? 10;
    $allowComments = $moduleConfig['allow_comments'] ?? true;
    
    // Register configuration as a service
    $container->bind('blog.config', function() use ($moduleConfig) {
        return new Config($moduleConfig);
    });
}

// Update module configuration programmatically
$packageManager = $container->make(PackageManagerService::class);
$packageManager->updateModuleConfig('ForgeBlog', [
    'posts_per_page' => 20,
    'allow_comments' => false
]);

Module Information

Accessing Module Information

Get information about loaded modules from the container or PackageManager.

use Forge\Core\PackageManager\Services\PackageManagerService;

$packageManager = $container->make(PackageManagerService::class);

// Get information about a specific module
$moduleInfo = $packageManager->getModuleInfo('ForgeBlog');

if ($moduleInfo) {
    echo "Module: " . $moduleInfo['name'] . "\n";
    echo "Version: " . $moduleInfo['version'] . "\n";
    echo "Description: " . $moduleInfo['description'] . "\n";
    echo "Type: " . $moduleInfo['type'] . "\n";
    echo "Order: " . $moduleInfo['order'] . "\n";
    echo "Provides: " . implode(', ', $moduleInfo['provides'] ?? []) . "\n";
    echo "Requires: " . json_encode($moduleInfo['requires'] ?? []) . "\n";
    echo "Author: " . ($moduleInfo['author'] ?? 'Unknown') . "\n";
    echo "License: " . ($moduleInfo['license'] ?? 'Unknown') . "\n";
}

// Get all installed modules
$allModules = $packageManager->getInstalledModules();

foreach ($allModules as $moduleName => $moduleData) {
    echo "Module: {$moduleName}\n";
    echo "  Version: {$moduleData['version']}\n";
    echo "  Description: {$moduleData['description']}\n";
    echo "  Status: " . ($moduleData['enabled'] ? 'Enabled' : 'Disabled') . "\n";
}
    echo "\n";
}

getStatus(string $name): string

Get the status of a module.

$status = $manager->getStatus('blog');

switch ($status) {
    case 'installed':
        echo "Module is installed and ready";
        break;
    case 'enabled':
        echo "Module is enabled and active";
        break;
    case 'disabled':
        echo "Module is disabled";
        break;
    case 'not_installed':
        echo "Module is not installed";
        break;
    case 'error':
        echo "Module has errors";
        break;
}

Module Loading System

Forge Engine uses a sophisticated module loading system that discovers modules through their forge.json configuration files and loads them using PHP 8 attributes. The system handles dependency resolution, lifecycle hooks, and module validation automatically.

Module Discovery

Automatic Discovery

Modules are automatically discovered by scanning the /modules directory for forge.json files.

// The ModuleLoader automatically discovers modules during bootstrap
// No manual discovery needed - just place your module in the /modules directory

// Example module structure:
/modules/
├── ForgeAuth/
│   ├── forge.json          # Module configuration
│   ├── src/
│   │   └── ForgeAuthModule.php  # Module class
│   └── ...
├── ForgeBlog/
│   ├── forge.json
│   ├── src/
│   │   └── ForgeBlogModule.php
│   └── ...
└── ForgeUI/
    ├── forge.json
    ├── src/
    │   └── ForgeUIModule.php
    └── ...

Module Validation

Modules are validated against the JSON schema during discovery.

// The ModuleLoader validates each forge.json against the schema
// engine/Core/Schema/module-schema.json

// Validation checks include:
// - Required fields (name, version, class)
// - Version format compliance
// - Dependency format
// - Lifecycle hook method existence
// - Class file accessibility

// If validation fails, the module is skipped and an error is logged

Dependency Resolution

Modules are loaded in the correct order based on their dependencies and order values.

// Example dependency resolution process:
$modules = [
    'ForgeAuth' => ['order' => 1, 'provides' => ['auth']],
    'ForgeUI' => ['order' => 2, 'provides' => ['ui', 'templates']],
    'ForgeBlog' => ['order' => 10, 'requires' => ['auth', 'ui']]
];

// The loader will:
// 1. Sort by order field (lower numbers first)
// 2. Resolve dependencies (required modules loaded first)
// 3. Detect circular dependencies
// 4. Throw exception if required modules are missing
// 5. Load modules in resolved order

Usage Examples

Common patterns and examples for working with the Module System.

Basic Module Setup

// Example ForgeBlogModule.php
namespace Modules\ForgeBlog;

use Forge\Core\Module\Attributes\Module;
use Forge\Core\Module\Attributes\Compatibility;
use Forge\Core\Module\Attributes\Repository;
use Forge\Core\Module\Attributes\Provides;
use Forge\Core\Module\Attributes\Requires;
use Forge\Core\Module\Attributes\LifecycleHook;
use Forge\Core\Module\LifecycleHookName;

#[Module(
    name: "ForgeBlog",
    version: "1.0.0",
    description: "Blog functionality for Forge Engine",
    order: 10,
    core: false,
    isCli: false
)]
#[Compatibility(engine: "^3.0.0")]
#[Repository(type: "git", url: "https://github.com/forge-engine/forge-blog.git")]
#[Provides("blog")]
#[Provides("posts")]
#[Requires("ForgeAuth", "^1.0.0")]
#[Requires("ForgeUI", "^1.0.0")]
class ForgeBlogModule
{
    public function register(Container $container): void
    {
        // Register blog services
        $container->bind(BlogService::class, BlogService::class);
        $container->bind(PostRepository::class, PostRepository::class);
        
        // Register configuration
        $container->bind('blog.config', function() {
            return new Config([
                'posts_per_page' => 10,
                'allow_comments' => true,
                'moderation_required' => true,
                'seo_enabled' => true
            ]);
        });
    }

    #[LifecycleHook(hook: LifecycleHookName::AFTER_MODULE_REGISTER)]
    public function onAfterModuleRegister(): void
    {
        // Register routes, event listeners, etc.
        // This is called after all modules are registered
        $this->registerRoutes();
        $this->registerEventListeners();
    }

    private function registerRoutes(): void
    {
        // Register blog routes
        $router = Container::getInstance()->make('router');
        $router->get('/blog', [BlogController::class, 'index']);
        $router->get('/blog/{slug}', [BlogController::class, 'show']);
    }

    private function registerEventListeners(): void
    {
        $events = Container::getInstance()->make('events');
        $events->listen('blog.post.created', function($post) {
            // Handle post creation
        });
    }
}

Module Configuration

// forge.json - Module configuration file
{
    "name": "ForgeBlog",
    "version": "1.0.0",
    "description": "Blog functionality for Forge Engine",
    "type": "module",
    "keywords": ["blog", "posts", "content"],
    "homepage": "https://github.com/forge-engine/forge-blog",
    "license": "MIT",
    "authors": [
        {
            "name": "Forge Engine Team",
            "email": "team@forge-engine.com"
        }
    ],
    "support": {
        "issues": "https://github.com/forge-engine/forge-blog/issues",
        "source": "https://github.com/forge-engine/forge-blog"
    },
    "require": {
        "php": "^8.1",
        "forge-engine/core": "^3.0.0",
        "forge-engine/forge-auth": "^1.0.0",
        "forge-engine/forge-ui": "^1.0.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^10.0"
    },
    "autoload": {
        "psr-4": {
            "Modules\\ForgeBlog\\": "src/"
        }
    },
    "extra": {
        "forge": {
            "class": "Modules\\ForgeBlog\\ForgeBlogModule",
            "order": 10,
            "core": false,
            "isCli": false,
            "provides": ["blog", "posts"],
            "compatibility": {
                "engine": "^3.0.0"
            }
        }
    }
}

Module Dependencies

// ForgeBlogModule.php - Using attributes for dependencies
use Forge\Core\Module\Attributes\Requires;
use Forge\Core\Module\Attributes\Provides;

#[Module(
    name: "ForgeBlog",
    version: "1.0.0",
    description: "Blog functionality for Forge Engine"
)]
#[Requires("ForgeAuth", "^1.0.0")]      // Requires auth module
#[Requires("ForgeUI", "^1.0.0")]        // Requires UI module
#[Requires("ForgeDatabase", "^1.0.0")]    // Requires database module
#[Provides("blog")]                        // Provides blog functionality
#[Provides("posts")]                      // Provides posts functionality
class ForgeBlogModule
{
    public function register(Container $container): void
    {
        // Services are automatically available through dependency injection
        $authService = $container->make('ForgeAuth');
        $uiService = $container->make('ForgeUI');
        $dbService = $container->make('ForgeDatabase');
        
        // Register blog services with dependencies
        $container->bind(BlogService::class, function($container) use ($authService, $dbService) {
            return new BlogService(
                $container->make(PostRepository::class),
                $authService,
                $dbService
            );
        });
    }

    #[LifecycleHook(hook: LifecycleHookName::AFTER_MODULE_REGISTER)]
    public function onAfterModuleRegister(): void
    {
        // Dependencies are guaranteed to be loaded at this point
        $this->registerBlogRoutes();
        $this->setupBlogDatabase();
    }

    private function registerBlogRoutes(): void
    {
        // Use services from required modules via dependency injection
        $auth = Container::getInstance()->make('ForgeAuth');
        $router = Container::getInstance()->make('router');
        
        // Register protected blog routes
        $router->get('/blog', [BlogController::class, 'index']);
        $router->get('/blog/{slug}', [BlogController::class, 'show']);
        
        // Admin routes (require auth)
        $router->group(['middleware' => $auth->getAuthMiddleware()], function($router) {
            $router->get('/admin/blog', [AdminBlogController::class, 'index']);
            $router->post('/admin/blog/posts', [AdminBlogController::class, 'create']);
        });
    }

    private function setupBlogDatabase(): void
    {
        $db = Container::getInstance()->make('ForgeDatabase');
        
        // Create blog tables using the database service
        $db->createTable('blog_posts', function($table) {
            $table->id();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('content');
            $table->foreignId('author_id')->constrained('users');
            $table->timestamps();
        });
    }
}

Module Events

// ForgeBlogModule.php - Using lifecycle hooks and events
use Forge\Core\Module\Attributes\LifecycleHook;
use Forge\Core\Module\LifecycleHookName;

#[Module(name: "ForgeBlog", version: "1.0.0")]
class ForgeBlogModule
{
    #[LifecycleHook(hook: LifecycleHookName::AFTER_BOOT)]
    public function onAfterBoot(): void
    {
        // Register module-level event listeners
        $events = Container::getInstance()->make('events');
        
        // Listen for module system events
        $events->listen('module.installed', function($moduleName) {
            if ($moduleName === 'ForgeBlog') {
                $this->onBlogModuleInstalled();
            }
        });
        
        $events->listen('module.enabled', function($moduleName) {
            if ($moduleName === 'ForgeBlog') {
                $this->onBlogModuleEnabled();
            }
        });
        
        $events->listen('module.disabled', function($moduleName) {
            if ($moduleName === 'ForgeBlog') {
                $this->onBlogModuleDisabled();
            }
        });
    }

    #[LifecycleHook(hook: LifecycleHookName::AFTER_MODULE_REGISTER)]
    public function onAfterModuleRegister(): void
    {
        // Register blog-specific event listeners
        $events = Container::getInstance()->make('events');
        
        // Blog post events
        $events->listen('blog.post.created', function($post) {
            $this->clearPostCache($post->id);
            $this->notifySubscribers($post);
            $this->updateSearchIndex($post);
        });
        
        $events->listen('blog.post.updated', function($post) {
            $this->clearRelatedCaches($post);
            $this->updateSearchIndex($post);
        });
        
        $events->listen('blog.post.deleted', function($post) {
            $this->removeFromSearchIndex($post);
            $this->cleanupMedia($post);
            $this->clearPostCache($post->id);
        });
        
        // Category events
        $events->listen('blog.category.created', function($category) {
            $this->clearCategoryCache($category->id);
        });
        
        // Comment events
        $events->listen('blog.comment.created', function($comment) {
            $this->notifyPostAuthor($comment);
            $this->moderateComment($comment);
        });
    }

    private function onBlogModuleInstalled(): void
    {
        // Create default blog settings
        $config = Container::getInstance()->make('config');
        $config->set('blog.settings', [
            'posts_per_page' => 10,
            'allow_comments' => true,
            'moderation_required' => true
        ]);
        
        // Log installation
        Container::getInstance()->make('log')->info('ForgeBlog module installed successfully');
    }

    private function onBlogModuleEnabled(): void
    {
        // Clear blog-related caches
        Container::getInstance()->make('cache')->forget('blog.posts');
        Container::getInstance()->make('cache')->forget('blog.categories');
        
        // Rebuild search index
        $this->rebuildSearchIndex();
        
        // Log enablement
        Container::getInstance()->make('log')->info('ForgeBlog module enabled');
    }

    private function onBlogModuleDisabled(): void
    {
        // Clear all blog caches
        $this->clearAllBlogCaches();
        
        // Log disablement
        Container::getInstance()->make('log')->info('ForgeBlog module disabled');
    }

    // Helper methods for event handling
    private function clearPostCache($postId): void
    {
        Container::getInstance()->make('cache')->forget("blog.post.{$postId}");
        Container::getInstance()->make('cache')->forget("blog.post.{$postId}.html");
    }

    private function notifySubscribers($post): void
    {
        $notificationService = Container::getInstance()->make('notification');
        $subscribers = $this->getPostSubscribers($post->id);
        
        foreach ($subscribers as $subscriber) {
            $notificationService->send($subscriber, 'New blog post: ' . $post->title);
        }
    }

    private function updateSearchIndex($post): void
    {
        $searchService = Container::getInstance()->make('search');
        $searchService->index('blog_posts', $post->id, [
            'title' => $post->title,
            'content' => $post->content,
            'tags' => $post->tags,
            'category' => $post->category->name
        ]);
    }
}

Best Practices

Recommended patterns and guidelines for working with the Module System.

Do's

  • • Use PHP 8 attributes for module configuration
  • • Define module metadata in forge.json
  • • Use semantic versioning (semver)
  • • Document all dependencies with #[Requires]
  • • Declare provided services with #[Provides]
  • • Use lifecycle hooks appropriately
  • • Implement register() method for service registration
  • • Use dependency injection container
  • • Handle module enable/disable events
  • • Test modules in isolation
  • • Use PSR-4 autoloading
  • • Include proper error handling

Don'ts

  • • Don't extend base Module classes
  • • Don't create circular dependencies
  • • Don't hardcode module paths or names
  • • Don't ignore dependency version constraints
  • • Don't use global variables or state
  • • Don't skip compatibility checks
  • • Don't leave temporary files on uninstall
  • • Don't modify core engine files
  • • Don't make modules too large or monolithic
  • • Don't forget cleanup in disable hooks
  • • Don't ignore security best practices
  • • Don't bypass the service container