Руководство Melbis Shop: оглавление
Модули-библиотеки — это модульные скрипты группы inc. В
отличие от обычных модулей, парсер их не вызывает и шаблонов у них нет.
Их задача — содержать общие функции, которые используются сразу
несколькими обычными модулями. Это позволяет вынести повторяющуюся
логику в одно место и не дублировать её в каждом модуле отдельно.
В имени библиотеки после inc идёт её собственная группа
— melbis_inc_page_callback.php относится к группе
page. По ней библиотеки и раскладываются в дереве «Среды
разработки», рядом с модулями своей части проекта (см. «Принятые
обозначения»).
Чтобы подключить библиотеку к обычному модулю, достаточно поставить галочку напротив неё в правой панели IDE — парсер загрузит её автоматически перед запуском модуля, и все её функции станут доступны.
Библиотека подключает другие библиотеки теми же галочками. Парсер
загружает их по дереву подключений, каждую один раз, и всё, от чего
библиотека зависит, загружено раньше её самой. Взаимные галочки двух
библиотек загрузчик допускает, и функции друг друга они вызывают без
помех. Но первой в таком круге загружается та, до которой обход дошёл
позже, а это зависит от галочки модуля. Поэтому код в теле файла такой
библиотеки, вне функций, не вызывает функций другой: в одном модуле он
сработает, в другом остановится на
Call to undefined function.
Библиотека, как и обычный модуль, объявляет собственное пространство имён — имя своего файла в верхнем регистре — и называет функции коротко, без приставок (см. «Модульные скрипты»):
<?php
namespace MELBIS_INC_LOGIC_ORDER;
function Create()
{
...
}Вторая половина дела — короткое имя, под которым библиотеку
будут звать. Его объявляет сама библиотека, и потому оно
одинаково во всём магазине: LOGIC_ORDER\Create читается
одинаково в любом файле. Пишется имя в поле параметров над редактором: у
обычного модуля там объявлены входные параметры, а у библиотеки их нет,
и поле занято именно этим.
Имя одно. Список через запятую движок при сохранении
не примет и ответит One alias only, показав, что написано в
поле. Одинаковые имена у разных библиотек не запрещены: столкнутся они
только в файле, который подключил обе, и там решает автор файла.
Поле можно оставить пустым — тогда библиотека в коротких вызовах не участвует, и её зовут полным именем пространства. Это законный вариант, никаких предупреждений он не вызывает.
Заполнить имя, но не объявить namespace, нельзя: при
сохранении движок предупредит Namespace missing, потому что
импортировать в таком случае нечего.
Объявленное имя — рекомендация для всего магазина, а не запрет.
Модуль вправе дать библиотеке в своём файле любое имя, но при сохранении
получит Alias differs с обоими вариантами: в файле и в
манифесте библиотеки. Предупреждение не мешает сохранению — оно нужно,
чтобы одну библиотеку в разных файлах не звали по-разному, когда на то
нет причины.
Библиотеку, которую модуль подключает, но не вызывает, объявляют
строкой use с полным именем и без as. Такая
библиотека работает самим подключением — например, код в теле её файла
регистрирует колбэки для вложенных модулей. Без этой строки движок при
сохранении предупредит Unused: библиотека подключена, а ни
одна её функция в модуле не вызывается.
Рассмотрим три характерных примера из демонстрационного магазина.
Короткое имя этой библиотеки — PAGE_MENU. Её функция
Sub строит временную таблицу в памяти со всеми подразделами
заданного раздела, рекурсивно обходя дерево категорий. Вторая функция,
Menu, отдаёт разделы одного меню — её зовут
melbis_cataloge и melbis_cataloge_sub.
namespace MELBIS_INC_PAGE_MENU;
function 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 в каждом из
этих модулей, достаточно один раз вызвать PAGE_MENU\Sub из
библиотеки — и временная таблица готова к использованию в последующих
запросах:
// В модуле melbis_page_topic:
namespace MELBIS_PAGE_TOPIC;
use MELBIS_INC_PAGE_MENU as PAGE_MENU;
function Main($mVars)
{
$id = $mVars['id'];
// Создать временную таблицу подразделов
PAGE_MENU\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
GROUP BY s.id
ORDER BY MIN(t.absindex), MIN(ts.pos)
LIMIT 100
";
$goods = MELBIS()->SqlSelect(__LINE__, $command);
...
}Этот модуль регистрирует модификаторы шаблонизатора. Колбэк — это PHP-функция, которую можно вызывать прямо из HTML-шаблона как модификатор переменной.
Регистрация стоит в теле файла библиотеки, а колбэки — ниже, рядом с ней:
// В библиотеке melbis_inc_page_callback:
namespace MELBIS_INC_PAGE_CALLBACK;
// Register the template callbacks
MELBIS()->DefineCallback('TopicLink');
MELBIS()->DefineCallback('StoreLink');
MELBIS()->DefineCallback('StatusName');
function TopicLink($mVars)
{
$link = ( $mVars['kind_key'] == 'kLink' ) ? $mVars['link'] : '/?topic_id='.$mVars['id'];
return $link;
}Имя функции в регистрации не написано: движок ищет
TopicLink в том же файле, где стоит строка регистрации, —
здесь это MELBIS_INC_PAGE_CALLBACK\TopicLink, а в
библиотеке без пространства имён —
MELBIS_INC_PAGE_CALLBACK_TopicLink. Поэтому короткая
регистрация стоит в самой библиотеке: в файле модуля та же строка искала
бы функцию модуля.
Назвать функцию явно нужно, только если её имя отличается от имени модификатора или она лежит в другом файле:
MELBIS()->DefineCallback('TopicLink', PAGE_CALLBACK\TopicLink(...));Три точки — не сокращение записи в примере, а синтаксис PHP: так
функция передаётся как значение, не вызываясь, и имя разрешает
компилятор вместе со строкой use, а не строка в кавычках.
Строковая форма ('MELBIS_INC_PAGE_CALLBACK\TopicLink') тоже
работает, но короткого имени из use она не знает: имя в
кавычках пишут полностью, и при переезде функции оно остаётся прежним.
Любую из форм движок проверяет сразу при регистрации, так что
несуществующее имя до витрины не доживёт.
Тело файла библиотеки выполняется один раз за запрос — когда её подключает первый модуль. Модуль подключается на каждом запросе, даже когда его результат берётся из кеша, поэтому регистрация стоит в теле файла, а не внутри функции: функция при отдаче из кеша не запускалась бы вовсе.
Библиотеку подключает каждый модуль, в чьём шаблоне стоит
модификатор: галочкой в правой панели и строкой
use с полным именем и без as. Функции
библиотеки модуль не вызывает — она работает самим подключением:
// В модуле меню разделов melbis_cataloge:
namespace MELBIS_CATALOGE;
use MELBIS_INC_PAGE_MENU as PAGE_MENU;
use MELBIS_INC_PAGE_CALLBACK;Так модификатор работает везде, где модуль выведен: на странице, в шапке другой точки входа и в ответе на отдельный запрос, когда страницы вокруг модуля нет вовсе. Модулю, который сам модификаторов не использует, библиотека не нужна, даже если внутри него выводятся модули с модификаторами: они подключат её сами.
Реестр колбэков общий на весь запрос: модуль получает те колбэки, что
зарегистрированы к моменту его подключения. Собственные библиотеки
модуля подключаются раньше него самого, поэтому своя строка
use успевает всегда.
В шаблоне такого модуля доступен модификатор $TopicLink,
который вычисляет правильный URL раздела в зависимости от его типа:
{#MENU}
<a href="{ID|$TopicLink:KIND_KEY,LINK}">{NAME|html}</a>
{MENU#}Имя модификатора — регистрозависимый ключ. По соглашению оно
совпадает с именем функции: одно имя ищется поиском во всех трёх местах
— в шаблоне, в регистрации и в объявлении. Заодно в теге читаются три
класса имён: заглавные ID, KIND_KEY — данные,
строчные path, webp — встроенные модификаторы,
$TopicLink — функция модуля.
Так это сделано в демонстрационном магазине: $TopicLink
стоит в шаблонах меню melbis_cataloge и подменю
melbis_cataloge_sub, $StoreLink и
$StatusName — в карточке товара
melbis_store_card и на странице товара
melbis_page_store, и каждый из них подключает библиотеку
сам. Подменю зовут и отдельным запросом
/?mod=melbis_cataloge_sub, без страницы вокруг, а меню
разделов выводится и в шапке веб-модуля melbis_web_sample.
Роутер страницы melbis_base_page библиотеку не подключает:
в его шаблонах модификаторов нет.
Колбэк, читающий базу. Таблицы, которые колбэк читает через двери движка, попадают в зависимости кеша того модуля, в чьём шаблоне он сработал. Какое чтение колбэку допустимо — «Модификаторы» → «Колбэк и база данных».
Подробнее о модификаторах и синтаксисе колбэков — в разделе «Модификаторы».
Это наиболее значимый тип библиотечного модуля. Работа с заказом в
демонстрационном магазине разложена по библиотекам группы
logic: создание и загрузка заказа, добавление и удаление
товаров, скидки, расчёт, запись:
| Библиотека | Короткое имя | Функции |
|---|---|---|
melbis_inc_logic_order |
LOGIC_ORDER |
версия заказа: Create — новая, Load —
текущая, GoodsAdd и GoodsRemove — добавить и
удалить товар, GoodsSum — сумма товаров,
GoodsDiscount — скидка на товар, OptionSet —
опция заказа и другие |
melbis_inc_logic_order_calc |
LOGIC_ORDER_CALC |
Run — рассчитать версию заказа |
melbis_inc_logic_order_edit |
LOGIC_ORDER_EDIT |
Run — записать версию заказа |
melbis_inc_logic_common |
LOGIC_COMMON |
Rate — курс валюты, Price — сумма в валюте
магазина |
Эти функции вызываются из модуля корзины на витрине сайта:
// В модуле melbis_basket:
namespace MELBIS_BASKET;
use MELBIS_INC_LOGIC_ORDER as LOGIC_ORDER;
use MELBIS_INC_LOGIC_ORDER_CALC as LOGIC_ORDER_CALC;
...
$version = MELBIS()->SessionGetValue('order') ?? LOGIC_ORDER\Create();
$version = LOGIC_ORDER\GoodsAdd($version, $store_id);
$version = LOGIC_ORDER_CALC\Run(null, $version);Ключевое преимущество этого подхода — единая бизнес-логика
для сайта и настольной программы. Те же самые функции библиотек
logic вызываются программой Melbis Shop, когда менеджер
работает с заказами через Windows-клиент. Настройка вызываемых функций
выполняется в программе через меню «Проектирование → Реестр
настроек», вкладка «Базовые настройки», раздел «Вызываемые
модули». Например, в разделе «Заказы → Калькуляция» указывается
библиотека melbis_inc_logic_order_calc.php и функция,
которую программа должна вызывать для расчёта заказа, —
MELBIS_INC_LOGIC_ORDER_CALC\Run. Имя функции
хранится строкой и пишется полностью, с пространством имён: код
о нём не знает, и при переезде функции его правят руками.
Таким образом, покупатель, оформляющий заказ через сайт, и менеджер, редактирующий его в программе, работают через один и тот же PHP-код — без риска расхождения логики и ошибок синхронизации.
Помимо перечисленных, в проектах на платформе Melbis типичны и другие библиотеки:
inc_auth — проверка авторизации
клиента, вход, выход, работа с сессией.inc_email — отправка
email-уведомлений.inc_telegram — отправка сообщений
через Telegram Bot API.inc_curl — HTTP-запросы к внешним
API.inc_lang — работа с языковыми
настройками в мультиязычных проектах.Любую логику, которая нужна более чем в одном модуле, стоит выносить в библиотеку.