Корневые скрипты

Корневые скрипты — это точки входа вашего сайта. Как правило, это index.php и cron.php. Их задача минималистична: инициализировать платформу, определить, какой модуль нужно запустить, и опубликовать результат. Никакой бизнес-логики в корневых скриптах нет — она вся находится в модулях.

Единственное ручное подключение файла во всём проекте — units/melbis.php первой строкой. Что именно он инициализирует, описано в разделе «melbis.php».

index.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();

Загружает пользовательские константы из таблицы {DBNICK}_self_key_value в пространство 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)];
}

Определение точки входа — имени модуля, который будет запущен первым. Логика проста:


// 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();

Финальная тройка вызовов присутствует в каждом корневом скрипте:

Полный пример index.php

<?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;

Без этого переменная тихо останется пустой — ошибки не будет, поломка обнаружится позже и в другом месте.

cron.php

Если проект использует периодические задачи — рассылки, очистку данных, автоматические импорты, — рядом с index.php появляется второй корневой скрипт, cron.php. Он так же начинается с require 'units/melbis.php', но вместо Run объявляет расписание методом CronAdd и запускает подошедшие задачи вызовом CronRun. Прямой доступ к нему из браузера закрыт правилом .htaccess.

Формат расписания, устройство cron-модуля, защита от вызова извне, журналы задач и обработка ошибок — в разделе «Планировщик задач».

.htaccess

Файл .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-редиректы и правила кеширования статики.