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