Руководство Melbis Shop: оглавление
Корневые скрипты — это точки входа вашего сайта. Как правило, это
index.php и cron.php, а для AI-агента —
agent.php. Их задача минималистична: инициализировать
платформу, определить, какой модуль нужно запустить, и опубликовать
результат. Никакой бизнес-логики в корневых скриптах нет — она вся
находится в модулях.
Единственное ручное подключение файла во всём проекте —
units/melbis.phpпервой строкой. Что именно он инициализирует, описано в разделе «melbis.php».
Разберём структуру типового корневого скрипта построчно:
// Melbis start
require 'units/melbis.php';Подключение units/melbis.php инициализирует всё
окружение платформы: загружает config.json, настраивает
обработчики ошибок, регистрирует автозагрузчик классов и готовит
соединение с базой данных. Само соединение открывается при первом
запросе к базе, поэтому страница, целиком взятая из кеша, обходится без
него. После этой строки доступна функция MELBIS(),
возвращающая единственный экземпляр парсера. Подробнее о том, что делает
melbis.php — в конце этого раздела.
// Define session
MELBIS()->DefineSession('MELBIS_SHOP');Инициализация PHP-сессии. Параметр — уникальное имя сессии для этого
проекта. Помимо сессии, этот вызов автоматически активирует CSRF-токен и
проверяет наличие в URL параметра отладчика: если передан
?debug_on_KEY и KEY совпадает с
MELBIS_DEBUG_CODE из конфигурации — включается режим
отладки. Полная сигнатура и работа с сессией — в разделе «Сессии и
CSRF».
// Define self constants
MELBIS()->DefineSelfConst();Загружает пользовательские константы в пространство PHP-констант. Вызов необязателен, если в проекте нет пользовательских констант. Подробнее — в разделе «Конфигурация».
// Entry point
if ( isset($_GET['lazy']) )
{
$entry_point = $_POST['mod'];
$entry_param = $_POST['params'];
}
else
{
$entry_point = $_GET['mod'] ?? 'melbis_base_page';
$entry_param = [serialize($_GET), serialize($_POST)];
}Определение точки входа — имени модуля, который будет запущен первым. Логика проста:
mod. Если параметр не передан, запускается модуль по
умолчанию (как правило, роутер страниц).?lazy (асинхронная загрузка)
имя модуля и параметры берутся из POST-данных. Этот механизм
задействуется системой отложенной загрузки — подробнее в разделе
«Отложенная загрузка».// Entry point check
$entry_exists = MELBIS()->UnitExists($entry_point, true);
if ( !$entry_exists )
{
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
$entry_point = 'melbis_base_404';
}Имя модуля пришло из адреса, то есть его выбрал не разработчик, а
посетитель — и совпадать с реальным модулем оно не обязано.
UnitExists($mName, $mCheckEntry = false)
проверяет, что модуль с таким именем в проекте есть, и возвращает
true или false.
Второй аргумент добавляет к проверке ещё одно условие: модуль должен
быть разрешён как точка входа — отмечен галочкой «Точка
входа» в IDE либо включён в отложенную загрузку. Правило то же, по
которому парсер решает, запускать ли модуль из корневого скрипта,
поэтому вердикт UnitExists и поведение Run
всегда совпадают.
Без этой проверки Run с несуществующим именем
останавливает разбор ошибкой парсера: посетитель увидит служебную
страницу, а поисковый робот получит её с кодом 200 и
проиндексирует. Проверка стоит одного обращения к файловой системе и
превращает это в нормальный 404.
Имя обеззараживается внутри метода ровно так же, как перед запуском
модуля, — подделать путь через ../ не выйдет.
Та же проверка нужна везде, где имя модуля приходит снаружи: в обработчике cron-запросов и в скрипте отложенной загрузки.
// Run
MELBIS()->Run($entry_point, $entry_param);
// Publish
MELBIS()->Publish();
// Possible report
MELBIS()->Report();Финальная тройка вызовов присутствует в каждом корневом скрипте:
Run() — запускает указанный модуль,
получает от него HTML-результат и рекурсивно обрабатывает все вложенные
вызовы модулей из шаблонов.Publish() — отправляет заголовок
Content-Type (если он ещё не был отправлен) и выводит
готовый HTML-код страницы. Также добавляет заголовок
Server-Timing с данными о времени компиляции — удобно при
профилировании через DevTools. Парный Fetch() возвращает
тот же HTML строкой, ничего не печатая, — он нужен, когда страницу
подменяют из кода модуля (см. «Служебные методы»).Report() — если активирован режим
отладки, выводит после страницы панель отладчика со статистикой по
модулям, SQL-запросам и кешированию. Если отладчик не включён, метод
ничего не делает.<?php
// Melbis start
require 'units/melbis.php';
// Define session
MELBIS()->DefineSession('MELBIS_SHOP');
// Define self constants
MELBIS()->DefineSelfConst();
// Entry point
if ( isset($_GET['lazy']) )
{
$entry_point = $_POST['mod'];
$entry_param = $_POST['params'];
}
else
{
$entry_point = $_GET['mod'] ?? 'melbis_base_page';
$entry_param = [serialize($_GET), serialize($_POST)];
}
// Entry point check
$entry_exists = MELBIS()->UnitExists($entry_point, true);
if ( !$entry_exists )
{
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
$entry_point = 'melbis_base_404';
}
// Run
MELBIS()->Run($entry_point, $entry_param);
// Publish
MELBIS()->Publish();
// Possible report
MELBIS()->Report();Обычно библиотеки подключаются галочками в параметрах модуля через
IDE — так парсер знает не только сами файлы, но и таблицы, от которых
они зависят. Корневой скрипт модулем не является, и такой галочки у него
нет: если функция библиотеки нужна прямо здесь, до Run, её
подключают вызовом UnitInc:
MELBIS()->UnitInc('melbis_inc_logic');Имя передаётся без расширения, как и всем остальным методам движка. Зависимости самой библиотеки — те, что отмечены галочками в её параметрах, — подтягиваются рекурсивно, повторное подключение того же модуля игнорируется.
Только в корневом скрипте.
UnitIncподключает код, но не регистрирует таблицы библиотеки в системе отслеживания изменений. Для кешируемого модуля это тихая поломка: библиотека читает свои таблицы, кеш модуля о них не знает и при их изменении не сбрасывается — витрина продолжает показывать старые данные, пока кеш не истечёт по времени. В некешируемом модуле вызов отработает, но и там правильный способ — галочка в IDE. Отсюда правило: в модулях — только IDE, в корневом скрипте —UnitInc.
Тем же UnitInc удобно разбирать на части сам корневой
скрипт. Определение языка, выбор точки входа, запись в журнал — каждый
шаг живёт в своём inc-модуле, а index.php остаётся
оглавлением:
// Define lang
MELBIS()->UnitInc('melbis_inc_index_lang');
// Run
MELBIS()->UnitInc('melbis_inc_index_run');
// Log
MELBIS()->UnitInc('melbis_inc_index_log');Такой inc-модуль — обычный модуль-библиотека, созданный в среде разработки и видимый в общем дереве. Отличие только в том, что он не объявляет функции, а выполняет код сразу при подключении.
Переменные из подключённого файла наружу не выходят. Подключение выполняется внутри метода парсера, поэтому код файла работает в его области видимости, а не в области видимости
index.php. Всё, что должно пережить подключение, объявляется в самом файле черезglobal:global $gLang, $gLangPath;Без этого переменная тихо останется пустой — ошибки не будет, поломка обнаружится позже и в другом месте.
Если проект использует периодические задачи — рассылки, очистку
данных, автоматические импорты, — рядом с index.php
появляется второй корневой скрипт, cron.php. Он так же
начинается с require 'units/melbis.php', но вместо
Run объявляет расписание методом CronAdd и
запускает подошедшие задачи вызовом CronRun. Прямой доступ
к нему из браузера закрыт правилом .htaccess.
Формат расписания, устройство cron-модуля, защита от вызова извне, журналы задач и обработка ошибок — в разделе «Планировщик задач».
Третий корневой скрипт — точка входа AI-агента. Через неё MCP-сервер запускает модули агента: так агент делает работу, для которой в магазине нет AI-инструмента, — отчёт по нескольким таблицам, проверку всего каталога, разовую правку со своей логикой. Скрипт поставляется с магазином, и править его не нужно.
Как и index.php, он начинается с
require 'units/melbis.php', а затем запускает модуль,
только если:
agent_;units/;entry_point: 1 в манифесте, в
программе — «Модуль входной точки».Иначе скрипт отвечает отказом с кодом 403 или 404 и ничего не
запускает. Служебные поля запроса — login,
secret и mod — он убирает, а остальные отдаёт
модулю одним сериализованным аргументом: в манифесте модуля объявлено
post: serial, и значения лежат в
$mVars['post']. Ответ модуля проходит через парсер, как
любая страница.
С магазином поставляется образец —
units/agent_claude_sample.php. Он показывает, что точка
входа передаёт модулю, и на нём агент проверяет канал:
<?php
namespace AGENT_CLAUDE_SAMPLE;
function Main($mVars)
{
$post = $mVars['post'] ?? [];
$hello = $post['hello'] ?? '';
$how = (int)( $post['how'] ?? 0 );
$keys = array_keys($mVars);
$post_keys = array_keys($post);
$answer = [
'time' => date('Y-m-d H:i:s'),
'nick' => MELBIS_DB_NICK,
'keys' => $keys,
'post_keys' => $post_keys,
'hello' => $hello,
'doubled' => $how * 2
];
return json_encode($answer, JSON_UNESCAPED_UNICODE);
}
?>Запуск с параметрами hello: мир и how: 21
отвечает так:
{"time": "2026-10-03 12:47:00", "nick": "ms", "keys": ["post"],
"post_keys": ["hello", "how"], "hello": "мир", "doubled": 42}Как агент создаёт и запускает свои модули — «Витрина» и «shop_agent» в описании MCP-сервера.
Файл .htaccess решает несколько задач одновременно.
Минимальный набор правил, который должен присутствовать в каждом
проекте:
AddDefaultCharset UTF-8
DirectoryIndex index.html index.htm index.php
Options -Indexes
# Запрет прямого доступа к конфигурации
RewriteRule ^config.json$ - [F]
# Запрет прямого доступа к cron
RewriteRule ^cron.php$ - [F]
# Запрет прямого доступа к внутренним папкам движка
RewriteRule ^core/(cache|class|init|tmp|trick|log)/ - [F]
# Запрет прямого доступа к журналам в любом месте проекта
RewriteRule \.log$ - [F]
# Маршрут для отложенной загрузки модулей (Lazy Loading)
RewriteRule ^lazy/$ index.php?lazy [L,QSA]Правило ^core/(…)/ закрывает всё, что движок держит для
себя: классы (class), скомпилированный кеш
(cache), данные аналитики (trick), временные
файлы (tmp), SQL-скрипты установки (init) и
журналы (log). Снаружи там не нужно ничего и никогда. Точки
входа core/*.php, через которые с движком общается
программа Melbis Shop, правило не затрагивает — журналы читаются в
дереве скриптов программы, а не по адресу в браузере.
Правило \.log$ добирает всё остальное: дампы
TplDumpVar внутри шаблонов, журналы прежних версий движка,
оставшиеся в корне, и любой лог, который положит рядом с проектом
разработчик.
Правило lazy/ обязательно, если в проекте используется
отложенная загрузка модулей — оно перенаправляет все AJAX-запросы
платформы на index.php с признаком ?lazy.
При необходимости здесь же размещают ЧПУ-маршруты, HTTPS-редиректы и правила кеширования статики.
Ошибка в .htaccess ломает весь сайт сразу, вместе с
точками входа core/*.php, через которые его можно было бы
исправить. Поэтому движок, сохранив .htaccess из программы
или от AI-агента, тут же спрашивает свой сайт с того же сервера: главную
страницу и core/mcp.php. Файл остаётся, только если главная
ответила нормально, а core/mcp.php — кодом 200. Ошибка,
закрытая точка входа или молчание сервера возвращают прежний файл, и
сохранение получает отказ.