Template Syntax

Everything available in an .htm file: including partials, outputting variables, loops, and conditions. The methods by which a module passes data here are described in the “Templating Engine Methods” section, and value transformations are described in the “Modifiers” section.


1. Assembling a Template from Parts

The final HTML is rarely assembled from a single file — it usually consists of a header, footer, various content variants, and placeholders. Melbis provides two ways to do this, and they are not mutually exclusive — they can be combined within a single module.

1.1. Assembly from the PHP Side — TplParse

The classic approach is already described above in the “Template Methods” section: the module explicitly decides which file to parse and which variable to store the result in:

MELBIS()->TplParse($tpl, 'ORDER', 'order_absent');

This approach is justified when the choice of template depends on business logic that lives entirely in PHP — for example, the module itself decides whether to parse order_absent or order_exist based on data that the markup layer has no access to.

1.2. Assembly Inside the Template — the {^NAME} Tag

If the choice of parts is purely a markup concern (when to show a header/footer, when to show a placeholder instead of a block), assembling the structure from PHP is unnecessary and even undesirable: it mixes View logic with Controller code. For such cases, another .htm file from the same module can be included directly in the template using the {^FILE_NAME} tag:

<div class="order-page">
    {*ORDER}
        {^ORDER_EXIST}
    {ORDER*}

    {*!ORDER}
        {^ORDER_ABSENT}
    {ORDER*}
</div>

In this case, the module limits itself to only passing data:

MELBIS()->TplAssign($tpl, 'ORDER', $order_data);

and which “branch” of the markup to display is decided by the main.htm template itself.

How it works:

Important limitation: you cannot split a single loop or condition block across two different inclusions — that is, you cannot put the opening {#STORE} in one file and the closing {STORE#} in another. Each included file must be a self-contained markup fragment.

Recommendations:


The templating engine works on the principle of a safe pipeline: it silently ignores missing keys (turning null into an empty string), blocks direct output of arrays to avoid PHP errors, and allows building modifier chains.

2. Outputting Variables and Working with Data

The Melbis Shop templating engine can work not only with flat variables but also with deeply nested arrays. Data exchange between different system modules is described in the “Global Array” section.

2.1. Basic Syntax

Outputting a value:

URL-encoded output:

Space is encoded as + — following application/x-www-form-urlencoded rules. In a query string this is correct, PHP will decode the value back automatically; in module call arguments — also fine, as the parser applies a paired urldecode. However, in a path segment the plus sign will remain a plus sign: /search/[QUERY]/ for the value two stars will produce /search/two+stars/, which is not the correct address. For paths you need rawurlencode — it is available as a regular modifier (see “Modifiers”):

<a href="/search/{QUERY|rawurlencode}/">

System tags. Thirteen values — {PATH}, {LANG}, {CSRF_TOKEN}, {BUILD}, and others — are available in any template of any module without any TplAssign. The service tag {NULL} stands apart. All of them, along with an important note about modifiers, are collected in the “System Constants” section.

<link rel="stylesheet" href="{PATH}/statics/bundle.base.css">
<html lang="{LANG}">

2.2. Accessing Nested Arrays (Multidimensional Data)

If a module passes not just a string to the template but a complex multidimensional array (for example, an array with profile settings or prices), you can access any level of nesting using a colon :.

The templating engine automatically “flattens” arrays, creating convenient keys, which ensures instant rendering speed. Syntax: {ARRAY:KEY:SUBKEY} or {ARRAY:INDEX:KEY}

Usage examples:

2.3. Working with Simple (Flat) Lists

Modules often pass not complex associative arrays but simple lists of values (for example, an array of tags, a list of IDs, a set of colors: ['red', 'blue', 'green']).

For outputting such lists in a loop, the parser automatically creates a virtual key ITEM that holds the current value.

How it works: Inside the loop {#LIST} ... {LIST#} you simply use the {ITEM} tag. Since it is now a full-fledged engine element, all modifiers and system variables can be applied to it.

Example (Outputting tags separated by commas, without a trailing comma):

Passed array: $mData['TAGS'] = ['php', 'mysql', 'js'];

In the template:
{#TAGS}
    <a href="/search/?q=[ITEM]">{ITEM|html}</a>{*!IS_LAST}, {IS_LAST*}
{TAGS#}

3. Logic Blocks (Loops)

Used for iterating over arrays (for example, lists of products, properties, files).

Call syntax and limits:

Example:

{#STORE=4}
    <div class="item {*IS_FIRST}first-item{IS_FIRST*}">
        <b>{NUM1}. {NAME}</b> 
    </div>
{STORE#}

Service variables inside a loop:

💡 Virtual variable :COUNT (Checks outside the loop) When any indexed array is passed to a template, the parser automatically creates a “flat” variable with the :COUNT suffix containing the size of that array. This allows you to check the number of elements and build logic before calling the loop itself:

{*STORE:COUNT>4}
    <div class="alert">We have more than 4 products! Total: {STORE:COUNT} items.</div>
{STORE:COUNT>4*}

4. Conditions (Display Logic)

Condition tags start with * and close the same way.

Basic checks:

Comparison operations:

Smart checks (IN and BETWEEN):

Conditions based on system tags:

Why there are no compound conditions (&&, ||)

Sometimes you want to write something like {*NAME&&STATUS} ... {NAME*} — directly in the template. This is intentional: compound conditions are not supported, and here is why.

The correct approach is to evaluate the compound condition once in the module and pass an already complete, semantically self-contained flag to the template:

$goods_absent = empty($goods['PRICE']) || empty($goods['STOCK']) || $goods['STATUS'] === 'disabled';

MELBIS()->TplAssign($tpl, 'GOODS_ABSENT', $goods_absent);
{*GOODS_ABSENT}
    <div class="out-of-stock">Item is out of stock</div>
{GOODS_ABSENT*}

Now the markup shows what is being checked (GOODS_ABSENT — a descriptive name), not a set of operators that needs to be decoded. All the logic lives in one place — in PHP, alongside the rest of the module’s business logic — and it can be reused, tested, and changed without touching a single .htm file.


5. Complex Examples

Example 1: SEO Block for a Product Page

Using modifier chains for safe generation of microdata and meta tags.

<title>{PRODUCT_NAME|html} buy for {PRICE|nums} UAH</title>
<meta name="description" content="{DESCRIPTION|plain|short:160}">

<script type="application/ld+json">
{ROW|json}
</script>

Example 2: Product Card with Complex Logic

Checking availability, calculating the discount percentage on the fly, and outputting placeholders.

<div class="product-card {*IS_FIRST}first-item{IS_FIRST*}">
    <img src="{IMAGE_URL|def:/images/no-photo.png}" alt="{PRODUCT_NAME|html}">
    
    <h3>{PRODUCT_NAME}</h3>
    
    {*PRICE<OLD_PRICE}
        <div class="badge-discount">
            Discount {ROW|calc:100-(price/old_price*100)|nums:0} %
        </div>
        <span class="old-price">{OLD_PRICE|num} UAH</span>
    {PRICE*}
    
    <span class="current-price">{PRICE|num} UAH</span>

    {*STOCK_STATUS==instock,preorder}
        <button onclick="addToCart({ID|int})">Buy</button>
    {STOCK_STATUS*}
    {*STOCK_STATUS!=instock,preorder}
        <span class="out-of-stock">Out of stock</span>
    {STOCK_STATUS*}
</div>

Example 3: Order Table in a Personal Account

Using ranges, dates, and loop counters.

<table>
    <tr>
        <th>#</th>
        <th>Date</th>
        <th>Amount</th>
        <th>Status</th>
    </tr>
    {#ORDERS}
    <tr class="{*IS_EVEN}bg-gray{IS_EVEN*}">
        <td>{NUM1}</td>
        <td>{CREATED_AT|date:d M Y}</td>
        <td>{TOTAL|num:2,., } $</td>
        <td>
            {*STATUS_ID==1-3} <span class="badge-blue">{STATUS_NAME}</span> {STATUS_ID*}
            {*STATUS_ID==4-5} <span class="badge-green">{STATUS_NAME}</span> {STATUS_ID*}
        </td>
    </tr>
    {ORDERS#}
</table>