Module extensions

Extensions let application code collect integrations from modules without hard-coding every module name. A manifest maps an extension name to a file, and the application decides what that file's returned value means.

Declaring an extension

Add to the news module's manifest:

'extensions' => ['navigation' => 'Integrations/navigation.php'],

Create modules/news/Integrations/navigation.php before synchronizing:

<?php
return [
    ['label' => 'News', 'path' => '/news'],
];

Then load integrations:

use FloCMS\Core\Modules\ModuleSystem;

$navigation = ModuleSystem::extensions()->all('navigation');
// ['news' => [['label' => 'News', 'path' => '/news']]] when news is enabled

all($extension, $enabledOnly = true) returns values keyed by module ID. Only enabled modules are included by default. all('navigation', false) includes disabled modules too; use this deliberately for administration, not public availability checks.

An extension file must return an array, callable or object. The registry loads it but does not invoke a returned callable or merge arrays for you. Invalid values throw RuntimeException. clear() drops loaded values so the next call reads them again.

Page-block integration

PageBlockRegistry is a compatibility integration for applications with a page builder. It supplies block definitions, resource lists and resolvers; the application must supply the editing interface, persistence and rendering.

Declare the integration in module.php:

'extensions' => ['page-blocks' => 'Integrations/page-blocks.php'],

The key page_blocks is also supported. For older modules without either declaration, the registry looks for Integrations/page-blocks.php. Prefer an explicit declaration for new modules.

Definitions and resolvers

Create modules/news/Integrations/page-blocks.php:

<?php
declare(strict_types=1);

return [
    'module_id' => 'news',
    'definitions' => [
        [
            'type' => 'news_list',
            'label' => 'News list',
            'default_settings' => ['limit' => 5],
        ],
    ],
    'resolvers' => [
        'news_list' => static function (array $settings, string $lang): array {
            $limit = max(1, min(20, (int) ($settings['limit'] ?? 5)));

            return ['title' => 'News', 'limit' => $limit, 'items' => []];
        },
    ],
    'disabled_data' => [
        'news_list' => ['title' => 'News', 'items' => []],
    ],
    'resources' => [
        'news_categories' => static fn (string $lang): array => [],
    ],
];

Replace the empty item/category lists with your application's queries. This example does not assume a category table or a page-builder schema.

The returned module_id must match the owner. Block types are lowercase letters, digits and underscores, starting with a letter, and must be unique across modules. Other definition fields, such as label, belong to the consuming application.

Reading blocks

use FloCMS\Core\Modules\ModuleSystem;
use FloCMS\Core\Modules\PageBlockRegistry;

$manager = ModuleSystem::manager();
$definitions = PageBlockRegistry::definitions($manager);
$resources = PageBlockRegistry::resources($manager, 'en');
$owner = PageBlockRegistry::moduleIdForType('news_list');
$defaults = PageBlockRegistry::defaultSettingsForType('news_list');
$data = PageBlockRegistry::resolve($manager, 'news_list', ['limit' => 3], 'en');
Method Behavior
definitions($manager = null) Flattened definitions from enabled modules
resources($manager, $lang) Resource lists from enabled modules; providers receive the language
moduleIdForType($type) Owning module ID, or null; includes disabled modules
defaultSettingsForType($type) Defaults or null; includes disabled modules
resolve($manager, $type, $settings, $lang) Call the enabled module's resolver; use disabled_data when disabled; null for an unknown type
clear() Clear the static integration cache

Pass null for $manager to use ModuleSystem::manager(). Missing resolvers and non-array resolver results become []; invalid resource providers throw. Resource providers should return arrays. Keep resource names unique when combining integrations, since a later value replaces an earlier one with the same key.

State changes and caching

ExtensionRegistry caches loaded values per extension and enabled-only mode. PageBlockRegistry caches integration definitions statically, then checks module availability when resolving data. In a long-running process, clear the relevant registry after changing source/state and rebuild providers when needed. See module lifecycle.

Esc