Milk Admin

Module Extensions

Module Extensions allow you to extend module functionality by adding UI components, permissions, navigation links, and lifecycle hooks without modifying the module class itself. They extend the AbstractModuleExtension class.

Creating a Module Extension

namespace Extensions\MyExtension;

use App\Abstracts\{AbstractModuleExtension, ModuleRuleBuilder};

class Module extends AbstractModuleExtension
{
    // Configuration parameters
    protected $show_in_header = true;

    // Add UI components during configuration
    public function configure(ModuleRuleBuilder $rule_builder): void
    {
        if ($this->show_in_header) {
            $rule_builder->addHeaderLink(
                'My Link',
                '?page=mymodule&action=custom',
                'bi bi-star'
            );
        }
    }

    // Called after module bootstrap
    public function bootstrap(): void
    {
        // Initialize extension
    }

    // Called on each page load
    public function init(): void
    {
        // Page-specific initialization
    }
}

Extension Parameters

Extensions can have configurable parameters defined as protected properties:

class Module extends AbstractModuleExtension
{
    protected $show_in_list = true;
    protected $link_position = 'top-left';
}

Pass parameters when adding the extension:

$rule_builder->addExtension('MyExtension', [
    'show_in_list' => false,
    'link_position' => 'top-right'
]);

Configuration Hook

The configure() method is called during module initialization. Use it to add UI elements and permissions:

Adding Header Links

public function configure(ModuleRuleBuilder $rule_builder): void
{
    $page = $this->module->getPage();

    $rule_builder
        ->addHeaderLink(
            'Audit Trail',                    // Link text
            "?page={$page}&action=audit",    // URL
            'bi bi-database'                  // Icon class
        )
        ->headerPosition('top-left');        // Position: top-left, top-right, etc.
}

Adding Permissions

public function configure(ModuleRuleBuilder $rule_builder): void
{
    $rule_builder->addPermissions([
        'audit' => 'View Audit Trail',
        'export' => 'Export Data'
    ]);
}

Checking Permissions

use App\Permissions;

public function configure(ModuleRuleBuilder $rule_builder): void
{
    $page = $this->module->getPage();

    // Only add link if user has permission
    if (Permissions::check($page . ".audit")) {
        $rule_builder->addHeaderLink(
            'Audit Trail',
            "?page={$page}&action=audit",
            'bi bi-database'
        );
    }
}

Lifecycle Hooks

Hook When Called Use Case
configure() During module configuration Add UI elements, permissions, settings
bootstrap() After module initialization (once) Initialize services, register hooks, load dependencies
init() On each page load Load page-specific assets, track page views

bootstrap() Example

Called once after module initialization. Use for one-time setup:

public function bootstrap(): void
{
    // Load extension dependencies
    require_once __DIR__ . '/AuditModel.php';

    // Configure extension settings
    \Extensions\Audit\Model::setMaxAuditRecords(100);

    // Register global hooks
    Hooks::set('model_save', [$this, 'onModelSave']);

    // Log initialization
    $page = $this->module->getPage();
    error_log("Extension loaded for: {$page}");
}

init() Example

Called on every page request. Use for page-specific initialization:

public function init(): void
{
    // Load page-specific JavaScript/CSS
    Theme::set('javascript', Route::url() . '/Extensions/Audit/Assets/audit.js');
    Theme::set('styles', Route::url() . '/Extensions/Audit/Assets/audit.css');

    // Track page view
    $page = $this->module->getPage();
    $action = $_REQUEST['action'] ?? 'home';
    error_log("Page view: {$page}/{$action}");
}

Accessing the Parent Module

Extensions have access to the parent module via $this->module->get():

public function myMethod()
{
    $module = $this->module->get();

    $page = $module->getPage();
    $title = $module->getTitle();
    $permissions = $module->getPermissions();

    // Access module configuration
    $config = $module->getConfig();
}

Complete Example: Audit Module Extension

This extension adds audit trail UI and functionality to modules:

namespace Extensions\Audit;

use App\Abstracts\{AbstractModuleExtension, ModuleRuleBuilder};
use App\{Permissions, Get};

class Module extends AbstractModuleExtension
{
    public function configure(ModuleRuleBuilder $rule_builder): void
    {
        // Initialize Auth to check permissions
        Get::make('Auth');

        $page = $this->module->getPage();

        // Add header link if user has permission
        if (Permissions::check($page . ".audit")) {
            $rule_builder
                ->addHeaderLink(
                    'Audit Trail',
                    "?page={$page}&action=audit",
                    'bi bi-database'
                )
                ->headerPosition('top-left');
        }

        // Add audit permission
        $rule_builder->addPermissions([
            'audit' => 'View Audit Trail'
        ]);
    }

    public function bootstrap(): void
    {
        // Load audit model
        require_once __DIR__ . '/AuditModel.php';

        // Configure audit settings
        \Extensions\Audit\Model::setMaxAuditRecords(100);
        \Extensions\Audit\Model::setSessionTimeWindow(18000); // 5 hours

        // Log module initialization
        $page = $this->module->getPage();
        $title = $this->module->getTitle();
        error_log("Audit extension loaded: {$page} ({$title})");
    }

    public function init(): void
    {
        // Load audit-specific assets for the page
        $action = $_REQUEST['action'] ?? 'home';

        if ($action === 'audit') {
            // Load audit viewer JavaScript
            Theme::set('javascript', Route::url() . '/Extensions/Audit/Assets/audit-viewer.js');
        }
    }
}

UI Customization

Header Link Positions

Position Description
top-left Top navigation bar, left side
top-right Top navigation bar, right side
sidebar Left sidebar menu

Icon Classes

Use Bootstrap Icons classes for header links:

  • bi bi-database - Database icon
  • bi bi-person - Person icon
  • bi bi-gear - Settings icon
  • bi bi-file-earmark - File icon

See Bootstrap Icons for more options.

Example: Author Extension

Simple extension that adds author tracking UI:

namespace Extensions\Author;

class Module extends AbstractModuleExtension
{
    protected $show_username = true;
    protected $show_in_list = true;

    public function configure(ModuleRuleBuilder $rule_builder): void
    {
        // No UI elements needed for author tracking
        // All functionality is handled in the Model extension
    }

    public function getDisplayField(): string
    {
        return $this->show_email ? 'email' : 'username';
    }

    public function shouldShowInList(): bool
    {
        return $this->show_in_list;
    }
}

Best Practices

  • Check permissions - Always verify user permissions before adding UI elements
  • Use parameters - Make extensions configurable for different use cases
  • Initialize Auth early - Call Get::make('Auth') in configure() if checking permissions
  • Load assets conditionally - Only load JavaScript/CSS when needed
  • Keep bootstrap light - Heavy initialization should be done in init() or lazily
  • Use WeakReference - The parent module is stored as a WeakReference to prevent memory leaks
  • Log important events - Use error_log() for debugging and tracking

Combining Module and Model Extensions

Most extensions have both Module and Model components working together:

Extensions/
└── Audit/
    ├── Module.php        # Adds UI (header links, permissions)
    ├── Model.php         # Adds functionality (audit trail tracking)
    ├── AuditModel.php    # Separate model for audit data
    └── Controller.php    # Handles audit viewer pages

Add both in your module configuration:

// In Module configure()
$rule_builder->addExtension('Audit');

// In Model configure()
$rule_builder->addExtension('Audit');

See Also

Loading...