Шаблонизатор представлений Jet - energy-coresky/air Wiki

При разработке компилятора шаблонов Jet, за основу был взят шаблонизатор Blade популярного PHP Framework Laravel. Многие вещи совместимы с Blade, но все же имеется много новшеств, а некоторые вещи, которые имеются в Blade, в Jet отсутствуют.

В главном PHP файле публичной части приложения (как правило public/index.php), обычно делается вызов topview (MVC::top()). Это приводит к запуску действия, в одном из контроллеров, например c_article::a_show(). По умолчанию устанавливается шаблон view/_article.php, точнее его часть show. Шаблон можно переопределить вернув строку (или с помощью вызова MVC::body(body)), например 'article.list', где точка разделяет псевдо-имя файла и его части. Вообще, существуют следующие элементы управления шаблонами:

  • body - указывает на имя файла шаблона и возможно уточняет его часть (см. $mvc_instance->$body);
  • layout - указывает на layout шаблон-файл, используется совместно с body. Файл шаблона имеет префикс y_ в имени файла, но в MVC::$layout префикс y_ указывать не нужно;
  • MVC::$_y - переменные layout, префикс $y_ добавляется автоматически, если нет никакого другого однобуквенного префикса. Статический массив переменных;
  • $mvc_instance->_v - переменные центральной части страницы (для body). Может быть несколько экземпляров класса MVC - несколько subview + один topview;
  • $sky->is_mobile - переменная поддержки отдельного стиля для мобильных устройств. По умолчанию стандартный файл layout имеет имя view/y_desktop.php, а для мобильных устройств view/y_mobile.php
  • $_stdout - переменная содержит вывод буфера STDOUT, если в контроллере было сделано echo. Кстати, из контроллеров нельзя передавать переменные с префиксом $_ (зарезервировано для автоматических переменных Jet);
  • константа PHP DESIGN - управление вторым набором шаблонов для совершенствования дизайна прямо на продакшн в специально отведенном месте публичной части сайта. Используется редко и обычно равна пустой строке;
  • $sky->style - управление другими наборами шаблонов для приложений, имеющих более одного стиля. Располагаются выше корня веб-сервера (закрытая часть файловой системы). Если используется, то файлы шаблонов располагаются по схеме view/stylename/tplname. Но используется редко и обычно равна пустой строке;

Операторы вывода данных

Синтаксис Описание Совместимость
{{переменная или выражение}}, например {{date()}} печать с ескейпингом, пример будет компилирован так: <?php echo html(date()) ?> совместимо с Blade
{-some text-} комментарий, передается в компилированный шаблон: <?php /* some text */ ?> совместимо с Blade, но там по два минуса
{!переменная или выражение!} печать без ескейпинга: <?php echo переменная или выражение ?> аналогично
~{! $var !} компилируется в <?php echo isset($var) ? $var : '' ?>, аналогично для шаблона с ескейпингом. Заметим, что оператор вывода эквивалентен такому: {! $var or '' !}, но немного короче запись добавленно в Jet
@{{something}} после компилирования останется {{something}}, т.е. как и было но без @ аналогично Blade
@{-something-} после компилирования останется {-something-}, т.е. как и было но без @ аналогично
@{!something!} после компилирования останется {!something!}, т.е. как и было но без @ аналогично
~{-something-} комментарий исходного шаблона, просто удаляется при компиляции добавлено в Jet
{{$var or 'Default'}} компилируется как: <?php echo isset($var) ? html($var) : 'Default' ?> or аналогично можно применять и для печати без ескейпинга совместимо с Blade
~{{$var or 'def'}} выдаст: <?php echo isset($var) && '' != trim($var) ? html($var) : 'def' ?> добавлено в Jet
{{$some and ' class="c"'}} компилируется как <?php isset($some) && $some ? html(' class="c"') : '' ?> в примерах указан вывод с ескейпингом, но можно применять и для вывода без эскейпинга. Левая от and часть анализируется на предмет того просто переменная ли там или выражение, если выражение, то isset() не добавляется добавлено в Jet

Итого: в принципе все совместимо с Blade, но добавлен новый префикс - тильда и в связи с этим получилось несколько новых операторов, так-же добавлен вариант с and. Как видно из таблицы, синтаксис использующий or и and изменяет PHP код. Поэтому если вам нужны булевы операторы в выражениях, просто используйте аналоги с более высоким приоритетом && и ||.

Основные операторы

@if(..), @elseif(..), @else, ~if - условные операторы, все совместимо с Blade, за исключением того, что вместо @endif используется ~if. Имеется также @unless(..) и ~unless.

@inc(..) - включение других шаблонов, - вставляет компилированный шаблон вместо оператора или <?php echo $_stdout ?> (если в контроллере было сделано echo и определен инклудер центральной части страницы в layout @inc(*)). Примеры: включить весь файл: @inc(tpl_name), часть файла: @inc(tpl_name.marker_name) или часть "своего" файла @inc(.marker_name). Если @inc включает шаблон (файл) с префиксом r_, это включение будет содержать механизм RED-LABEL, например: @inc(r_counters)

@loop(..), ~loop, @loop, ~loop(..) - циклы. Для циклов PHP for(), foreach(), while(), do-while(), в Jet используется один и тот-же синтаксис: @loop. Какой именно цикл необходимо использовать, Jet определяет на основании того, что находится в скобках. Внутри каждого из этих циклов, можно использовать @empty (кроме do-while()). Следующий за этим оператором код выполнится, если не пройдет ни одна итерация. Циклы могут иметь произвольную вложенность, для них генерируются автоматические переменные $_, $_2 .. $_N, содержащие номер итерации (первая итерация имеет значение равное нуль). Внутри всех циклов можно также использовать @break(..) и @continue(..). Имеется также специальный синтаксис, для итераторов с префиксом $e_ - @loop($e_list) и ~loop($e_list). В первом случае, используется foreach и дописывается переменная по умолчанию as $row. Во втором - do-while, также с переменной по умолчанию $row. Префикс $e_ превращает массив в итератор класса eVar на этапе подготовки переменных шаблона.

@view( name ) - генерировать sub-представление name. Действие ищется вначале в мастер (там где выполнился topview) контроллере, если не найдено, то в общем контроллере common_c. Если name не имеет префикса, то устанавливается автоматически x_, т.е. вызов будет идти на метод контроллер::x_name(), в отличие от обычных действий (action), которые имеют префикс a_ или j_, например контроллер::a_actname(). Вызовы subview могут быть вложенными. Также можно использовать PHP функцию view() в любом месте кода PHP.

@block( name ) template ~block или @block( template as name ) - определить блок кода шаблона, и его местоположение, для возможной замены с помощью оператора #use(..), или условного выполнения с помощью оператора @use(..), а также обработчика для него если нужно, в стиле блоков Symfony/Twig или секций Laravel/Blade. Шаблон template может содержать произвольный Jet-код или пустую строку. В последнем случае, оператор блока будет только лишь указывать место в шаблоне, куда может быть вставлен Jet-код с помощью #use(..) или условно добавлен, с помощью @use(..). Перед именем блока name, могут быть указаны символы "X ", "X*", где X - буква латинского алфавита, указывающие на наличие обработчика. Компилированные шаблоны блоков и переменные возвращённые обработчиками для блоков, могут находиться в своей собственной или общей области видимости переменных шаблона. Для синтаксиса с "as", template может содержать короткий inline-шаблон, заключенный в косые кавычки, или указатель на шаблон, в полном соответствии с синтаксисом @inc(..).

@use( template as name ) - добавить условно исполняемый шаблон, к блоку с именем name (и возможно обработчик к нему), определенному где-то ранее или позже в операторе @block(..). Есть сокращение: если имя шаблона в текущем файле, совпадает с именем блока и нет вызова хендлера - можно написать @use(.marker) вместо @use(.marker as marker). Имеется также оператор препроцессора, осуществляющий замену блока на этапе компиляции. Синтаксис #use(..) внутри скобок, полностью соответствует @use(..). Если блок с соответствующим именем не найден, операторы @use(..) и #use(..) будут полностью проигнорированы, после компиляции от них останется пустая строка. Эта ситуация не считается ошибкой. Местоположение оператора #use(..) в текущем шаблоне не имеет значения, кроме позиций внутри #if(..) .. #end. Если условий препроцессора нет, все такие операторы нужно размещать в начале шаблона с целью стандартизации. Местоположение @use(..) имеет значение, - если он выполнится, то осуществится визуализация кода представленного @use(..) вместо блока с соответствующим именем. Если выполнится несколько @use(..) или #use(..) для блока с определенным именем, то сработает только последний.

@cache( name ), ~cache - кешировать область шаблона. Внутри области кеширования может быть вызов subview, в котором делаются SQL запросы и прочие действия. Файловый кеш сохраняется в папке var/cache, смотрите Rare::cache(..). С помощью @cache легко организовать кеширование прямо в шаблоне.

Прочие операторы

@php php_code ~php или @php( php_code ) - вставка на php. В шаблонах Jet, нельзя использовать native PHP тег <?php, это вызовет фатальную ошибку компиляции шаблона. Следует также стараться избегать использования операторов @php в шаблонах и выполнять всю работу PHP в контроллерах.

@pdaxt - генерирует код <?php MVC::pdaxt() ?> стандартных ссылок: P - production, D - development настройки, A - секция admin, X - X-трассировка (хранится в БД), Т - обычная трассировка, выводится в хвосте страницы с помощью @tail

@head(..) - генерирует код <?php MVC::head(..) ?>. Во многих приложениях, часть шапки HTML кода можно унифицировать. @head(..) выводит этот унифицированный код. Управление нюансами выдачи, как правило делается в ::head_y($action) или ::tail_y() контроллеров

@tail - генерирует код <?php MVC::tail() ?>. Выводит трассировку (как правило в режиме DEV) и устанавливает признак $sky->tailed, который указывает на то, что все корректно завершилось и не произошел, например Exception.

@t(..) - используется на сайтах поддерживающих более 1 языка. Инструкция делает перевод строки с основного языка на другой. Внутри скобок можно писать текст без кавычек (добавляются автоматически)

@p(..) - используется для печатания абсолютного пути к статическим файлам например картинкам (добавляется значение константы PATH). Это удобно на страницах с длинными семантическими URL. Если статика хранится не на своем домене, можно создать собственные statements приложения, например @p2(..) и т.д.

@verb, ~verb - определение области, которая остается как есть, не смотря ни на что. Аналогично оператору Blade @verbatim

@dump(..) - выводит значение переменной с помощью print_r() внутри тега <pre>, предназначен для использования на этапе отладки скриптов

@mime(..) - меняет значение значение заголовка Content-Type ответа на указанный и устанавливает $sky->ajax = 3; Это удобно использовать например для генерации SVG файлов, указав вначале шаблона layout @mime(image/svg+xml) или в других подобных случаях

@href(..) - обёртка для JS-кода в ссылках, которая просто укорачивает код в шаблоне, например, @href(location.href='user?id{!$id!}') преобразуется в href="javascript:;" onclick="location.href='user?id<?php echo $id ?>'". Код без оператора: href="javascript:;" onclick="location.href='user?id{!$id!}'", т.е. выигрыш 23 символа.

@csrf() - выводит значение CSRF токена в форму: <input type="hidden" value="{{scrf_token_value}}" name="_csrf" /> В формах, построенных с помощью класса Form, этот оператор добавлять не нужно (CSRF токен добавится автоматически).

Оператор по умолчанию - можно написать, например, @selected($bool), это будет транслировано в код: <?php echo ($bool) ? ' selected' : '' ?>. В Blade имеются отдельные операторы - @selected, @checked, @disabled, но нет оператора @active. Использование оператора по умолчанию, эффективнее для таких целей. Если имя оператора не занято другими операторами Jet или custom-операторами приложений, можно использовать оператор по умолчанию, он сокращает код:

<div class="menu">Menu</div>
@loop($list as $uri => $name)
    <[email protected]($uri == URI) href="{! $uri !}">{{ $name }}</a>
~loop

Если нужно чтобы, например @loop(..) в исходном шаблоне не было трактовано, как оператор Jet, необходимо дописать впереди тильду:

в исходном шаблоне не будет трактовано как оператор: [email protected](..)
в компилированном шаблоне останется: @loop(..)

Custom операторы

В Jet имеется возможность добавлять новые операторы для конкретных приложений (и переопределять существующие), смотрите пример:

<?php
 
class common_c extends Controller
{
    static function dt($str, $is_hm = true) {
        if (is_numeric($str))
            $str = date(DATE_DT, $str);
        $tpl = '<span class="date%s">%s GMT</span>';
        return sprintf($tpl, $is_hm ? 'time' : '', gmdate('j M Y' . ($is_hm ? ' H:i' : ''), strtotime($str));
    }
 
    function jet_c() {
        Jet::directive('dt', function($arg) {
            return "<?php echo common_c::dt($arg) ?>";
        });
    }
    # ... другие функции класса
}

В общем контроллере common_c (или активном контроллере запроса) следует определить метод с именем jet_c() (для callback функций указывайте постфикс _c), в нем вызовите Jet::directive() и добавьте все необходимые определения. Используется callback потому что, большую часть времени, на production, файл main/w2/jet.php вообще не загружается (используются готовые откомпилированные шаблоны), это увеличивает производительность приложений. Если новых операторов много, вызов Jet::directive(), также, можно поместить в отдельный файл main/app/jet.php по той же причине.

Итератор eVar

Итераторы в Jet настолько удобны и просты, что на самом деле, удобно делать почти все листинги с использованием этого функционала:

<?php
# модель
    ...
    function listing() {
    ...
        return [
            'query' => ... # sql query or just sql string
            'row_c' => function ($row) { # callback function for rows tuning
                $row->column = '<h3>' . $row->column_in_the_table . '</h3>';
            },
            'title' => ... , # you can add some variables also (not for loop)
            'ifunc' => function ($i) { # and functions also (not for loop)
                return 1 + $i;
            },
        ];
    }
    ...
# контроллер
    ...
    function a_page() {
        ...
        return [ # переменная с префиксом e_ автоматически преобразуется в объект SKY-итератора eVar
            'e_tbl' => $this->t_tbl->listing(),
        ];
    }
    ...
# представление
...
<h1>{{$e_tbl->title}}</h1> <!-- $_ is auto-var in each loop -->
@loop($e_tbl)
    Row: {{$e_tbl->ifunc($_)}}
    {{$row->column}} <!-- $row is default variable name here -->
    @empty
        No rows
    @else
        Total items: {{ $_ }} <!-- This will printed when rows exists only -->
~loop
...

Итераторы eVar позволяют работать не только с SQL запросами, но и генерировать значения рядов вручную (возвращая из row_c массив). По умолчанию, существует ограничение в 500 рядов для не SQL итераций. Для окончания цикла, необходимо вернуть false. Чтобы пропустить ряд, нужно вернуть true. Пример:

$e = new eVar([
    'row_c' => function ($row) {
        $i = $row->__i;
        if (4 == $i)
            return true; # this work for SQL also
        $alfas = ['first', 'second', 'third'];
        return $i > 5 ? false : [
            'num' => $i,
            'alfa' => $alfas[$i > 2 ? 0 : $i],
        ];
    },
    'after_c' => function ($i) {
        echo "\nlast index: $i";
    },
]);

foreach ($e as $row)
    echo "\n$row->num. $row->alfa";

# output:
0. first
1. second
2. third
3. first
5. first
last index: 5

# а так увидим массив объектов:
# print_r($e->all());

Смотрите ещё примеры в коде приложения AB.SKY в t_activity::timeline(..). Специальные ключи в массиве:

query - для итераторов с SQL запросами

row_c - callback функция с одним параметром $row. В $row->__i всегда содержится номер итерации. Для итераторов с SQL запросами, в $row содержится ряд значений, считанный из БД, который необходимо подстроить. Для "ручных" итераторов в $row, на нулевой итерации, - только лишь $row->__i=0, для других итераций - ряд значений из предыдущей итерации.

after_c - callback функция вызывается после завершения работы итератора

max_i - максимальное кол-во итераций. По умолчанию - 500 для "ручных" итераций и не ограничено для SQL. Для отмены ограничения необходимо использовать -1. Для "ручных" итераторов, если кол-во итераций может приближаться к 500, необходимо явно установить max_i, иначе при достижении пяти сот итераций, произойдет фатальная ошибка. Это сделано с целью предотвращения ошибочных бесконечных циклов.

Иногда в Jet-шаблонах удобно использовать итератор eVar в цикле do { .. } while (..). Это работает без проблем: @loop .. ~loop($e_tbl). Jet транслирует цикл так: do { .. } while ($row = $e_tbl->one()). В итераторах eVar, для повторной инициализации, можно использовать eVar::__invoke(..). Смотрите использование этой возможности в Globals::c_report(), обработчик для итератора $e_idents.

Маркеры частей шаблона

Исходные шаблоны могут содержать маркеры частей файла, и к частям файла можно обращаться как к полноценному отдельному файлу в операторах @inc, @block, @use, #use, например вы можете из контроллера вернуть 'article.edit', это установит $mvc_instance->body в это значение, а Jet будет использовать часть файла view/_article.php помеченную как edit. Если необходимо включить часть "своего" файла, имя файла можно не писать, а только маркер, например: @inc(.edit). Маркеры могут быть вложенными. После вырезания части файла, строки содержащие вложенные маркеры полностью удаляются. Поэтому маркеры, нужно указывать на отдельных строках, которые не включают другие операторы и код. Хотя, в общем-то, справа от маркера, через пробел, можно написать комментарий. Чтобы указать часть файла, нужно указать два одинаковых маркера, начала и конца нужной области, синтаксис такой: #.edit. Именами маркеров могут быть любые слова из английских букв, цифр или подчеркивания.

#.first -- вложенные маркеры
template elements 1
#.second -- если сделать @inc(.first) строки с маркерами .second полностью удалятся
template elements 2
#.second
template elements 3
#.first
 
#.one -- перекрестные маркеры также корректны, хотя это вряд-ли понадобится
template elements 4
#.two
template elements 5
#.one
template elements 6
#.two
template elements 7

#.first_name.second_name.third_name -- маркеры могут иметь алиасы
template 8
#.first_name.second_name.third_name

Маркеры частей файла могут иметь алиасы. Часто в контроллерах удобно не менять предустановленный шаблон, который всегда указывает на часть файла. Лучше написать алиас для части шаблона, который будет использоваться для нескольких action в контроллере:

<?php
 
class c_article extends Controller
{
    function a_first_name() { # предустановлен шаблон `article.first_name`
        # код действия
        return [
            # установка переменных шаблона
        ];
    }
 
    function a_second_name() { # предустановлен шаблон `article.second_name`
        # некоторый код
        return $this->a_first_name(); # используем тот-же самый шаблон и код другого действия
    }
}

Алиасы, помогают быстро понять архитектуру приложения. Чтобы движок подсветки синтаксиса распознал jet-файл, можно в первой строке файла написать #.jet.

Части шаблона, ограниченные маркерами, удобно разделять пустой строкой, также как обычно разделяются методы в контроллерах. А шаблоны, которые используются в операторах @use, #use, @block удобно связывать в одной строке, чтобы подчеркнуть принадлежность шаблонов:

#.tpl1 =========================================
tpl code
#.tpl1

#.tpl2 =========================================
other tpl code
#.tpl2

#.tpl0 =========================================
code for block
#.tpl0.tpl3 -- последовательно расположенные шаблоны связаны в одной строке
#use(.tpl0 as block_name)
third tpl code
#.tpl3

Маркерами пользоваться удобнее, чем делать наследования или включения других файлов шаблонов. Во-первых, часто бывает нужно много маленьких шаблонов, которые можно компактно разместить в одном файле. Во-вторых, иногда, при ajax запросах бывает нужна меньшая часть шаблона, который используется и в полном виде. Но делать верстку удобно целиком, не разделяя полный шаблон на разные файлы. В-третьих, лучше написать алиас, чем переопределять предустановленный шаблон. И в-четвёртых: разделяй и властвуй. Чтобы облегчить понимание сложной верстки, маркеры можно использовать подобно методам в классе, разделяя такую верстку на части и соединяя с помощью @inc(.somepart). На скорость работы приложения это не повлияет, так как компиляция шаблона происходит лишь один раз.

Препроцессор

В Jet имеется возможность вырезать или заменять части шаблона на этапе компиляции, тем самым, делая компилированные представления более эффективными:

#if(..), #elseif(..), #else, #end - условные операторы препроцессора;
#use(..) - оператор замены блока на этапе компиляции;

Переменные, которые передаются в шаблон из контроллеров, в препроцессор не передаются, так как это не имеет смысла. Вместо их, в условиях препроцессора, можно использовать информацию из статических свойств компилятора Jet:

  • Jet::$tpl - FILO стек вызовов шаблонов. Всегда не пустой массив, первый элемент которого, с индексом ноль, содержит протопит имени файла и маркер body (или если используется layout, то файл-маркер для layout, а для body всё будет в значении массива с индексом один), для компиляции которого произошел вызов из MVC::jet(..). В последнем элементе массива, всегда содержится имя файла и маркер текущего шаблона.

  • Jet::$fn - содержит имя файла компилированного шаблона, в котором имеется информация о layout, шаблоне body, мобильном или desktop устройстве, опции DESIGN, и имени стиля, если используется. Простой код формирующий имя файла, можно открыть по ссылке

  • Jet::$loop - содержит стек вызовов циклов. В препроцессоре, может быть лишь полезно иногда анализировать, находится ли оператор в цикле или нет.

Чтобы в исходных шаблонах использовать короткие и лаконичные условия для сравнения, применяются псевдопеременные. Нужно создать файл main/app/jet.let содержащий сопоставления для псевдопеременных с выражениями PHP. Этот файл можно копировать из проекта в проект иногда немного корректируя:

:count      count(Jet::$tpl)
:current    Jet::$tpl[:count - 1][0]
:marker     Jet::$tpl[:count - 1][1]
:inner      'inner' == :marker
:outer      ! :inner
:std       '__std' == Jet::$tpl[0][0]
:list       'list' == Jet::$tpl[0][1]

:fn0        explode('_', Jet::$fn, 2)[0]
:fn1        explode('_', Jet::$fn, 2)[1]
:design     'dm' == :fn0 || 'd' == :fn0
:mob        'dm' == :fn0 || 'm' == :fn0
:pc         ! :mob
:style      explode('-', :fn1)[0]
:layout     explode('-', :fn1)[1]
:svg        'svg' == :layout

:caller     1 == :count ? '' : Jet::$tpl[:count - 2][1]
:loop       0 != count(Jet::$loop)
:dev        'WINNT' == PHP_OS
:prod       ! :dev

# препроцессор также автоматически добавит псевдопеременные:
:_0         $sky->_0
:_1         $sky->_1
:_2         $sky->_2
# еще, в условиях препроцессора, имеет смысл использовать PHP-константу URI

Пусть, имеется шаблон calendar.month, содержащий вложенный шаблон calendar.inner, которые используются для генерации различных компилированных шаблонов:

..
#.month
code1
#.inner
code2
#if(:inner)
code3
#end
code4
#if(:outer)
code5
#end
code6
#.inner
code7
#.month

В примере выше, вызов шаблона @inc(calendar.month) будет содержать части шаблона: code1,code2,code4,code5,code6,code7. Этот вызов не содержит часть шаблона code3. А вызов @inc(calendar.inner) будет содержать части шаблона: code2,code3,code4,code6, т.е. нет части code5. Этот пример взят из приложения AB.SKY: шаблон используется на странице "месяц" и странице "год", где нужно напечатать 12 месяцев. На странице "месяц" нужны дополнительные части code1 и code7 и не нужна часть code3 - заголовок месяца печатается в другом месте. А на странице год, шаблон .inner вызывается 12 раз, где добавлена часть code3 - заголовок месяца. В приложении AB.SKY, часть #if(:outer)code5#end не используется и здесь добавлена просто для демонстрации. На изображении ниже, @block(..) используется просто для вызова обработчика, а не для замены с помощью use. Это еще один способ применения блоков. Вместо блока, можно было бы использовать и sub-представление, но для @view(..) будет создан отдельный файл откомпилированного шаблона, а в случае применения блока, шаблон .inner встроится прямо в компилированный шаблон года:

Jet source templates

Условия препроцессора могут иметь произвольную вложенность. Каждое условие препроцессора, должно завершаться словом #end. Любые неверно составленные условия, например #if(:rule1) code1 #else code2 #elseif(:rule2) code3 #end не будут трактоваться как условия препроцессора, и ошибка не будет сгенерирована.

Предпочтительно (если это возможно) использовать оператор препроцессора #if вместо @if и #use вместо @use, так как это повысит производительность. Препроцессор, маркеры частей файла и их алиасы, блоки и sub-представления Jet, позволяют чрезвычайно эффективно следовать принципу DRY и сокращать код. Например, в приложении AB.SKY. один и тот-же шаблон, может использоваться из многих (около 20) различных action. Одновременно такая архитектура, делает приложение проще и понятнее.

Sub-представления

В шаблонах Jet, оператор @view(..) можно использовать, в основном тремя способами:

@view(subname)
@view($difname)
@view(ctrl.action)

/*  
class MVC ..
    static function handle($method, &$param = null) {
        if (method_exists(MVC::$mc, $method))
            return MVC::$mc->$method($param);
        if (method_exists(MVC::$cc, $method))
            return MVC::$cc->$method($param);
    }
*/

Вызвать sub-представление с помощью MVC::handle(..), статически указав имя представления, через переменную и явно указав контроллер-действие. Но использование функции view($in, $return = false, $param = null) в коде приложений шире. Вернуть визуализацию, использовав шаблон, без вызова обработчика, явно передав переменные для шаблона:

$visual = view('tpl_file.marker', $array_of_vars);

Генерировать представление с layout шаблоном, если первый параметр массив:

view([
    'bodyname',
    'layoutname',
]);

Использовать Closure в качестве непосредственного действия:

view(function() {
    // вычисление переменных шаблона
    // ..
    return [
        'var1' => ..,
        'var1' => ..,
        ...
        'varN' => ..,
    ];
});

Также, во втором параметре, можно явно указать напечатать ли представление в stdout или вернуть в строке. В третьем параметре, можно передать произвольные данные в действие для использования.

Блоки и переменные шаблонов

Файлы layout и шаблоны центральной части страниц, в Jet, устанавливаются в контроллерах одновременно, перед началом визуализации. Оператор @extends из Blade (или аналогичный из Twig) отсутствует в Jet, так как, такое наследование рационально только для шаблонов layout. Дальнейшее манипулирование визуализациями в частях шаблонов, вполне легко обеспечивается sub-представлениями или использованием блоков. Типичное применение блоков такое: в шаблоне layout помечаются места с помощью оператора @block, а в шаблонах центральной части страниц применяется @use или #use. Таким образом, визуализация от body внедряется в layout. Или наоборот: в layout декларируется @use или #use, а в шаблонах центральной части страниц указывается @block, - так общая часть визуализации от layout, может быть внедрена в произвольное место body-части выборочных шаблонов. В некотором смысле, блоки - это альтернативный функционал для sub-представлений, так как оператор @view($different) может быть использован с переменной. Но блоки имеют преимущества в шаблонах - очень часто, может быть неэффективно всегда использовать обработчики для простой замены в части визуализации. А преимущество функции view(..) - она может гибко использоваться в коде приложений, а не только в Jet-шаблонах. Примеры использования функционала блоков:

// в layout шаблоне:
<h1>@block(h1)My default title~block</h1>

// в шаблоне body это заменит содержимое тега H1 на этапе компиляции:
#use(`Some other title` as h1)

// а так в компилированном шаблоне будут присутствовать оба варианта, и если @use выполнится,
// то визуализируется его шаблон, иначе шаблон из @block:
@if($boolean_rule)
    @use(`Some other title` as h1)
~if

// также можно вызвать обработчик:
#use(`` as b h1)
// это действие в layout, вместо "@block(h1)My default title~block" вставит код:
<?php MVC::handle('b_h1', $_vars) ?>
// в common_c::b_h1(&$vars) можно сделать "echo", так как шаблон пустой
// ===============================================================

// Обработчик может передать переменные. Сложная визуализация по умолчанию в layout шаблоне:
@block(complex.tpl1 as z*miracle)         <!-- или лучше вместо tpl1, назвать маркер: z_miracle -->

// заменяется на другую сложную визуализацию под контролем body:
#use(complex.tpl2 as q miracle)

В последнем примере @block-#use, подразумевается отдельный обработчик для блока шаблона в методе common_c::z_miracle(&$vars) (или в мастер контроллере - с_some_controller::z_miracle(&$vars)) для @block. Префикс z* вставит вызов обработчика в начало файла откомпилированного шаблона, перед extract($_vars, EXTR_REFS), кодом формирующим все основные переменные шаблона из массива (основная область видимости переменных):

<?php # начало откомпилированного шаблона
# .. здесь может быть что-то еще..
$_z_ = (array)MVC::handle('z_miracle', $_vars); # вызов хендлера блока
MVC::vars($_vars, $_z_, 'z_'); # добавление префикса $z_ к переменным, проверка на то, что имена переменных
# не начинается с подчеркивания (внутренние нужды Jet), и создание итераторов eVar из массивов с префиксом e_
# ... здесь возможно другие подобные обработчики @[email protected]
extract($_vars, EXTR_REFS); # генерация переменных шаблона из массива
?>
.. и где-то вместо @block ~block, вставится откомпилированный шаблон complex.tpl1

Обработчик ::z_miracle(..) должен вернуть массив (или то что сможет привестись к массиву с помощью (array)). Переменным не имеющим однобуквенного префикса, добавится префикс z_. Для такого обработчика, в общей области видимости переменных шаблона, нельзя использовать буквы "k", "e", "y" - это приведет к фатальной ошибке. Также, нельзя допускать использование одной и той-же буквы в вызовах разных обработчиков, в одном откомпилированном файле шаблона. Переменные с префиксом "$k_" - всегда экспортируемые переменные SKY::$vars. Переменные "$e_" - итераторы eVar. Переменные "$y_" - переменные layout. Во избежание путаницы, также лучше не использовать буквы основных действий - "a" и "j". Но возможно, может оказаться уместным использование одного и того-же шаблона и хэндлера к нему в sub-представлении и блоке Jet - буква "x".

Для префикса "q "#use), вместо оператора @block..~block (а не в начало откомпилированного шаблона), вставится код:

..
<?php call_user_func(function() use(&$_vars) {
    $_a = SKY::$vars;
    $_b = (array)MVC::handle('q_miracle', $_vars);
    MVC::vars($_a, $_b);
    extract($_a, EXTR_REFS) ?>

        здесь откомпилированный шаблон complex.tpl2

<?php }) ?>
..

Если бы откомпилированный шаблон complex.tpl2, не имел исполняемый код PHP, как в примере #use(`` as b h1), вызов Closure, для создания своей собственной области видимости переменных, не был бы сгенерирован. Произошла бы оптимизация: нет символа ?, значит в откомпилированном шаблоне нет открывающего тега <?php и нет использования переменных, возвращенных обработчиком.

Итак, @block выполняется всегда, только если не был найден #use, а #use выполняется только когда был найден @block. Перед именем блока, может присутствовать префикс: "буква-пробел" или "буква-умножение" и для @block и для #use. Первый, вызывает обработчик в месте шаблона, где был найден @block и в своей собственной области видимости для переменных шаблона. Переменные, не имеющие однобуквенного префикса, остаются как есть, в отличие от "буква-умножение". Второй, вызывает обработчик в начале откомпилированного шаблона, в общей области видимости переменных шаблона. Как показано выше, при вызове обработчиков, им передается весь массив переменных $_vars, сформированный основной обработкой в ::head_y(..), ::tail_y(..) и основным действием ::j_action(..) или ::a_action(..). Поэтому, если необходимо использовать эти переменные в действиях @block-#use, нужно принимать этот массив исключительно по ссылке.

Вариант использования @use, не заменяет визуализацию блока на этапе компиляции, как #use, а добавляет дополнительную визуализацию, которая будет отображена, если только выполнится @use в коде, иначе будет показана визуализация из @block.

Без таких префиксов, вызов обработчика не будет добавлен в откомпилированный шаблон. Но будет произведена замена шаблона @block шаблоном @use, если последний будет найден. Это также предоставляет большие возможности для манипуляций с визуализациями. Можно определить переменные для таких случаев, например, в common_c::tail_y() и использовать в шаблонах блоков, если это не влияет на производительность или эти переменные должны быть вычислены всегда. Но если необходимо поднять производительность кода, то при отключении некоторой визуализации в @[email protected], можно отключать вычисление переменных для такой визуализации, поместив их в отдельный обработчик (action).

Использование блоков, возможно не только в связке layout-body шаблоны, объединенные через инклудер центральной части страницы @inc(*). Замена в операторах @[email protected], может быть обусловлена использованием условий препроцессора. Когда @use может быть отключен или включен, в зависимости от условия сравнения в #if(..). В статической последовательности включенных шаблонов, одновременное использование @[email protected] бессмысленно, так как работать будет только @use и никогда @block. Хотя, может иметь смысл, статический вызов обработчика для пустого @block из определенного места в шаблоне для некоторой специальной обработки.

⚠️ **GitHub.com Fallback** ⚠️