Методы шаблонизатора

Платформа Melbis использует архитектуру MVC: логика работы с данными сосредоточена в PHP-коде модуля, а формирование HTML-кода — в шаблонах. Хотя PHP позволяет собрать весь HTML прямо в модуле с помощью простых операций подстановки строк, мы настоятельно рекомендуем этого не делать. Шаблонизатор Melbis оснащён мощным инструментарием: циклы, условия, модификаторы, колбэки, вложенность — всё это избавляет модуль от логики отображения и делает код чище и поддерживаемее.

Где лежат .htm-файлы, что такое группа шаблонов и как выбрать её для конкретного посетителя — в разделе «Группы шаблонов».

Методы работы с шаблонами

Все взаимодействия модуля с шаблонизатором происходят через методы MELBIS()->Tpl*:

TplCreate() — создаёт новый контекст шаблонизатора и возвращает его указатель. Вызывается в начале каждого модуля:

$tpl = MELBIS()->TplCreate();

TplAssign($tpl, $vars, $value = '') — передаёт данные в контекст, полностью затирая то, что лежало под этим именем раньше: и скаляр, и массив, независимо от того, что записывается сейчас. Это простое присваивание, никаких слияний.

Вызывается в двух формах — пара «имя + значение» или один массив пар:

// Имя + значение
MELBIS()->TplAssign($tpl, 'TITLE', 'Каталог товаров');

// Вложенный массив — в шаблоне доступен как {PAGE:TITLE}, {PAGE:DIR} и т.д.
MELBIS()->TplAssign($tpl, 'PAGE', $page);

// Массивом пар: каждый ключ становится отдельной переменной
MELBIS()->TplAssign($tpl, $product);
MELBIS()->TplAssign($tpl, ['TITLE' => 'Каталог', 'GOODS' => $goods]);

При передаче массивом пар третий аргумент не используется.

Ключи приводятся к верхнему регистру — и само имя переменной, и все строковые ключи внутри переданного массива, на любую глубину. В PHP можно писать как удобно: TplAssign($tpl, 'page', $page) и TplAssign($tpl, 'PAGE', $page) — одно и то же, а в шаблоне ключ всегда пишется в UPPERCASE. Числовые ключи списков остаются как есть.

TplAppend($tpl, $var, $value = '', $replace = false) — дописывает значение к уже существующей переменной, не затирая её целиком. Поведение жёстко зависит от типа $value, и это два принципиально разных режима:

Если тип не совпал, старое значение теряется. Дописать массив туда, где лежит скаляр, — скаляр отбрасывается, слияние начинается с пустого массива. Дописать скаляр туда, где лежит массив, — массив отбрасывается, и в переменной остаётся только новая строка. Ошибки при этом не будет, поэтому следите, чтобы Append получал тот же тип, что уже лежит в переменной.

Формы вызова те же, что у TplAssign, но с одной особенностью: при передаче массивом пар роль $replace играет второй аргумент, а не четвёртый.

// Имя + значение
MELBIS()->TplAppend($tpl, 'TAGS', $extra_tags);

// Массивом пар: второй аргумент — $replace
MELBIS()->TplAppend($tpl, ['TAGS' => $extra_tags, 'FILTERS' => $extra_filters]);
MELBIS()->TplAppend($tpl, ['SETTINGS' => $extra_settings], true); // одноуровневая замена вместо слияния

TplParse($tpl, $var, $file) — загружает .htm-шаблон по имени файла, парсит его с текущими данными контекста и помещает результат в переменную. Имя переменной может быть путём: TplParse($tpl, 'PAGE:CONTENT', 'page_index') положит разобранный шаблон внутрь PAGE, создав недостающие уровни. Именно этим методом модуль собирает различные части своего HTML:

// Спарсить шаблон page_index.htm и записать в переменную CONTENT
MELBIS()->TplParse($tpl, 'CONTENT', 'page_index');

// Дополнительные шаблоны
MELBIS()->TplParse($tpl, 'WINDOWS', 'windows');
MELBIS()->TplParse($tpl, 'SCRIPTS', 'scripts');

Накопление результата. Обычно TplParse записывает результат в переменную, затирая прежнее значение. Точка в имени файла меняет это поведение на дозапись:

// Обычно: результат заменяет содержимое CONTENT
MELBIS()->TplParse($tpl, 'CONTENT', 'page_index');

// '.имя' — результат дописывается в КОНЕЦ того, что уже лежит в CONTENT
MELBIS()->TplParse($tpl, 'CONTENT', '.page_extra');

// 'имя.' — результат дописывается в НАЧАЛО
MELBIS()->TplParse($tpl, 'CONTENT', 'page_notice.');

Приём достался платформе от старого стиля модулей, где список собирался циклом в PHP: на каждой итерации данные строки передавались через TplAssign, а TplParse($tpl, 'ITEM', '.item') дописывал очередную карточку к предыдущим. Сейчас так писать не нужно — список целиком передаётся в шаблонизатор одним TplAssign, а перебор делает цикл {#...} в шаблоне (см. «Синтаксис шаблонов»). Дозапись остаётся полезной в других случаях: собрать переменную из нескольких разных шаблонов, добавить блок по условию, приклеить предупреждение перед основным содержимым.

TplFinal($tpl, $file, $postProdName = false) — финальный шаг: парсит указанный шаблон (как правило main) с текущим состоянием контекста (включая уже заполненные переменные из TplParse) и возвращает готовую HTML-строку. Результат возвращается из главной функции модуля:

return MELBIS()->TplFinal($tpl, 'main');

Третий аргумент — имя функции постобработки, через которую пропускается готовый HTML модуля перед возвратом. Сама функция регистрируется заранее, обычно в библиотечном модуле:

// Регистрация: имя → функция
MELBIS()->DefinePostProd('minify', 'MELBIS_INC_BASE_html');
// Применение в любом модуле
return MELBIS()->TplFinal($tpl, 'main', 'minify');

Функция получает готовую HTML-строку единственным аргументом и должна вернуть строку. Это место для сквозных преобразований разметки: сжатие HTML, подстановка версий у статических файлов, простановка loading="lazy" у картинок. Если функция с указанным именем не зарегистрирована, разбор останавливается с ошибкой.

Важно, что постобработка выполняется до кеширования результата модуля — то есть один раз на компиляцию, а не на каждый запрос. Персональные для посетителя данные так подставлять нельзя, для них есть глобальный массив (см. «Глобальный массив»).

TplFree($tpl, $var) — альтернативный финал: возвращает значение уже готовой переменной контекста и уничтожает сам контекст. Это TplFetch и освобождение памяти одним вызовом:

// Модуль собрал всё в CONTENT, и main-шаблон ему не нужен
MELBIS()->TplParse($tpl, 'CONTENT', 'page_index');

return MELBIS()->TplFree($tpl, 'CONTENT');

Разница с TplFinal в том, что TplFinal разбирает указанный .htm-файл с текущими данными, а TplFree ничего не парсит — просто отдаёт то, что уже накоплено. После вызова указатель $tpl недействителен: обращаться к этому контексту больше нельзя. Имя переменной обязано существовать — как и TplFetch без значения по умолчанию, на неизвестном имени метод останавливает разбор с ошибкой.

TplParseStr($tpl, $var, $str, $vars = []) — то же, что TplParse, но шаблон берётся не из файла, а из переданной строки. Нужен, когда разметка приходит из базы: письма, SMS-шаблоны, тексты соглашений с подстановками, которые редактирует менеджер.

$template = $letter['body'];    // '<p>Здравствуйте, {NAME}! Заказ {ORDER_ID} принят.</p>'
$vars = [
    'NAME'      => $client['name'],
    'ORDER_ID'  => $order_id
    ];
MELBIS()->TplParseStr($tpl, 'LETTER', $template, $vars);

Четвёртый аргумент — необязательный набор данных, он просто передаётся в контекст перед разбором, как если бы вы вызвали TplAssign. Строке доступны все возможности шаблонизатора: циклы, условия, модификаторы.

TplParseVar($tpl, $var, $varTemplate) — разбирает как шаблон значение другой переменной контекста, а результат кладёт в $var. Применяется, когда текст с тегами уже попал в контекст раньше и его нужно разобрать вторым проходом. Точка в имени работает так же, как у TplParse. Путём может быть и приёмник, и источник, но правила у них разные: приёмнику недостающие уровни создаются, источник же только читается — если такого пути нет, в приёмник попадёт пустая строка.

TplFetchAll($tpl) — возвращает все переменные контекста одним массивом. Инструмент отладки: посмотреть, что реально накопилось в шаблонизаторе к моменту вызова. В рабочем коде вместо него берите конкретное значение через TplFetch.

TplFetch($tpl, $var, $default = null) — читает значение переменной обратно уже на стороне PHP. Принимает и путь: TplFetch($tpl, 'PAGE:DIR') достанет вложенный элемент, ничего при этом не создавая. Если переменной с таким именем или путём нет: без третьего аргумента — ошибка парсинга (защита от опечаток в имени); если третий аргумент передан (даже null) — вернётся он, без ошибки. Это разные вызовы, а не один и тот же с «более мягким» дефолтом:

MELBIS()->TplFetch($tpl, 'DEBUG_INFO');          // нет переменной — упадёт с ошибкой
MELBIS()->TplFetch($tpl, 'DEBUG_INFO', null);    // нет переменной — вернёт null, без ошибки
MELBIS()->TplFetch($tpl, 'DEBUG_INFO', []);      // нет переменной — вернёт []

TplClear($tpl, $var) — удаляет переменную (или несколько) из контекста, как будто её никогда не передавали через TplAssign. Принимает как одно имя, так и массив имён, и в любом из них — путь: TplClear($tpl, 'PAGE:DIR') удалит один вложенный ключ, оставив остальной PAGE нетронутым. Несуществующий путь просто игнорируется.

// Удалить одну переменную
MELBIS()->TplClear($tpl, 'DEBUG_INFO');

// Удалить сразу несколько
MELBIS()->TplClear($tpl, ['DEBUG_INFO', 'RAW_VARS']);

Обращение по вложенному пути

Везде, где метод принимает имя переменной, вместо простого имени можно передать путь через : — в той же нотации, что используется на чтение в шаблонах ({PAGE:DIR}). Тогда метод адресует не всю переменную целиком, а один конкретный вложенный элемент, не трогая соседей.

Различаются методы только тем, создают ли они недостающие уровни пути:

Метод Создаёт недостающие уровни
TplAssign, TplAppend да
TplParse, TplParseStr да
TplParseVar приёмник — да, источник — нет
TplFetch, TplClear нет

Правило простое: пишущие методы путь создают, читающие — нет. Поэтому TplFetch по несуществующему пути даст ошибку разбора (или вернёт значение по умолчанию), а TplClear просто ничего не сделает — ни тот, ни другой ничего не создадут и не испортят.

// Вместо «прочитать весь PAGE, поправить DIR в PHP, записать PAGE обратно целиком»
MELBIS()->TplAssign($tpl, 'PAGE:DIR', '/catalog/');
// затронут только PAGE:DIR — остальные ключи PAGE остаются как были

Путь работает во всех формах вызова, в том числе ключом внутри массива пар — это удобно, когда нужно разложить несколько значений по разным веткам одним вызовом:

MELBIS()->TplAssign($tpl, [
    'PAGE:DIR'   => '/catalog/',
    'PAGE:TITLE' => 'Каталог',
    'USER:NAME'  => $client_name
    ]);

Регистр не важен. Путь целиком приводится к верхнему регистру, поэтому 'page:dir' и 'PAGE:DIR' — одно и то же.

Числовые сегменты адресуют элемент списка: TplAssign($tpl, 'GALLERY:0:TITLE', 'Обложка') меняет заголовок первой картинки галереи, не перезаписывая остальные.

Промежуточные уровни создаются сами. Если PAGE ещё нет, TplAssign($tpl, 'PAGE:DIR', '/') создаст и PAGE, и ключ внутри. Это касается только Assign и Append; Fetch и Clear ничего не создают — при отсутствующем пути Fetch завершится ошибкой разбора (или вернёт значение по умолчанию, если оно передано), а Clear просто ничего не сделает.

Промежуточный скаляр по пути будет уничтожен. Если в PAGE лежала строка, то TplAssign($tpl, 'PAGE:DIR', '/') превратит PAGE в массив, а прежняя строка пропадёт: путь требует, чтобы все уровни выше листа были массивами. Ошибки не будет.

Assign или Append по пути

Разница ровно та же, что и без пути, но по пути она видна нагляднее — сравните на одних и тех же данных:

MELBIS()->TplAssign($tpl, 'PAGE', ['DIR' => '/catalog/', 'TITLE' => 'Каталог']);

// Assign по пути — чистая замена листа
MELBIS()->TplAssign($tpl, 'PAGE:TITLE', 'Новинки');
// PAGE = ['DIR' => '/catalog/', 'TITLE' => 'Новинки']

// Append по пути — конкатенация текста в том же листе
MELBIS()->TplAppend($tpl, 'PAGE:TITLE', ' — страница 2');
// PAGE = ['DIR' => '/catalog/', 'TITLE' => 'Новинки — страница 2']

Правило простое: Assign по пути — заменить лист, Append по пути — дописать к листу. Соседние ключи не страдают ни в том, ни в другом случае.

Ради этого путь и появился. Раньше, чтобы поменять один вложенный лист, приходилось передавать в Append целый вложенный массив — и рисковать соседними ключами, если слияние оказывалось одноуровневым, — либо читать переменную целиком, править в PHP и записывать обратно. С путём адресуется ровно один лист, и выбор между заменой и дозаписью делается явно.

Таким образом, типичный сценарий работы модуля с шаблонизатором выглядит так:

function MELBIS_BASE_PAGE($mVars)
{
    $tpl = MELBIS()->TplCreate();

    // ... получить данные из БД ...

    // Передать данные
    MELBIS()->TplAssign($tpl, 'PAGE', $page);
    MELBIS()->TplAssign($tpl, 'GOODS', $goods);

    // Собрать нужный вариант контента
    MELBIS()->TplParse($tpl, 'CONTENT', 'page_goods');

    // Собрать дополнительные части
    MELBIS()->TplParse($tpl, 'SCRIPTS', 'scripts');

    // Вернуть итоговый HTML
    return MELBIS()->TplFinal($tpl, 'main');
}