2. Руководство разработчика › 2.3 Скрипты › Модули-библиотеки

Модули-библиотеки — это модульные скрипты группы 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 — временные таблицы

Этот модуль содержит одну функцию — 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);
    ...
}

melbis_inc_web_callback — колбэки шаблонизатора

Этот модуль регистрирует модификаторы шаблонизатора. Колбэк — это 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 содержит все функции по работе с заказами: создание, загрузку, редактирование, калькуляцию, добавление и удаление товаров, расчёт скидок, уведомления:

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 типичны и другие библиотеки:

Любую логику, которая нужна более чем в одном модуле, стоит выносить в библиотеку.