%D0%A8%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%82%D0%BE%D1%80 %D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9 Jet - energy-coresky/air GitHub Wiki

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

Существуют следующие элементы управления шаблонами:

  • MVC::$layout - указывает на layout шаблон, используется совместно с body. Файл шаблона имеет префикс y_ в имени файла, но в MVC::$layout префикс указывать не нужно;
  • MVC::$_y - переменные layout, префикс $y_ добавляется автоматически, если нет никакого другого однобуквенного префикса. Статический массив переменных;
  • $mvc->body - указывает на имя файла шаблона и возможно уточняет его часть (маркер);
  • $mvc->_v - переменные центральной части страницы (для body). Может быть несколько экземпляров класса MVC - несколько subview + один topview. Для blkview экземпляр класса MVC не создается;
  • $mvc->return - возвращать визуализацию в строке или вывести в STDOUT;
  • $mvc->ob - переменная содержит вывод буфера STDOUT, если в контроллере было сделано echo;
  • $sky->is_mobile - переменная поддержки отдельного стиля для мобильных устройств, значение используется в имени компилированных шаблонов Jet;
  • Plan::$view - имя активного view-ware. Может использоваться для реализации поддержки нескольких стилей приложений;

Для sub-представлений генерируется отдельный шаблон, поэтому переменные изолированы от других шаблонов. В компилированных шаблонах top-view, переменные body и layout, находятся в одной области видимости. Имена переменных начинающиеся с подчеркивания, зарезервированы для автоматических и служебных переменных Jet, поэтому, если попытаться передать переменную с таким именем, доступ к её значению будет возможен из массива SKY::$reg:

// возврат из контроллеров:
return [
    'for_body' => 1,   # $for_body - главное, простое правило
    'for_layout' => 2, # $y_for_layout - префикс y_ добавится автоматически
    'z_layout_or_body' => 3, # $z_layout_or_body - нет авто-префикса
    '_any_view' => 4,  # значение будет доступно в $sky->_any_view
];
// $_, $_2, .. - автоматические переменные с номером итерации циклов

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

Синтаксис Описание Совместимость
{{переменная или выражение}}, например {{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 $mvc->ob ?> (если в контроллере было сделано echo и определен инклудер центральной части страницы в layout @inc(*)). Примеры: включить весь файл: @inc(file_name), часть файла: @inc(file_name.marker_name) или часть "своего" файла @inc(.marker_name). В такой нотации @inc(file_name.) - будет включён шаблон из другого файла с таким же (текущим) именем маркера. Такие включения удобно делать из шаблонов определенных в "магическом" маркере "_". Если @inc включает шаблон (файл) с префиксом r_, это включение будет содержать механизм RED-LABEL, например: @inc(r_counters). Первым символом внутри скобок, также может быть двоеточие : в этом случае, будет работать "внешний" поставщик inline-шаблона. Читайте об этом подробнее в разделе "Jet inline mode".

@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 в контроллерах.

@eat - удаляет после себя все пробельные символы

@svg( [package.]name ) - вставляет вместо оператора, код SVG изображения из пакета в плане "mem" текущего Ware. Префикс в плане "mem" - svg_list_, расширение файлов .pack, имя пакета по умолчанию - 0. Например, в шаблоне Jet, при вызове @svg(settings) источник будет находиться в файле path_to_wares_mem/svg_list_0.pack. Eсли указано имя пакета: @svg(1.name), то файл: svg_list_1.pack. Пример синтаксиса изображения внутри пакета:

:settings viewBox="0 0 512 512" width="16" height="16" fill="currentColor"
<path d="..."/>

:other_image_definition

К пакетам, также можно обращаться по ссылке, например в CSS-файле: pre:focus {outline:none; background:url(../?_svg=settings)}. В DEV-приложениях это "достаёт" изображение "на лету". Или для продакшн: url(../img/_settings.svg). После установки кода продакшн, необходимо выполнить в консоли sky warm svg это сгенерирует реальные изображения в папке public.

@pdaxt - генерирует код <?php MVC::pdaxt() ?> стандартных ссылок: P - production, D - dev-tools, 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(..) - используется для печатания абсолютного пути к статическим файлам, например картинкам или формирования длинных семантических URL (добавляется значение константы PATH). PATH может отличаться на разных инстансах приложения. Если статика хранится не на своем домене, можно создать собственные statements приложения, например @p2(..) и т.д.

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

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

@mime(..) - меняет значение значение заголовка Content-Type ответа на указанный и устанавливает $sky->fly = 2; Это удобно использовать например для генерации 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 токена в форму: В формах, построенных с помощью класса 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)
    <a@active($uri == URI) href="{! $uri !}">{{ $name }}</a>
~loop

удобно делать даже вот так:
<div style="display:@none(2 != SKY::d('trans'))">...

У оператора по умолчанию двойное назначение: если имя оператора соответствует некоторому установленному WARE, то он будет работать подобно @p(..):

<a href=@acl(acl?profile)>PROFILES</a>
строка выше даст:
<a href="{{PATH}}ctrl/acl?profile">PROFILES</a> - if Acl ware use "tune" "ctrl"
<a href="{{PATH}}acl?profile">PROFILES</a> - if Acl ware do not use "tune"

То есть, этот вариант удобно использовать для настраиваемой адресации продуктов. Если имя оператора конфликтует с другим оператором, необходимо сделать аналогичный custom-оператор с другим именем.

2do: сделать переназначение имен операторов?
#opname(act=active)
#alias(x=acl)
#..??

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

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

Custom операторы

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

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, файл w2/jet.php вообще не загружается (используются готовые откомпилированные шаблоны), это увеличивает производительность приложений. Если новых операторов много, вызов Jet::directive(), также, можно поместить в отдельный файл mvc/jet.php по той же причине.

Итератор eVar

Использование eVar подобно использованию генераторов, реализованных с помощью yield, но имеется дополнительная функциональность:

  • возвращаемые из callback функции массивы, автоматически приводятся к объектам stdClass
  • имеется защита от ошибочных бесконечных циклов в 500 итераций. Когда код отлажен, можно отменить ограничение указав максимальное количество итераций -1 (бесконечно)
  • итератор умеет пропускать итерации с помощью return true
  • в callback функцию автоматически возвращается переданное значение предыдущей итерации, а также номер текущей итерации (начинается с 0)
  • итератор можно пере-инициализировать с помощью eVar::__invoke(..)
  • а также некоторая другая функциональность

Итератор настолько удобен и прост, что на самом деле, удобно делать почти все листинги Jet с использованием этого функционала, хотя он востребован не только для этого. Он разработан так, чтобы максимально просто разделять код в шаблонах Jet и контроллерах/моделях. eVar это универсальный итератор, который поддерживает практически любую алгоритмическую необходимость, в отличие от генераторов использующих yield. Пример использования для MVC:

# модель
    ...
    function listing() {
    ...
        return [
            'query' => ... # sql query (instanseof SQL) 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";
    },
    'str_c' => function ($e) {
        echo "\nstr_c called";
        return 1 + $e->key();
    },
]);

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

# output:
0. first
1. second
2. third
3. first
5. first
last index: 5
str_c called
iterations: 6

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

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

query - строка SQL запроса или объект класса SQL. Отсутствует, при использовании "ручного" составления рядов

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

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

str_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::_def(), обработчик для итератора $e_idents.

Если в массиве, который передается итератору только один ключ, тогда вместо массива, можно передать Closure (как row_c) или строку/объект SQL (как query), это сократит код:

# Closure:
$fn = fn($i) => $i > 5 ? false : ($i % 2 ? ['i' => $i] : true);
foreach (new eVar(fn($r) => $fn($r->__i)) as $row)
    echo $row->i; // 135

# String:
foreach (new eVar('select * from $_') as $row)
    echo $row->column;

Во время работы с SQL, можно полностью заменить значение ряда или реализовать, например, задержку выдачи рядов. В этом случае, параметр $row, нужно принимать по ссылке:

<?php

class some_model_t extends Model_t
{
    . . .
    function row_c(&$row) { # use this in array like: [$this, 'row_c']
        static $prev;

        if (!$row->__i) { # first row
            $prev = $row; # usable when "left join"
            return true; # skip row
        }
        $tmp = $row;
        $row = $prev;
        $prev = $tmp;
    }
    . . .
}

Переменные SKY::$vars

В Coresky, три типа префиксов переменных, в шаблонах Jet имеют специальное функциональное назначение: $k_, $e_, $y_, функционал переменных с другими однобуквенными префиксами, определяется кодом приложений. Последние, $y_ - это переменные Layout. Этот префикс добавляется автоматически, для переменных, возвращаемых из ..::head_y(..) и ..::tail_y() контроллеров, если у них отсутствует явный однобуквенный префикс. Переменные $e_.. - итераторы eVar, а с префиксом $k_ - переменные SKY::$vars. Если нужно, чтобы переменная передавалась во все шаблоны, включая SUB-VIEW и BLK-VIEW, в контексте одного HTTP запроса - определите ее значение в SKY::$vars:

$this->k_user = $user; # во всех шаблонах будет доступна переменная $k_user

Также, SKY::$vars бывает удобно использовать, чтобы не передавать некоторое значение по длинной цепочке в контроллер из моделей.

Массив SKY::$vars может использоваться кодом приложений и первоначально пуст. Он бесполезен для использования в продакшн продуктах, ориентированных на любые SKY-приложения, из-за возможных коллизий в именах переменных. Хотя, использование этих переменных в таких продуктах, может декларироваться в их параметрах. Если это заявлено, то перед использованием продукта, программист приложения, должен проверить, что коллизий нет. Если такой продукт использует SKY::$vars, то рекомендуется использовать только одну переменную, соответствующую имени продукта. Например продукт Acl, использует одну переменную $k_acl, содержащую объект класса stdClass.

Объект SKY имеет дополнительно, два других реестра данных: SKY::$reg и SKY::$mem. Последний используется для обеспечения функционирования "Ghost SQL", а SKY::$reg - простой реестр, возвращающий пустую строку при чтении несуществующих переменных. Это часто удобно для сокращения кода приложения:

echo $sky->undefined_variable; # no error

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

Переменная $sky, содержащая объект класса SKY, всегда передается во все шаблоны. Поэтому, информация из SKY::$reg, также доступна во всех шаблонах. Обычно, информация в $sky, в шаблонах, должна только считываться, но не записываться. Использование её для записи, детектируется DEV-утилитой "Global Reports" и помечается, как потенциальная ошибка. Коллизии в именах SKY::$vars, также детектируются утилитой.

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

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

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

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

Несуществующие в этом файле шаблоны попадут в магический маркер, и поиск направится
в другой файл. Если в текущем файле было обращение к шаблону `thisfile.test`, то в файле
otherfile.jet нужно определить маркер `#.test`, ну или `#._`
#._
@inc(otherfile.)
#._
Будьте осторожны с магическими маркерами, хотя Jet легко детектирует зацикленные включения маркеров.

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

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

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

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

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

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

Чтобы в исходных шаблонах использовать короткие и лаконичные условия для сравнения, применяются псевдопеременные. Нужно создать файл mvc/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]

:main       'main' == Jet::$fn[0] /* main ware */
:mob        'm' == Jet::$fn[1]
:pc         ! :mob

:layout     Jet::$fn[2]
:svg        'svg' == :layout

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

# препроцессор также автоматически добавит псевдопеременные:
:0         $sky->_0
:1         $sky->_1
:2         $sky->_2
:-         true, если перед условием препроцессора пустая строка

# еще, в условиях препроцессора, имеет смысл использовать PHP-константу URI

Если в некоторый шаблон, по AJAX, должна вставиться визуализация, использующая другой шаблон, то определите последний шаблон на "своем" месте и окружите его условием: #if(:-)..#end. Когда Jet использует вложенные маркеры, то #if(:-) будет равно true, а когда внешние, то false. Это удобно для понимания кода:

#.outer
...
<div id="from-ajax">
#.ajax
#if(:-)
  ajax template
#end
#.ajax
</div>
...
#.outer

Пусть, имеется шаблон 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), кодом формирующим все основные переменные шаблона из массива (основная область видимости переменных):

# начало откомпилированного шаблона
# .. здесь может быть что-то еще..
$_z_ = (array)MVC::handle('z_miracle', $_vars); # вызов хендлера блока
MVC::vars($_vars, $_z_, 'z_'); # добавление префикса $z_ к переменным, проверка на то, что имена переменных
# не начинается с подчеркивания (внутренние нужды Jet), и создание итераторов eVar из массивов с префиксом e_
# ... здесь возможно другие подобные обработчики @block-@use
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() и использовать в шаблонах блоков, если это не влияет на производительность или эти переменные должны быть вычислены всегда. Но если необходимо поднять производительность кода, то при отключении некоторой визуализации в @block-@use, можно отключать вычисление переменных для такой визуализации, поместив их в отдельный обработчик (action).

Способы использования блоков Jet

  1. Замена блока кода в layout с помощью #use. Этот функционал аналогичен использованию блоков в Blade и Twig, когда подменяется блок в родительском шаблоне;

  2. Инжекция блока кода layout в тело центральной части страниц (наоборот). В этом случае, #use определяется в layout, а @block в body шаблонах;

  3. Можно компилировать несколько визуализаций для блока, т.е. разные @use могут отрабатывать или нет, в зависимости от условий @if, в которых они находятся. Кстати циклы, в этом контексте - тоже условия. Если в цикле @block - он визуализируется по количеству итераций, а для @use положение в цикле, - это просто срабатывание условия;

  4. Представьте, что имеется метод, который выводит визуализацию непосредственно в STDOUT и возвращает другую визуализацию в строке. Тогда, с помощью блоков, возвращенную в строке визуализацию можно поместить в любое место. В случае если @block определен выше @use, будут использованы функции PHP семейства ob_.. Или же второй пример: переменные блока (и вся визуализация) не вычислены в тот момент, где определен блок. Когда визуализация будет готова, используем @use:

# пример 1
{!Root::_overview(0)!} # меню будет после таблицы

@block(`` as menu) # меню будет перед таблицей
@if($menu = Root::_overview(0)) # этот метод выводит таблицу прямо в STDOUT
    @use(`{!$menu!}` as menu)
~if

# пример 2
@block(`` as late_visual) # визуализация еще не вычислена
@loop($e_some_iterator)
    ..
    # в callback-методе row_c собирается визуализация для блока late_visual
    ..
~loop
@if(true) # нужно заключить в условие, иначе @use сработает как #use
    @use(`{!$e_some_iterator->get_visual()!}` as late_visual)
~if
  1. Замена блоков может быть обусловлена использованием условий препроцессора. Когда #use может быть отключен или включен, в зависимости от условия сравнения в #if(..);

  2. Можно определить блок в любом месте шаблона, с целью вызова его обработчика, без намерения использовать #use. Это может иметь смысл по разным причинам. Например, когда есть общая, обособленная часть шаблона для разных страниц и вместо использования @view(..) (SUB-View), нужно сделать inject шаблона в родительский (BLK-View);

  3. Синхронизация master/slave: представьте два блока - "А" и "В". Последний содержит в своем шаблоне вложенный use, который делает замену блока "А". Если шаблон "В" актуален, то "А" "показывает" шаблон из use вложенный в блок "В". А если блок "В" заменен некоторым use, то вложенный use, который ссылается на "А" пропадет, и "А" покажет другую визуализацию. Это будет происходить синхронно с выбором визуализации блока "В".

  4. Блоки удобно использовать для реализации нескольких визуализаций, имеющих общую часть (подобно layout). В Blade для этого необходимо наследовать родительский шаблон с помощью @extends и в дочернем переопределить блоки. А в Jet - просто включите родительский шаблон и замените блоки с помощью #use:

#.tool_1
@inc(tools.layout)
#use(.head)
#use(.body)
#use(.tail)
#.tool_1.head
head code
. . .
#.head
. . .

Но есть способ мощнее для j-top-view или sub-view, - установите реальный layout: MVC::$layout = '_venus.tool';, а в самом layout, вместо определения блока @block(body) .. ~block, используйте @inc(*). Всё это неплохо сократит код.

Статус return компилированных шаблонов

В компилированных шаблонах, для j_ (CSN-AJAX) действий, всегда используется "статус return" равный true. Это означает, что в начале файлов, используется ob_start(), а в конце return ob_get_clean(), т.е. визуализация возвращается в строке. Для действий a_, "статус return" всегда false, т.е. визуализация "уходит" прямо в STDOUT и функции PHP семейства ob_ не используются. Для sub-представлений, файлы могут компилироваться по разному, в зависимости, от параметра $return функции view(..). Иногда, для sub-представлений, может потребоваться один и тот-же файл, но с разным "статусом return". Вызов, когда "статус return" не совпадает, приведет к фатальной ошибке. В таких редких случаях, необходимо сделать алиас для маркера и применить MVC::body(..), чтобы создать компилированный файл с другим именем, так можно разрешить конфликт.

Функции PHP, семейства ob_, могут так-же использоваться для файлов со "статусом return" равным false, для реализации функционала блоков. Если в компилированном файле, используется условно выполняемый @use(..), то его код содержится вместе с кодом блока. Если @use(..), условно выполняется позже кода блока, то нет никакой другой возможности, кроме как, вместо кода блока поместить подобный код:

<?php $_ = 0; while ($_ < 3): ?>
    <h1><?php $_ob[] = ob_get_clean(); $_ob["10-$_i10"] = ''; ob_start(); $_i10++ ?></h1>
<?php $_++; endwhile ?>

В примере выше, блок определен внутри тега <H1> и выполняется в цикле. Т.е. визуализация всего файла, помещается в буфер-массив $_ob и когда станет ясно, выполнился ли @use(..) или нет, тогда в значения массива $_ob["10-$_i10"] скопируются визуализации либо блока либо @use(..). А в конце файла будет сделано: echo implode("", $_ob);

Jet inline mode

Для режима DEV (или невысоко нагруженных скриптов), также можно использовать компилятор Jet в режиме inline:

$output = Jet::inline(string $inline_template, ?array $vars);

Такой запуск не создаёт кешируемый компилированный шаблон по умолчанию. При этом, inline шаблон, может поставляться из любого источника, например из БД, а не плана view.

Родительские шаблоны могут содержать инструкцию @inc(:..) (а также block и use) начинающуюся с двоеточия. Это подразумевает включение inline шаблона из внешнего источника: будет вызван метод _inc, который должен возвратить inline шаблон. В качестве параметра, ему будет передано содержимое скобок из родительского шаблона. Метод _inc, должен быть определен, как custom-оператор:

<?php

class venus_c extends Controller
{
    function jet_c() {
        Jet::directive('_inc', function($arg) {
            return sql('+select tpl from $_ where name=$+', $arg);
        });
    }
    . . .
}

Эта возможность Jet, используется, например, в DEV-продукте Venus.

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