N1 Nex1on

Власний модуль: як він влаштований і як його написати

Тека модуля, поля module.json, права, налаштування, переклади, перевірка й пакування; робочий приклад «Привіт» повністю.

Оновлено 20.10.2026

Модуль — це тека в 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 і покладіть у неї файли, як показано; у кожного блока є кнопка «Копіювати».

modules/hello/module.jsonJSON
{
    "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"
}
modules/hello/src/HelloModule.phpPHP
<?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();
    }
}
modules/hello/src/HelloController.phpPHP
<?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'),
        ]);
    }
}
modules/hello/routes.phpPHP
<?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');
modules/hello/views/index.phpPHP
<?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/. Ключі в усіх мов однакові. Англійська:

modules/hello/locales/en.jsonJSON
{
    "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) влаштовані так само, змінюються лише значення. Для української:

modules/hello/locales/uk.jsonJSON
{
    "title": "Привіт",
    "text": "Вітаємо, відвідувачу! Цю сторінку створив модуль, який ви написали самі."
}

Далі з кореня сайту:

Bash
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), тому виглядає частиною сайту за будь-якої теми.

Заготовка однією командою

Щоб не набирати файли вручну:

Bash
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службове поле: для власних модулів не потрібне (без нього модуль вважається внутрішнім і не віддається через каталог випусків)

Права

Права вводить модуль, а не ядро. Додайте в клас модуля метод і дайте назви прав у перекладах:

PHP
public function permissions(): array
{
    return [
        'hello.view'   => 'content',   // право => розділ у «Ролі й права»
        'hello.manage' => 'content',
    ];
}
JSON
"permissions": {
    "view": "Дивитися",
    "manage": "Керувати"
}

Назва права — ключ permissions.<без назви модуля> в усіх файлах перекладів. Без нього перевірка випуску відмовить: інакше в «Ролях» замість «Дивитися» буде написано hello.view. Якій ролі що можна, вирішує сам модуль: роздайте права в методі install() (зразок — module:new). Закривати адресу правом потрібно в routes.php, а не лише ховати пункт меню.

Налаштування

Значення за замовчуванням оголошуються в класі модуля: назва, значення й тип (string, int, bool):

PHP
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: перевірка випуску їх знаходить.

Від ідеї до архіву

  1. php nex1on module:new ім'я "Назва" — заготовка (або файли руками, як у прикладі).
  2. php nex1on module:install ім'я і module:enable ім'я — поставити й увімкнути на своєму стенді; сторінка відкривається.
  3. php nex1on module:check ім'я — перевірка випуску: маніфест, розбір усіх php-файлів, відкат у міграціях, переклади сімома мовами, назви прав, опис, налагоджувальні виклики. Помилки позначено «✗», з назвою файла або мови.
  4. php nex1on module:pack ім'я — знову перевірка, потім архів. Команда друкує шлях: за замовчуванням тека nex1on-moduli/zip на два рівні вище за теку сайту; змінити можна ключем app.release_directory у config/local.php. Архів називається ім'я-версія.zip, усередині — тека модуля.
  5. Поставити архів на інший сайт — за статтею «Як встановити власне доповнення».

Часті помилки

Що ви бачитеЩо не так
«Файл module.json не розбирається як JSON»пропущено кому або лапку; зворотна коса риска в class не подвоєна (або просто приберіть поле class)
«Ім’я в маніфесті не збігається з назвою теки»name у module.json має бути таким самим, як тека
«Потрібне ядро …, встановлено …»в engine стоїть версія новіша за ваш рушій: проставте ту, на якій перевіряли
«немає перекладів: …» або «бракує ключів»для кожної з семи мов потрібен файл, і ключі в усіх однакові
«немає назви права …»додайте permissions.<право> в усі файли перекладів
«у міграції … не написано відкат down()»допишіть down(), який прибирає створене в up()
Сторінка відкривається, але замість слів — ключі на кшталт hello::titleнемає такого ключа в locales/en.json або файл не розбирається як JSON
Чи допомогла ця стаття?
На цій сторінці