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/ru.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. Модуль, который занимает адрес прямо в корне сайта (/о-нас), ставьте около 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
Помогла ли эта статья?
На этой странице