Обычно каждый модуль парсит только те данные, которые сам сгенерировал (локальный контекст). Но в сложных интерфейсах данные из одного модуля часто нужны в другом (например, модуль Корзины посчитал сумму, а вывести ее нужно в шапке сайта).
Для этого существует отдельный глобальный массив. Работа с ним идёт через четыре метода.
Они устроены точно так же, как
Tpl*.GlobalAssign— этоTplAssign,GlobalAppend—TplAppend, и так далее: те же формы вызова, то же приведение ключей к верхнему регистру, те же правила слияния, тот же путь через:. Отличие ровно одно — не нужен указатель контекста: глобальный массив на всю страницу один. Если вы разобрались с одной парой методов, вторая не потребует ничего нового.
GlobalAssign($name, $value = '') —
записывает значение под указанным именем, полностью
затирая то, что там было раньше: и скаляр, и массив. Простое
присваивание, никаких слияний.
Вызывается в двух формах — пара «имя + значение» или один массив пар:
// Имя + значение
MELBIS()->GlobalAssign('site_currency', 'UAH');
MELBIS()->GlobalAssign('cart', ['total_sum' => 1500]);
// Массивом пар: каждый ключ становится отдельной переменной
MELBIS()->GlobalAssign(['cart' => $cart_data, 'user' => $user_data]);При передаче массивом пар второй аргумент не используется.
Ключи приводятся к верхнему регистру — и имя
переменной, и все строковые ключи внутри переданного массива, на любую
глубину. В PHP пишите как удобно, в шаблоне ключ всегда в UPPERCASE:
GlobalAssign('cart', ['total_sum' => 1500]) →
{CART:TOTAL_SUM}. Числовые ключи списков остаются как
есть.
GlobalAppend($name, $value = '', $replace = false)
— не затирает, а объединяет с уже существующим значением, и делает это
по-разному в зависимости от типа $value:
., даже
если это числа: было 5, дописали 3 — получится
строка "53", а не 8. Числовой суммы
GlobalAppend не считает никогда: считайте её в PHP и
записывайте через GlobalAssign.$replace = false) — глубокое слияние на любую вложенность:
совпавшие ассоциативные подключи сохраняются на каждом уровне, а
совпавшие числовые списки (галереи, теги и т.п.) не портятся позиционно,
а дописываются в конец. С $replace = true
— слияние одноуровневое: совпавший ключ заменяется новым значением
целиком, вместе со всем, что было внутри него (включая списки — они в
этом режиме не дописываются, а подменяются). Строковой склейки внутри
массивов не бывает никогда ни в одном из режимов — это особенность
только скалярного случая.Если тип не совпал, старое значение теряется. Дописать массив туда, где лежит скаляр, — скаляр отбрасывается, слияние начинается с пустого массива. Дописать скаляр туда, где лежит массив, — массив отбрасывается, остаётся только новая строка. Ошибки при этом не будет.
Формы вызова те же, но с одной особенностью: при передаче
массивом пар роль $replace играет второй аргумент,
а не третий.
// Имя + значение
MELBIS()->GlobalAppend('page', ['title' => 'Каталог товаров']);
// Массивом пар: второй аргумент — $replace
MELBIS()->GlobalAppend(['cart' => $extra_cart, 'user' => $extra_user]);
MELBIS()->GlobalAppend(['cart' => $extra_cart_data], true); // одноуровневая замена вместо слиянияТипичный случай для Append: несколько модулей независимо
друг от друга пишут под одним именем. Библиотека авторизации кладёт
page = ['auth' => 1, 'user_id' => 5], а модуль
страницы хочет добавить туда же заголовок:
MELBIS()->GlobalAppend('page', ['title' => 'Каталог товаров']);
// -> page = ['auth' => 1, 'user_id' => 5, 'title' => 'Каталог товаров']GlobalFetch($name, $default = null) —
читает значение обратно уже на стороне PHP (например, чтобы прочитать
то, что записал в глобальный массив другой модуль). Принимает и путь:
GlobalFetch('PAGE:DIR') достанет вложенный элемент, ничего
при этом не создавая. Поведение при отсутствующем ключе — как у
TplFetch: без второго аргумента — ошибка разбора, с любым
вторым аргументом (в том числе null) — тихо вернёт его
вместо ошибки.
GlobalClear($name) — удаляет значение
из глобального массива. Принимает как одно имя, так и массив имён:
GlobalClear(['CART', 'USER']) удалит обе переменные разом.
В любом из них можно указать путь: GlobalClear('PAGE:DIR')
удалит один вложенный ключ, оставив остальной PAGE
нетронутым. Несуществующий путь просто игнорируется.
Все четыре метода понимают путь через :
— ту же нотацию, что используется на чтение в шаблонах
({PAGE:DIR}). Метод адресует не всю переменную целиком, а
один вложенный элемент, не трогая соседей.
Различаются они только тем, создают ли недостающие уровни пути:
пишущие создают, читающие — нет.
GlobalAssign и GlobalAppend достроят структуру
до нужного листа, а GlobalFetch и GlobalClear
по несуществующему пути ничего не создадут: первый вернёт ошибку разбора
или значение по умолчанию, второй просто ничего не сделает.
// Вместо «прочитать весь PAGE, поправить DIR в PHP, записать PAGE обратно целиком»
MELBIS()->GlobalAssign('PAGE:DIR', $url);
// затронут только PAGE:DIR — остальные ключи PAGE остаются как былиПуть работает и ключом внутри массива пар, когда нужно разложить значения по разным веткам одним вызовом:
MELBIS()->GlobalAssign([
'PAGE:DIR' => $url,
'PAGE:LANG' => $lang,
'USER:NAME' => $client_name
]);Регистр не важен: путь целиком приводится к верхнему
регистру, 'page:dir' и 'PAGE:DIR' — одно и то
же.
Числовые сегменты адресуют элемент списка:
GlobalAssign('MENU:ITEMS:0:NAME', 'Главная').
Промежуточные уровни создаются сами — у
Assign и Append. Fetch и
Clear не создают ничего: при отсутствующем пути
Fetch завершится ошибкой разбора (или вернёт значение по
умолчанию, если оно передано), а Clear ничего не
сделает.
Промежуточный скаляр по пути будет уничтожен. Если в
PAGEлежала строка, тоGlobalAssign('PAGE:DIR', $url)превратитPAGEв массив, а прежняя строка пропадёт: путь требует, чтобы все уровни выше листа были массивами. Ошибки не будет.
Разница ровно та же, что и без пути, но по пути видна нагляднее:
MELBIS()->GlobalAssign('page', ['dir' => '/catalog/', 'title' => 'Каталог']);
// Assign по пути — чистая замена листа
MELBIS()->GlobalAssign('PAGE:TITLE', 'Новинки');
// PAGE = ['DIR' => '/catalog/', 'TITLE' => 'Новинки']
// Append по пути — конкатенация текста в том же листе
MELBIS()->GlobalAppend('PAGE:TITLE', ' — страница 2');
// PAGE = ['DIR' => '/catalog/', 'TITLE' => 'Новинки — страница 2']Assign по пути — заменить лист,
Append по пути — дописать к листу. Соседние ключи
не страдают ни в том, ни в другом случае.
Ради этого путь и появился: адресовать один лист глубоко во вложенной структуре, не перечитывая и не перезаписывая родителя целиком, и явно выбрать между заменой и дозаписью.
Прочитать записанное можно из любого шаблона на странице, независимо от того, какой модуль его вывел.
Как это работает:
MELBIS()->GlobalAssign('cart', ['total_sum' => 1500]);MELBIS()->GlobalAssign('user', ['is_logged' => 1]);{CART:TOTAL_SUM|num} грн{*USER:IS_LOGGED==1} Привет, юзер! {USER:IS_LOGGED*}GlobalAssign('site_currency', 'UAH') →
{SITE_CURRENCY}🔥 Важное правило приоритетов: Локальные переменные
модуля всегда “побеждают” глобальные. Если в глобальном массиве есть
ключ TITLE, и текущий модуль тоже передал ключ
TITLE, парсер выведет локальное значение из модуля. Это
защищает верстку от случайных конфликтов.
🔥 Абсолютно все возможности: Глобальные переменные
обрабатываются тем же мощным ядром парсера, что и обычные данные. Для
них доступны цепочки модификаторов
({CART:TOTAL_SUM|calc:VALUE*0.9|num:0}), логические условия
и циклы:
{#MENU:ITEMS}
<a href="{URL}">{NAME}</a>
{MENU:ITEMS#}Парсер использует двухуровневую систему защиты HTML-структуры. Если локальных данных для блока нет, парсер проверяет, является ли этот блок глобальным, чтобы решить его судьбу.
Главное правило разработки: Чтобы парсер понял, что блок (например,
{#PAGE:LANGS} или {*USER:IS_LOGGED}) относится к глобальному контексту и
его нельзя удалять на первом проходе, его корневой ключ (массив) обязан
быть инициализирован в PHP до начала парсинга модуля, хотя бы как пустой
элемент. Проще всего сделать это через
MELBIS()->GlobalAssign('page', []); в точке входа — так
ключ PAGE появится в глобальном массиве ещё до старта
первого модуля.
Архитектура шаблонизатора построена по принципу отложенного вычисления (Deferred Evaluation). Это значит, что обработка страницы происходит в два этапа (прохода). Это решает главную проблему: как кэшировать тяжелые модули, но оставлять в них динамические данные (например, актуальную корзину или имя пользователя).
Как работают два прохода:
{#PAGE:LANGS}), он понимает,
что это чужая зона ответственности. Он “замораживает”
этот блок и оставляет его в HTML-коде в виде исходного текста.🚀 Пробиваем кэш модуля Именно из-за того, что
второй проход не кэшируется, глобальный массив — штатный способ
“пробить” кэш тяжёлого модуля точечными динамическими вставками, не
отключая кэширование самого блока целиком. Вы можете агрессивно
кэшировать любую тяжёлую вёрстку, а актуальные для конкретного
посетителя данные — количество товаров в корзине
{CART:COUNT}, текущий язык, проверку авторизации
{*USER:IS_LOGGED} — доставлять в неё через глобальный
массив, и они всегда будут отрабатывать в реальном
времени, даже внутри закэшированного блока.
Пример: шапка сайта — тяжёлая, редко меняющаяся вёрстка, которую выгодно кэшировать целиком. Но ссылка на логотип должна вести с учётом текущего языка посетителя, а рядом стоит виджет телефона каталога, тоже завязанный на язык:
<div class="container-fluid">
<div class="row">
<div class="col-xs-12 col-sm-5 col-md-4 col-lg-4">
<a href="/{VAR:LANG_PATH}"><img src="{PATH}/images/design/logo.png" class="img-responsive"></a>
</div>
<div class="hidden-xs col-sm-7 col-md-8 col-lg-8" style="position: relative;">
{MELBIS:studio_cataloge_phone([VAR:LANG])}
<img src="{PATH}/images/design/memo.png" class="img-responsive pull-right">
</div>
</div>
</div>{VAR:LANG_PATH} — обычный тег глобального массива:
текущий язык куда-то заранее пишет другой, некэшируемый модуль
(например, определяющий язык по URL/сессии) через
GlobalAssign('var', [...]). Здесь он читается на втором
проходе — даже если вся шапка целиком отдаётся из файлового кэша, ссылка
на логотип всё равно ведёт на актуальный для посетителя язык.[VAR:LANG] — тот же глобальный ключ, но в квадратных
скобках (см. «URL-кодированный вывод» в разделе «Синтаксис шаблонов»),
использован как параметр вложенного вызова модуля
{MELBIS:studio_cataloge_phone(...)}. Второй проход
разворачивает теги глобального массива по всему тексту модуля ещё до
того, как движок доходит до раскрытия {MELBIS:...}-вызовов
— поэтому вложенный модуль получает в параметре уже готовое, живое
значение языка, а не то, что было актуально в момент, когда кэшировалась
шапка.Прямое следствие того, что второй проход не кешируется: данные из сессии нельзя выводить как обычную переменную модуля — первый же посетитель запечёт своё имя в кеш, и его увидят остальные. Такие значения передаются только через глобальный массив:
// В некешируемом модуле, определяющем посетителя
$client_id = MELBIS()->SessionGetValue('client_id');
MELBIS()->GlobalAssign('user', [
'is_logged' => !is_null($client_id),
'name' => $client_name
]);{*USER:IS_LOGGED}
Здравствуйте, {USER:NAME|html}
{USER:IS_LOGGED*}Так шапку сайта можно кешировать целиком, а приветствие всё равно будет персональным. Подробнее о работе с сессией — в разделе «Сессии и CSRF».