Структура модуля и module.json
Папки модуля, манифест, класс модуля с установкой и обновлением, сервисы, миграции, контроллеры.
Модуль — папка modules/<id>/. Ядро находит его само: Composer и пересборка не нужны.
modules/mymodule/
module.json манифест
src/ PHP-код (PSR-4 из манифеста)
config/services.yaml сервисы (Symfony DI, autowire)
migrations/ миграции базы
templates/ Twig-шаблоны витрины (тема может переопределить)
blocks/<code>/ свои блоки конструктора
assets/storefront.css стили витрины (подключаются сами)
admin/index.ts админка модуля (Vue)
translations/ переводы (messages.ru.yaml…)
market/ картинки для каталога
module.json
{
"id": "mymodule",
"name": "My module",
"version": "1.0.0",
"description": "What it does, in English",
"category": "marketing",
"icon": "star",
"license": {"type": "open"},
"requires": {"core": "^1.3.29", "php": ">=8.3", "modules": {"shop": "^1.2"}},
"autoload": {"psr-4": {"Acme\\MyModule\\": "src"}},
"class": "Acme\\MyModule\\MyModule",
"market": {"title": "Мой модуль", "summary": "…", "description": "…", "preview": "market/preview.jpg"}
}
- category — раздел каталога:
content,commerce,channels,crm,marketing,seo,system. - license —
open(бесплатный) илиcommercialс кодом продукта (см. «Лицензирование»). - requires.modules — от каких модулей зависит; ядро не даст включить без них.
Класс модуля
final class MyModule extends Module
{
public function install(ModuleLifecycleContext $context): void
{
// типы контента, поля, права — идемпотентно
}
public function update(ModuleLifecycleContext $context): void
{
$this->install($context);
}
}
install() вызывается при установке, update() — при обновлении версии. Делайте их идемпотентными: повторный вызов не должен ломать данные.
Сервисы
config/services.yaml — обычный Symfony DI с autowire и autoconfigure:
services:
_defaults: {autowire: true, autoconfigure: true, public: false}
Acme\MyModule\:
resource: '../src/'
exclude: ['../src/MyModule.php']
Миграции
Класс в migrations/, наследник NewCMS\Core\Database\Migration\AbstractMigration. В MySQL изменения схемы не транзакционны, поэтому проверяйте hasTable() и делайте миграцию повторяемой.
Контроллеры
Классы в src/Controller/, наследники NewCMS\Core\Http\AbstractController, маршруты — атрибутом:
#[Route('/api/admin/v1/mymodule/items', name: 'mymodule.items', methods: ['GET'])]
public function items(): JsonResponse { … }
Адреса /api/admin/v1/… требуют входа в админку и CSRF-токен — проверяйте права через AuthorizationChecker.