Власний модуль: як він влаштований і як його написати
Тека модуля, поля module.json, права, налаштування, переклади, перевірка й пакування; робочий приклад «Привіт» повністю.
Модуль — це тека в modules/. Рушій знаходить її сам, показує в «Доповнення → Модулі» і підключає, коли ви ввімкнете. Ядро при цьому не змінюється: модуль додає свої адреси, сторінки, права й тексти, а все спільне бере в рушія. Потрібно вміти писати на PHP 8 і трохи знати JSON. Нижче — усе, що має лежати в теці, і приклад, який можна скопіювати та запустити.
Що лежить у теці
modules/hello/
├── module.json
├── routes.php
├── src/
│ ├── HelloModule.php
│ └── HelloController.php
├── views/
│ └── index.php
├── locales/
├── database/migrations/
└── assets/module.json— опис модуля (обов'язково).routes.php— адреси модуля.src/— класи: клас модуляHelloModule.php(обов'язковий, називається за іменем теки) і контролери сторінок.views/— шаблони сторінок.locales/— переклади:en.json,ru.json,uk.json,no.json,pl.json,sv.json,lt.json.database/migrations/— таблиці модуля (якщо потрібні).assets/— стилі, скрипти, зображення (якщо потрібні).
Назва теки — малі латинські літери, цифри, дефіс і підкреслення, починається з літери: hello, shop-orders. Цю назву потім не можна буде змінити: за нею рушій зв’язує файли, записи в базі й ключі перекладів. Назви вбудованих модулів і службові слова на кшталт admin, api, core, search зайняті: архів із такою назвою встановлювач не прийме.
Мінімальний робочий приклад «Привіт»
Модуль із п’яти файлів (плюс переклади). Він додає сторінку /hello, не потребує бази й проходить перевірку випуску. Створіть теку modules/hello і покладіть у неї файли, як показано; у кожного блока є кнопка «Копіювати».
{
"name": "hello",
"version": "1.0.0",
"title": {"en": "Hello", "ru": "Привет", "uk": "Привіт", "no": "Hei", "pl": "Cześć", "sv": "Hej", "lt": "Labas"},
"description": {
"en": "A page that greets the visitor.",
"ru": "Страница, которая здоровается с посетителем.",
"uk": "Сторінка, яка вітається з відвідувачем.",
"no": "En side som hilser på besøkende.",
"pl": "Strona, która wita odwiedzającego.",
"sv": "En sida som hälsar på besökaren.",
"lt": "Puslapis, kuris pasisveikina su lankytoju."
},
"author": "Ваше ім’я",
"engine": ">=0.9.9",
"position": 100,
"group": "service",
"site": "/hello"
}<?php
declare(strict_types=1);
namespace Nex1on\Modules\Hello;
use Nex1on\Core\Modules\Module;
final class HelloModule extends Module
{
public function boot(): void
{
// підключити адреси з routes.php
$this->loadRoutes();
}
}<?php
declare(strict_types=1);
namespace Nex1on\Modules\Hello;
use Nex1on\Core\Http\Controller;
use Nex1on\Core\Http\Response;
final class HelloController extends Controller
{
public function index(): Response
{
return $this->view('hello::index', [
'title' => $this->t('hello::title'),
]);
}
}<?php
declare(strict_types=1);
use Nex1on\Modules\Hello\HelloController;
/** @var \Nex1on\Core\Http\Router $router */
$router->get('/hello', [HelloController::class, 'index'])
->name('hello.index')
->option('module', 'hello');<?php
/** @var \Nex1on\Core\Themes\View $this */
$this->extend('layouts/main');
?>
<?php $this->section('title') ?><?= $this->e($this->t('hello::title')) ?><?php $this->end() ?>
<?php $this->section('content') ?>
<article class="hero">
<h1><?= $this->e($this->t('hello::title')) ?></h1>
<p><?= $this->e($this->t('hello::text')) ?></p>
</article>
<?php $this->end() ?>Переклади — по одному файлу на мову в modules/hello/locales/. Ключі в усіх мов однакові. Англійська:
{
"title": "Hello",
"text": "Hello, visitor! This page comes from a module you wrote yourself."
}Решта шість файлів (ru.json, uk.json, no.json, pl.json, sv.json, lt.json) влаштовані так само, змінюються лише значення. Для української:
{
"title": "Привіт",
"text": "Вітаємо, відвідувачу! Цю сторінку створив модуль, який ви написали самі."
}Далі з кореня сайту:
php nex1on module:check hello
php nex1on module:install hello
php nex1on module:enable helloПеревірка має закінчитися словами «Модуль готовий до випуску». Після ввімкнення відкрийте /hello. Встановити й увімкнути можна і в панелі: «Доповнення → Модулі», у модуля «Привіт» кнопка «Встановити й увімкнути».
Що тут важливо. Ключі перекладів пишуться з назвою модуля: hello::title — це ключ title з modules/hello/locales/. Текст із перекладів завжди проходить через $this->e(): так вивід екранується. Сторінка малюється в рамці сайту (layouts/main), тому виглядає частиною сайту за будь-якої теми.
Заготовка однією командою
Щоб не набирати файли вручну:
php nex1on module:new shop-orders "Замовлення"Команда створює робочий модуль: маніфест, клас, сторінку, адресу в адмінці, права, міграцію з таблицею й переклади. Він уже встановлюється та вмикається. Перекладів заготовка кладе три (en, ru, uk): решту чотирьох мов додайте самі, інакше перевірка випуску відмовить.
module.json: поля
| Поле | Що це і приклад |
|---|---|
name | обов’язково, збігається з назвою теки: "hello" |
version | обов’язково, версія модуля: "1.0.0"; під час оновлення рушій порівнює її зі встановленою |
title | назва за мовами: {"en": "Hello", "ru": "Привет"}; немає потрібної мови — береться англійська |
description | одне-два речення про те, що робить модуль, за мовами; без опису всіма мовами перевірка випуску відмовить |
author | ваше ім’я або назва |
engine | які версії рушія підтримуєте: ">=0.9.9", "^0.9", ">=0.9 <2.0", "*" — будь-які. Якщо версія вашого рушія не підходить під обмеження, модуль не встановиться і ввімкнути його не вийде |
requires | від яких модулів залежить: {"modules": {"news": ">=1.0"}} або просто {"modules": ["news"]} |
position | порядок підключення, менше — раніше; за замовчуванням 100. Модуль, який займає адресу просто в корені сайту (/pro-nas), ставте близько 900: інакше він перехопить чужі адреси |
group | де показано в списку: content, community, sales, media, marketing, service; не вказано — «службові» |
site | адреса сторінки модуля для відвідувача ("/hello"); за нею в списку будується посилання «відкрити». Лише шлях від кореня сайту |
class | клас модуля. Можна не писати: для теки shop-orders рушій шукає Nex1on\Modules\ShopOrders\ShopOrdersModule. Якщо пишете, у JSON зворотна коса риска подвоюється: "Nex1on\\Modules\\Hello\\HelloModule" |
screenshot | картинка для картки; знімки для показу — файли preview/1.webp … не більше шести |
distribution | службове поле: для власних модулів не потрібне (без нього модуль вважається внутрішнім і не віддається через каталог випусків) |
Права
Права вводить модуль, а не ядро. Додайте в клас модуля метод і дайте назви прав у перекладах:
public function permissions(): array
{
return [
'hello.view' => 'content', // право => розділ у «Ролі й права»
'hello.manage' => 'content',
];
}"permissions": {
"view": "Дивитися",
"manage": "Керувати"
}Назва права — ключ permissions.<без назви модуля> в усіх файлах перекладів. Без нього перевірка випуску відмовить: інакше в «Ролях» замість «Дивитися» буде написано hello.view. Якій ролі що можна, вирішує сам модуль: роздайте права в методі install() (зразок — module:new). Закривати адресу правом потрібно в routes.php, а не лише ховати пункт меню.
Налаштування
Значення за замовчуванням оголошуються в класі модуля: назва, значення й тип (string, int, bool):
public function settings(): array
{
return [
'greeting' => ['Hello', 'string'],
'per_page' => [20, 'int'],
'show_date' => [true, 'bool'],
];
}Читаються всередині модуля так: $this->setting('greeting'). Свою сторінку налаштувань у панелі додають через подію settings.schema; зразок — модуль welcome (modules/welcome/src/WelcomeModule.php).
Блоки й теги для виводу
Щоб шматки вашого модуля можна було вставляти в теми на тегах (список останніх записів, форма, банер), модуль заявляє їх рушієві: сторінки — через ViewTags::register у boot(), елементи, що вставляються, — подією theme.blocks. Повний зразок — модуль polls (PollsModule.php, PollsViewTags.php, каталог у PollsCatalog.php). Як користуватися готовими тегами в темі, розповість стаття «Власна тема».
Переклади
Один файл на мову: locales/<код>.json. Рушій знає сім мов: en, ru, uk, no, pl, sv, lt. На сайті з однією з них потрібного файла може не бути: тоді береться англійська, тому англійський файл обов’язковий завжди. Але до випуску потрібні всі сім і з однаковим набором ключів: перевірка називає, чого бракує. Вкладені ключі пишуться через крапку: "menu": {"title": "…"} читається як hello::menu.title.
Таблиці
Файли міграцій лежать у database/migrations/ і називаються за датою: 2026_10_20_000000_create_hello_table.php. У кожній обов’язково написано down() — відкат: без нього модуль не можна видалити начисто, і перевірка випуску відмовить. Таблиці називайте з назвою модуля (hello_items), чужих не чіпайте. Зразок міграції створює module:new.
Правила, яких не можна порушувати
- Ядро не чіпаємо. Усе своє — в теці модуля; якщо бракує точки підключення, це питання до розробників рушія, а не привід правити
core/. - Текстів для людей у коді немає: підписи, повідомлення, назви — лише ключі перекладів і файли
locales/. - Усе, що приходить від людини, екранується на виводі (
$this->e()); запити до бази — лише через будівник запитів рушія, з параметрами. - У завантаженнях нічого не виконується: файли, які віддає відвідувач, не запускаються і не підключаються.
- PHP-файли лежать лише в
src/,views/,database/,demo/іroutes.phpу корені. Інакше архів із модулем під час встановлення буде відхилено. - Імена файлів — лише латиниця, цифри, крапка, підкреслення й дефіс, без пробілів і кирилиці; файли, ім’я яких починається з крапки (
.gitignore), в архів не годяться. - В архів потрапляють лише файли типів: php, json, md, txt, html, xml, csv, css, js, png, jpg, gif, webp, ico, woff, woff2 (svg — лише в архіві з підписом Nex1on). Решту встановлювач відхиляє.
- У готовому коді не залишайте
var_dump,print_r,dd,dump,error_log: перевірка випуску їх знаходить.
Від ідеї до архіву
php nex1on module:new ім'я "Назва"— заготовка (або файли руками, як у прикладі).php nex1on module:install ім'яіmodule:enable ім'я— поставити й увімкнути на своєму стенді; сторінка відкривається.php nex1on module:check ім'я— перевірка випуску: маніфест, розбір усіх php-файлів, відкат у міграціях, переклади сімома мовами, назви прав, опис, налагоджувальні виклики. Помилки позначено «✗», з назвою файла або мови.php nex1on module:pack ім'я— знову перевірка, потім архів. Команда друкує шлях: за замовчуванням текаnex1on-moduli/zipна два рівні вище за теку сайту; змінити можна ключемapp.release_directoryуconfig/local.php. Архів називаєтьсяім'я-версія.zip, усередині — тека модуля.- Поставити архів на інший сайт — за статтею «Як встановити власне доповнення».
Часті помилки
| Що ви бачите | Що не так |
|---|---|
| «Файл module.json не розбирається як JSON» | пропущено кому або лапку; зворотна коса риска в class не подвоєна (або просто приберіть поле class) |
| «Ім’я в маніфесті не збігається з назвою теки» | name у module.json має бути таким самим, як тека |
| «Потрібне ядро …, встановлено …» | в engine стоїть версія новіша за ваш рушій: проставте ту, на якій перевіряли |
| «немає перекладів: …» або «бракує ключів» | для кожної з семи мов потрібен файл, і ключі в усіх однакові |
| «немає назви права …» | додайте permissions.<право> в усі файли перекладів |
| «у міграції … не написано відкат down()» | допишіть down(), який прибирає створене в up() |
Сторінка відкривається, але замість слів — ключі на кшталт hello::title | немає такого ключа в locales/en.json або файл не розбирається як JSON |