- Laravel 13основа — без своего «ядра с хуками»
- PHP 8.5минимальная версия
- 25модулей в поставке, каждый — отдельная папка
- 5видов кирпичей: платформа, тема, шаблон, модуль, язык
Архитектура
Ядро и кирпичи
Контроллеры, модели, миграции и вьюхи лежат там, где их ищет любой Laravel-разработчик. Разобраться здесь дешевле, чем в системе с собственными правилами на каждый случай.
Ядро
Вход, раздел «Модули», Маркет, обновления, установка, темы, меню и материалы. Ядро есть всегда, и снять его нельзя.
Это правило сторожит отдельный тест: если ядро где-то обратится к модулю напрямую, снятие модуля уронит страницу — и тест упадёт раньше, чем это увидит покупатель.
Модуль изнутри
Модуль — это папка, а не магия
Каждый раздел живёт в modules/<Имя>/, и всё, что ему нужно, лежит рядом.
- module.jsonИмя, версия, приоритет загрузки, зависимости
requiresи список провайдеров. - Routes/web.phpМаршруты подключаются сами, но только у включённого модуля.
- Views/Вьюхи в пространстве имён модуля:
view('Имя::…'). - Lang/Словари модуля, тоже под его именем:
__('Имя::файл.ключ'). - Controllers, Models, ServicesОбычные классы Laravel в пространстве
Modules\Имя. - Providers/Обычный сервис-провайдер Laravel. Никаких чужих точек расширения.
Каркас создаёт одна команда, а модуль появится в разделе «Модули» сам — система сверяет папки с таблицей модулей при запуске.
php artisan make:module Portfolio
# modules/Portfolio/module.json
{
"name": "Portfolio",
"title": "Portfolio",
"version": "1.0.0",
"active": true,
"priority": 50,
"description": "Модуль Portfolio",
"providers": [
"Modules\\Portfolio\\Providers\\PortfolioServiceProvider"
]
}
Важно. Миграции модуля кладите в общую database/migrations/. В папке модуля миграция молча не выполнится. Выключенный модуль не грузится совсем — ни маршрутов, ни провайдеров, — а его таблицы остаются в базе и вернутся вместе с ним.
Шаблон материала
Новая витрина — два файла
Шаблон решает, как выглядит материал: лента, товар, услуга клиники, урок. Чтобы добавить свой, не нужно трогать ни контроллер, ни базу.
- Вьюха.
resources/views/frontend/templates/<ключ>.blade.php— как выглядит материал. - Карточка. Рядом
<ключ>.json: название, версия, описание, категория и рекомендованные модули. - Готово. Реестр шаблонов находит карточку сам — шаблон появляется в форме материала. Удалили файлы — пропал из списка, без висящих записей в базе.
# resources/views/frontend/templates/clinic.json
{
"template": "clinic",
"version": "1.3.0",
"title": "Клиника",
"description": "Медцентры и частные практики: приёмы, врачи, цены",
"category": "commerce",
"recommends": [
"Forms",
"Comments"
]
}
Маркет и консоль
Две равные двери
Всё, что владелец делает кнопкой в Маркете, разработчик делает командой — с тем же результатом и теми же проверками. Виды: theme, template, module, language.
php artisan market:list module --installed
php artisan market:install theme graphite
php artisan market:install template clinic --with=Forms --with=Comments
php artisan market:remove module Comments
php artisan market:remove template clinic --with-data
php artisan updates:apply --release=3.10.41 # только план
php artisan updates:apply --release=3.10.41 --yes # установить
| Команда | Что делает |
|---|---|
market:list | Что стоит и что можно поставить; --installed и --available сужают список |
market:install | Ставит тему, шаблон, модуль или язык; --with заодно ставит рекомендованные модули |
market:remove | Снимает кирпич. Данные модуля остаются в базе; --with-data удаляет и таблицы, а материалы снятого шаблона переводит на «Новости» |
updates:versions | Доступные версии и куда можно вернуться |
updates:apply | Без --yes показывает план и ничего не меняет; --release, --file и --module выбирают, что ставить |
security:watch | Сверяет файлы с описью и сообщает о подброшенных и изменённых; --accept принимает текущие находки как ваши правки |
lang:check | Сверяет словари всех языков с эталонным русским |
Поставки и подпись
Обновление — сравнение двух описей, а не копирование поверх
Канал обновлений — это канал исполнения кода сразу на всех сайтах покупателей. Поэтому он устроен строже, чем обычно.
Как обычно
- Архив копируется поверх сайта
- Файл, убранный в новой версии, остаётся и продолжает исполняться
- Правки владельца молча затираются
- Подпись проверяется после распаковки — или не проверяется вовсе
- Снятый модуль возвращается с первым же обновлением
В NEXUM-CORE
- Опись перечисляет все файлы с суммами: что добавить, заменить, удалить
- Файл, которого нет в новой версии, удаляется
- Файл, который правили вы, не трогается и показывается списком
- Подпись описи (RSA-SHA256) проверяется до распаковки
- Снятое записано и не вернётся, пока вы не поставите его снова
Проверок перед установкой две. Совместимость — карта: она гасит кнопку ещё до скачивания, если не хватает PHP, нужного модуля, места или прав на запись. Настоящий заслон — проверка по подписанной описи уже скачанной поставки с тремя исходами: отказ, предупреждение, порядок. Пустой открытый ключ не разрешает обновления без проверки, а запрещает их.
# так собирается каждая поставка: платформа, шаблон, тема, язык
php artisan release:build 3.10.41 --severity=feature --notes="..."
php artisan release:template clinic 1.3.0
php artisan release:theme graphite
php artisan release:language en 3.10.41
php artisan market:publish nexum-core-3.10.41.zip
Границы обновления
Чего обновление не коснётся
Не читается и не обновляется вовсе
.envи его варианты — настройки вашей установкиstorage/— загрузки, кеш, журналы, замок установкиpublic/uploads/— медиатекаvendor/— его ставит composer поcomposer.lock, отдельным процессом и только когда замок разошёлся
Обновляется, только пока вы его не правили
config/*.phppublic/.htaccesspublic/robots.txt
Безопасность в коде
Защита, которую не нужно включать
Включено с первой установки
Большая часть рубежей стоит в самом коде и работает без настройки.
- Выполняется только index.php.
public/.htaccessне даст запустить подброшенный PHP-файл. - PHP в содержимом выключен. Включается только явным
CONTENT_PHP=trueв.env. - Сторож целостности. Раз в час сверяет описи платформы, модулей, шаблонов и языков и ищет посторонний PHP; о находке сообщает один раз — в журнал безопасности и письмом администраторам.
- Автономный режим. Запрет исходящих запросов стоит на самом сетевом клиенте, а не в каждом сервисе по отдельности.
- Строка PHP в сводке. Панель смотрит ветку PHP и журнал пакета Debian или Ubuntu — видно, закрыта ли уязвимость, даже если номер версии старый.

Соглашения
Правила, по которым написан код
Их немного, и за каждым — грабли, на которые уже наступали.
- Только PostgreSQL. С другим драйвером приложение не запустится и скажет почему. Тесты идут на SQLite, поэтому миграции пишутся построителем схемы, без сырого SQL.
- Строки интерфейса — только через словари
resources/lang/ruиenс одинаковыми ключами; расхождения ловитlang:check. - Комментарии не доходят до браузера. Служебные пояснения вырезаются при компиляции Blade.
- Скрипты в браузере — только с латинскими именами.
- Правите модуль или шаблон — поднимите версию в
module.jsonили карточке шаблона: исправление — третья цифра, новое — вторая. - Компонент модуля в ядре выводится только через
<x-dynamic-component>с проверкой, что модуль стоит.
Ядро не знает кирпич по имени: только реестры и проверка, что класс существует.
Вопросы
Что спрашивают разработчики
Можно ли писать свои модули?
Да. make:module создаёт каркас, а сам модуль — обычный сервис-провайдер Laravel, объявленный в module.json. Он разрабатывается и обновляется отдельно от ядра.
Сторож целостности отметит мой модуль как чужой код?
Да, в первый раз: исполняемый PHP, которого нет ни в одной описи, — это находка. Нажмите «Это мои правки» в сводке или запустите security:watch --accept. Принятое запоминается по сумме: изменится файл ещё раз — сторож снова скажет.
Затрёт ли обновление платформы мои правки в файлах ядра?
Нет. Файл, который разошёлся с описью, обновление пропускает и показывает списком. Но свою логику удобнее держать в модуле: тогда правки ядра не нужны вовсе.
Можно ли откатить неудачное обновление?
Да. Перед заменой файлов снимается копия для возврата назад, включая vendor/, если менялись библиотеки. Куда можно вернуться, показывает updates:versions.
Почему нельзя MySQL, хотя бы для разработки?
Потому что боевая база — PostgreSQL, и запрос, написанный под другую базу, упадёт в худший момент. Заслон стоит при запуске приложения; для тестов разрешён только SQLite.
Где подробности устройства?
На этой странице и в частых вопросах. Исходный код открыт и читается как обычный Laravel-проект, а нестандартную задачу можно разобрать с автором системы напрямую.