Library Modules

Library modules are modular scripts belonging to the inc group. Unlike regular modules, the parser does not call them and they have no templates. Their purpose is to contain shared functions used by several regular modules at once. This allows repetitive logic to be placed in a single location rather than duplicated across individual modules.

In a library’s name, after inc comes its own group — melbis_inc_web_callback.php belongs to the web group. Libraries are organized by this group in the Development Environment tree, alongside the modules of their respective project section (see “Accepted Conventions”).

To connect a library to a regular module, simply check the box next to it in the IDE’s right panel — the parser will load it automatically before running the module, making all its functions available.

Let’s look at three characteristic examples from the demonstration store.

melbis_inc_web_topic — Temporary Tables

This module contains a single function — MELBIS_INC_WEB_TOPIC_sub. Its purpose: to build an in-memory temporary table containing all subsections of a given section, recursively traversing the category tree.

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);
}

Why is this needed? When a module displays a list of products in a section, it must account not only for products in that section itself, but also for products in all its subsections. The same table will be needed by the attribute filter module. Instead of writing a recursive CTE in each of these modules, it is sufficient to call MELBIS_INC_WEB_TOPIC_sub from the library once — and the temporary table is ready for use in subsequent queries:

// In the melbis_store_topic module:
function MELBIS_STORE_TOPIC($mVars)
{
    $id = $mVars['id'];

    // Create a temporary table of subsections
    MELBIS_INC_WEB_TOPIC_sub($id);

    // Now we can JOIN with {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 — Template Engine Callbacks

This module registers template engine modifiers. A callback is a PHP function that can be called directly from an HTML template as a variable modifier.

Registration is placed in a wrapper function, with the callback itself defined alongside it:

// In the melbis_inc_web_callback library:
function MELBIS_INC_WEB_CALLBACK()
{
    MELBIS()->DefineCallback('page_link', 'MELBIS_INC_WEB_CALLBACK_page_link');
}

function MELBIS_INC_WEB_CALLBACK_page_link($mVars)
{
    $link = ( $mVars['kind_key'] == 'kLink' ) ? $mVars['link'] : '/?topic_id='.$mVars['id'];

    return $link;
}

The wrapper is called by the top-level module — in the file body, before its main function:

// In the page router module, e.g. melbis_base_page:
MELBIS_INC_WEB_CALLBACK();

function MELBIS_BASE_PAGE($mVars)
{
    // ...
}

A single such call is sufficient for the entire page. The callback registry is shared across the entire request, and each subsequent module receives a copy of it at the moment it is connected — so in the templates of nested modules (melbis_cataloge, the product card, and others) the modifier works on its own, and there is no need to attach the library to them.

Why in the file body rather than inside the module function: the body executes when the module is connected on every request, whereas the module function is not executed at all when served from cache — and the registration would never take place.

After this, the $page_link modifier becomes available in any template, computing the correct URL for a section based on its type:

{#MENU}
    <a href="{ID|$page_link:KIND_KEY,LINK}">{NAME|html}</a>
{MENU#}

This is exactly how it is done in the demonstration store: the library is declared only by melbis_base_page, while the modifier is used in the templates of melbis_cataloge and melbis_cataloge_sub, where nothing is checked in the library list.

Registration must complete before the module is connected. A module receives the callbacks that were registered by the time it itself was connected. The parent module always makes it in time: its PHP executes before the template is parsed, and nested modules are connected during parsing. A sibling that registers a callback later, however, will not affect an already-connected module — register higher up the tree, not from the side.

The library’s tables are available only to the module that declared it. If a callback reads data from the database, those tables will be added to the cache dependency list only for the top-level module; nested modules will not be aware of them and will not be rebuilt when they change. For callbacks that merely format passed values this does not matter, but a library that reads from the database should also be attached to the modules where it is used.

For more details on modifiers and callback syntax, see the “Modifiers” section.

melbis_inc_logic — Unified Business Logic

This is the most significant type of library module. melbis_inc_logic contains all functions for working with orders: creation, loading, editing, calculation, adding and removing products, discount calculation, and notifications:

MELBIS_INC_LOGIC_order_create        — create a new order version
MELBIS_INC_LOGIC_order_load          — load the current version
MELBIS_INC_LOGIC_order_edit          — open an order for editing
MELBIS_INC_LOGIC_order_calc          — calculate totals
MELBIS_INC_LOGIC_order_goods_add     — add a product to the order
MELBIS_INC_LOGIC_order_goods_remove  — remove a product from the order
MELBIS_INC_LOGIC_order_goods_discount — calculate discounts
MELBIS_INC_LOGIC_notify_events       — check system events

These functions are called from the shopping cart module on the storefront:

// In the melbis_basket module:
$version = MELBIS()->SessionGetValue('order') ?? MELBIS_INC_LOGIC_order_create();
$version = MELBIS_INC_LOGIC_order_goods_add($version, $store_id);
$version = MELBIS_INC_LOGIC_order_calc(null, $version);

The key advantage of this approach is unified business logic for both the website and the desktop application. The very same functions from melbis_inc_logic are called by the Melbis Shop application when a manager works with orders through the Windows client. The configuration of called functions is done in the application via the “Design → Settings Registry” menu, the “Basic Settings” tab, the “Called Modules” section. For example, in the “Orders → Calculation” section, the library name and the function name that the application should call to calculate an order are specified.

This way, a customer placing an order through the website and a manager editing it in the application both work through the same PHP code — with no risk of logic divergence or synchronization errors.

Other Library Examples

In addition to those listed, other libraries are also common in projects on the Melbis platform:

Any logic needed in more than one module is worth moving into a library.