Перейти до основного вмісту

Структура модуля

Модуль — самодостатня тека в app/Modules/<Name>/:

app/Modules/MyModule/
├── module_info.php # метадані: назва, версія, опис (єдине джерело версії)
├── Config/
│ ├── Routes.php # роути модуля
│ ├── Events.php # підписки на події ядра, точки рендеру (опційно)
│ └── Registrar.php # внески в реєстри ядра: меню, CSRF, планувальник... (опційно)
├── Controllers/
│ ├── MyModule.php # публічні контролери (storefront)
│ └── Admin/
│ └── MyModuleAdmin.php# адмін-контролери
├── Models/ # CI4-моделі модуля
├── Libraries/ # бізнес-логіка, install()/ensureSchema() для власних таблиць
│ └── ModuleUpdater.php # самооновлення (типовий компонент)
├── Commands/ # spark-команди (планові задачі, обслуговування)
├── Resources/
│ └── install.sql # опційний ідемпотентний SQL, який виконує store-інсталятор
├── Views/
│ └── admin/ # Twig-в'юхи адмінки
└── .baseline.json # маніфест файлів версії (генерується при збірці)

module_info.php

Єдине джерело правди про версію модуля. Його читають самооновлювач і сервер оновлень; кожен реліз має піднімати версію тут.

Як ядро бачить модуль

Auto-discovery CI4 підхоплює Config/Routes.php, Config/Events.php і Config/Registrar.php лише для відомих йому просторів імен. Тому вбудованому модулю потрібен один рядок у $psr4 файлу app/Config/Autoload.php ('App\Modules\MyModule' => APPPATH . 'Modules/MyModule'); без нього роути 404-ять у slug-фолбек, а події мовчать — composer dump-autoload не достатньо. Модулі з магазину цей файл не чіпають: інсталятор пише простір імен у writable/store_modules.json, який Config\Autoload зливає при завантаженні.

Модулю також потрібен рядок у таблиці components (identif, enabled, active); store-інсталятор створює його сам, вбудований модуль — у своєму кроці встановлення.

Доступ до даних

Нові модулі використовують лише Query Builder CI4 (\Config\Database::connect()) або CodeIgniter\Modelбез Propel (App\Propel\*Query::create(), joinWithI18n, Propel-моделі) у новому коді. Зразки, написані так: CloudStorage, Comments/Controllers/CommentsApi.php.

Модуль володіє своєю схемою: таблиці створюються в ідемпотентному install()/ensureSchema() (CREATE TABLE IF NOT EXISTS, INSERT IGNORE для початкових рядків), який викликається при встановленні або ліниво перед першим використанням. Модулі не везуть міграцій CI4; міграції ядра — лише для схеми ядра (і spark migrate на живому сайті безпечний — baseline має гвард).

Config/Registrar.php — внески в реєстри ядра

Замість правок app/Config/* модуль оголошує потрібне у статичному методі ModuleExtensions(); registrar auto-discovery CI4 зливає його в Config\ModuleExtensions:

namespace App\Modules\MyModule\Config;

class Registrar
{
public static function ModuleExtensions(): array
{
return [
'csrfExempt' => ['my_module/callback'], // URI-префікси без CSRF
'lsCacheNever' => ['my_module/exchange'], // ніколи не кешувати сторінку
'adminMenu' => [['section' => 'settings', 'item' => [
'identifier' => 'my_module', 'text' => cms_lang('My module', 'admin_menu'),
'link' => '/admin/my-module', 'class' => '', 'id' => '', 'pjax' => '', 'icon' => '',
]]],
'jobHandlers' => ['my_module:sync' => [Handler::class, 'run']],
'scheduledTasks' => ['my-module-sync' => [
'command' => 'my_module:sync', 'every' => 900, // або 'at' => '04:10'
'label' => 'Мій модуль: синхронізація', 'module' => 'my_module', 'enabled' => true,
]],
'adminTranslations' => ['my_module' => ['Save' => ['uk_UA' => 'Зберегти', 'ru_RU' => 'Сохранить', 'en_US' => 'Save']]],
];
}
}

Підтримувані ключі: csrfExempt, lsCacheNever, moduleEnabledAlways (машинні ендпоїнти, які не мають 404-ити при вимкненому модулі), adminMenu, jobHandlers, scheduledTasks, adminTranslations.

Планові задачі

Запис scheduledTasks — це spark-команда (Commands/) плюс every (секунди) або at (HH:MM щодня). Внутрішній планувальник запускає її окремим підпроцесом і показує в Налаштування → Планувальник (/admin/scheduler), де власник може вимкнути її або виконати зараз. Задача доступна, лише коли module має активний рядок у components і команда є в реєстрі spark — перейменування команди без оновлення запису мовчки паркує задачу. Див. Планувальник.

Правила інтеграції з ядром

Не правте конфіги ядра руками

Якщо модуль реєструє клас у конфігурації ядра (фільтр у Config/Filters.php, роут-хук тощо), його видалення або оновлення без цього класу покладе весь сайт — ядро спробує завантажити неіснуючий клас на кожному запиті.

Правильний шлях: інтегруватися через події (Config/Events.php всередині модуля) та auto-discovery простору імен. Тоді вимкнений чи видалений модуль просто перестає працювати, не ламаючи систему.

  • У кожному слухачі події — перевірка, чи модуль увімкнено (module_enabled('my_module')), і try/catch: проблема модуля не має зривати бізнес-операцію ядра.
  • Віджети вітрини додаються через точки рендеру (слухачі storefront_render_point:<name> у Config/Events.php), а не записом у файли теми.
  • Якщо ядру бракує потрібного шва — додайте в ядро generic-подію чи ключ реєстру, а не виклик класу вашого модуля.
  • Файли модуля належать власнику сайту; після деплою руками не забувайте про права та скидання opcache.

Адмін-контролери

Адмінські екрани модуля рендеряться через Twig-в'юхи модуля (Views/admin/*.twig) у стилі адмін-теми (admin-redesign, rd-* класи).

SimpleXML і Twig

Не передавайте SimpleXMLElement напряму у в'юху — Twig падає на його ітераторі. Конвертуйте у масив: json_decode(json_encode($xml), true).

Самооновлення

Типовий модуль містить Libraries/ModuleUpdater.php і адмін-дії check_update/do_update: перевірка версії на сервері оновлень, завантаження пакета, бекап поточної версії, розпаковка, звірка з .baseline.json.