Руководство Melbis Shop: оглавление
Модули-библиотеки — это модульные скрипты группы inc. В
отличие от обычных модулей, парсер их не вызывает и шаблонов у них нет.
Их задача — содержать общие функции, которые используются сразу
несколькими обычными модулями. Это позволяет вынести повторяющуюся
логику в одно место и не дублировать её в каждом модуле отдельно.
В имени библиотеки после inc идёт её собственная группа
— melbis_inc_web_callback.php относится к группе
web. По ней библиотеки и раскладываются в дереве «Среды
разработки», рядом с модулями своей части проекта (см. «Принятые
обозначения»).
Чтобы подключить библиотеку к обычному модулю, достаточно поставить галочку напротив неё в правой панели IDE — парсер загрузит её автоматически перед запуском модуля, и все её функции станут доступны.
Библиотека, как и обычный модуль, может объявить собственное пространство имён — тогда её функции теряют приставку, а вызывающие получают право звать их коротко (см. «Модульные скрипты»):
<?php
namespace MELBIS_INC_LOGIC;
function OrderCreate($mOrder)
{
...
}Вторая половина дела — короткое имя, под которым библиотеку
будут звать. Его объявляет сама библиотека, и потому оно
одинаково во всём магазине: LOGIC\OrderCreate читается
одинаково в любом файле. Пишется имя в поле параметров над редактором: у
обычного модуля там объявлены входные параметры, а у библиотеки их нет,
и поле занято именно этим.
Имя одно. Список через запятую движок при сохранении
не примет и ответит One alias only, показав, что написано в
поле. Одинаковые имена у разных библиотек не запрещены: столкнутся они
только в файле, который подключил обе, и там решает автор файла.
Поле можно оставить пустым — тогда библиотека в коротких вызовах не участвует, и её зовут полным именем пространства. Это законный вариант, никаких предупреждений он не вызывает.
Заполнить имя, но не объявить namespace, нельзя: при
сохранении движок предупредит Namespace missing, потому что
импортировать в таком случае нечего.
Объявленное имя — рекомендация для всего магазина, а не запрет.
Модуль вправе дать библиотеке в своём файле любое имя, но при сохранении
получит Alias differs с обоими вариантами: в файле и в
манифесте библиотеки. Предупреждение не мешает сохранению — оно нужно,
чтобы одну библиотеку в разных файлах не звали по-разному, когда на то
нет причины.
Рассмотрим три характерных примера из демонстрационного магазина.
Этот модуль содержит одну функцию —
MELBIS_INC_WEB_TOPIC_Sub. Её задача: построить временную
таблицу в памяти со всеми подразделами заданного раздела, рекурсивно
обходя дерево категорий.
function MELBIS_INC_WEB_TOPIC_Sub($mId)
{
$command = "CREATE TEMPORARY TABLE {DBNICK}_topic_sub ENGINE=MEMORY
WITH RECURSIVE topic_sub AS (
SELECT t.tindex, t.id
FROM {DBNICK}_topic t
WHERE t.id = :ID
UNION ALL
SELECT ts.tindex, t.id
FROM topic_sub ts
JOIN {DBNICK}_topic t ON ts.id = t.tindex
)
SELECT * FROM topic_sub";
$param = [
'id' => $mId
];
MELBIS()->SqlQuery(__LINE__, $command, $param);
}Зачем это нужно? Когда модуль выводит список товаров в разделе, он
должен учитывать не только товары самого раздела, но и товары всех его
подразделов. Та же таблица понадобится модулю фильтров по
характеристикам. Вместо того чтобы писать рекурсивный CTE в каждом из
этих модулей, достаточно один раз вызвать
MELBIS_INC_WEB_TOPIC_Sub из библиотеки — и временная
таблица готова к использованию в последующих запросах:
// В модуле melbis_store_topic:
function MELBIS_STORE_TOPIC($mVars)
{
$id = $mVars['id'];
// Создать временную таблицу подразделов
MELBIS_INC_WEB_TOPIC_Sub($id);
// Теперь можно делать JOIN с {DBNICK}_topic_sub
$command = "SELECT s.id
FROM {DBNICK}_topic_sub t_sub
JOIN {DBNICK}_topic t
ON t_sub.id = t.id
JOIN {DBNICK}_topic_store ts
ON ts.topic_id = t.id
JOIN {DBNICK}_store s
ON ts.store_id = s.id
WHERE s.no_visible = 0
ORDER BY t.absindex, ts.pos
LIMIT 100
";
$goods = MELBIS()->SqlSelect(__LINE__, $command);
...
}Этот модуль регистрирует модификаторы шаблонизатора. Колбэк — это PHP-функция, которую можно вызывать прямо из HTML-шаблона как модификатор переменной.
Регистрация вынесена в функцию-обёртку, а сам колбэк — рядом с ней:
// В библиотеке melbis_inc_web_callback:
function MELBIS_INC_WEB_CALLBACK()
{
MELBIS()->DefineCallback('PageLink');
}
function MELBIS_INC_WEB_CALLBACK_PageLink($mVars)
{
$link = ( $mVars['kind_key'] == 'kLink' ) ? $mVars['link'] : '/?topic_id='.$mVars['id'];
return $link;
}Имя функции в регистрации не написано: движок ищет
PageLink в том же файле, где стоит строка регистрации, —
здесь это MELBIS_INC_WEB_CALLBACK_PageLink, а в библиотеке
с пространством имён была бы
MELBIS_INC_WEB_CALLBACK\PageLink. Считается именно файл с
регистрацией, а не модуль, который вызвал обёртку.
Назвать функцию явно нужно, только если её имя отличается от имени модификатора или она лежит в другом файле:
MELBIS()->DefineCallback('PageLink', MELBIS_INC_WEB_CALLBACK_PageLink(...));Три точки — не сокращение записи в примере, а синтаксис PHP: так
функция передаётся как значение, не вызываясь, и имя разрешает
компилятор, а не строка в кавычках. Строковая форма
('MELBIS_INC_WEB_CALLBACK_PageLink') тоже работает, но при
переводе библиотеки в пространство имён указывает в пустоту. Любую из
форм движок проверяет сразу при регистрации, так что несуществующее имя
до витрины не доживёт.
Обёртку вызывает модуль верхнего уровня — в теле файла, до своей основной функции:
// В модуле-роутере страницы, например melbis_base_page:
MELBIS_INC_WEB_CALLBACK();
function MELBIS_BASE_PAGE($mVars)
{
// ...
}Одного такого вызова достаточно на всю страницу. Реестр колбэков
общий на весь запрос, и каждый следующий модуль получает его копию в
момент своего подключения — поэтому в шаблонах вложенных модулей
(melbis_cataloge, карточка товара и прочие) модификатор
работает сам собой, и присоединять к ним библиотеку не
нужно.
Почему в теле файла, а не внутри функции модуля: тело выполняется при подключении модуля на каждом запросе, а функция модуля при отдаче из кеша не запускается вовсе — и регистрация не состоялась бы.
После этого в любом шаблоне становится доступен модификатор
$PageLink, который вычисляет правильный URL раздела в
зависимости от его типа:
{#MENU}
<a href="{ID|$PageLink:KIND_KEY,LINK}">{NAME|html}</a>
{MENU#}Имя модификатора — регистрозависимый ключ. По соглашению оно
совпадает с Pascal-хвостом функции: одно имя ищется поиском во всех трёх
местах — в шаблоне, в регистрации и в объявлении. Заодно в теге читаются
три класса имён: заглавные ID, KIND_KEY —
данные, строчные path, webp — встроенные
модификаторы, $PageLink — функция модуля.
Именно так это и сделано в демонстрационном магазине: библиотеку
объявляет только melbis_base_page, а модификатор
используется в шаблонах melbis_cataloge и
melbis_cataloge_sub, где в списке библиотек не отмечено
ничего.
Регистрация должна успеть до подключения. Модуль получает те колбэки, которые зарегистрированы к моменту, когда подключили его самого. Родительский модуль успевает всегда: его PHP отрабатывает до разбора шаблона, а вложенные модули подключаются уже во время разбора. А вот сосед, зарегистрировавший колбэк позже, на уже подключённый модуль не повлияет — регистрируйте выше по дереву, а не сбоку.
Таблицы библиотеки достаются только тому модулю, который её объявил. Если колбэк читает данные из базы, эти таблицы попадут в список зависимостей кеша только у верхнего модуля; вложенные о них не узнают и при их изменении не пересоберутся. Для колбэков, которые лишь форматируют переданные значения, это неважно, а читающую из базы библиотеку стоит присоединить и к тем модулям, где она используется.
Подробнее о модификаторах и синтаксисе колбэков — в разделе «Модификаторы».
Это наиболее значимый тип библиотечного модуля.
melbis_inc_logic содержит все функции по работе с заказами:
создание, загрузку, редактирование, калькуляцию, добавление и удаление
товаров, расчёт скидок, уведомления:
MELBIS_INC_LOGIC_OrderCreate — создать новую версию заказа
MELBIS_INC_LOGIC_OrderLoad — загрузить текущую версию
MELBIS_INC_LOGIC_OrderEdit — открыть заказ для редактирования
MELBIS_INC_LOGIC_OrderCalc — рассчитать итоговые суммы
MELBIS_INC_LOGIC_OrderGoodsAdd — добавить товар в заказ
MELBIS_INC_LOGIC_OrderGoodsRemove — удалить товар из заказа
MELBIS_INC_LOGIC_OrderGoodsDiscount — рассчитать скидки
MELBIS_INC_LOGIC_NotifyEvents — проверить системные события
Эти функции вызываются из модуля корзины на витрине сайта:
// В модуле melbis_basket:
$version = MELBIS()->SessionGetValue('order') ?? MELBIS_INC_LOGIC_OrderCreate();
$version = MELBIS_INC_LOGIC_OrderGoodsAdd($version, $store_id);
$version = MELBIS_INC_LOGIC_OrderCalc(null, $version);Та же корзина, если библиотека объявила пространство имён, а модуль подключил её галочкой:
// В модуле melbis_basket:
namespace MELBIS_BASKET;
use MELBIS_INC_LOGIC as LOGIC;
...
$version = MELBIS()->SessionGetValue('order') ?? LOGIC\OrderCreate();
$version = LOGIC\OrderGoodsAdd($version, $store_id);
$version = LOGIC\OrderCalc(null, $version);Одно уточнение к настройке ниже: функция, которую зовёт
программа, хранится строкой в реестре настроек. Переезд
библиотеки в пространство имён на неё не распространяется — имя в
настройке нужно поправить руками на
MELBIS_INC_LOGIC\OrderCalc.
Ключевое преимущество этого подхода — единая бизнес-логика
для сайта и настольной программы. Те же самые функции из
melbis_inc_logic вызываются программой Melbis Shop, когда
менеджер работает с заказами через Windows-клиент. Настройка вызываемых
функций выполняется в программе через меню «Проектирование →
Реестр настроек», вкладка «Базовые настройки», раздел «Вызываемые
модули». Например, в разделе «Заказы → Калькуляция» указывается
имя библиотеки и имя функции, которую программа должна вызывать для
расчёта заказа.
Таким образом, покупатель, оформляющий заказ через сайт, и менеджер, редактирующий его в программе, работают через один и тот же PHP-код — без риска расхождения логики и ошибок синхронизации.
Помимо перечисленных, в проектах на платформе Melbis типичны и другие библиотеки:
inc_auth — проверка авторизации
клиента, вход, выход, работа с сессией.inc_email — отправка
email-уведомлений.inc_telegram — отправка сообщений
через Telegram Bot API.inc_curl — HTTP-запросы к внешним
API.inc_lang — работа с языковыми
настройками в мультиязычных проектах.Любую логику, которая нужна более чем в одном модуле, стоит выносить в библиотеку.