Шаблонизатор представлений Jet

SKY / WINGS / SECOND /
JET1
Использование шаблонизаторов в коде веб-приложений увеличивает вероятность ошибок (с одной стороны), но те преимущества, которые они предоставляют однозначно перевешивают недостатки, это:

1) возможность увеличить скорость отклика страниц сайта;
2) более простое составление шаблонов, чем на чистом PHP;
3) способствует написанию кода, устойчивого к XSS-атакам;
4) удобные сервисные функции: автоматические переменные, препроцессор и прочее;

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

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

СинтаксисОписаниеСовместимость
{{переменная или выражение}}, например {{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 код. Поэтому если вам нужны булевые операторы в выражениях, просто используйте аналоги с более высоким приоритетом && и ||.

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

@view( name ) - вставить вместо оператора в компилированный шаблон выдачу действия `name`. Действие ищется вначале в "своем" контроллере, если не найдено, то в общем контроллере `common_c`. Выдача при таких вызовах происходит по всем правилам обычных действий в контроллерах, т.е. можно использовать `layout`, `body`, устанавливать переменные в массив ->_v и ->_y, использовать шаблоны. По умолчанию `layout` отключена (значение равно пустой строке), а `body` соответствует имени `view`. Если `name` не имеет префикса, то устанавливается автоматически x_, т.е. вызов будет идти на метод контроллер::x_name(), в отличие от обычных действий (action), которые имеют префикс a_, например контроллер:: a_actname()

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

@loop(..), ~loop, @loop, ~loop(..) - циклы. Для циклов PHP for(), foreach(), while(), do-while(), в Jet используется один и тот-же синтаксис: @loop. Какой именно цикл необходимо использовать, Jet определяет на основании того, что находится в скобках. Внутри каждого из этих циклов, можно использовать @empty (кроме do-while()). Следующий за этим оператором код выполнится, если не пройдет ни одна итерация. Циклы могут иметь произвольную вложенность, для них генерируются автоматические переменные $a, $a_2 .. $a_N, содержащие номер итерации (первая итерация имеет значение равное нуль). Внутри всех циклов можно также использовать @break(..) и @continue(..). Имеется также один специальный синтаксис, которого нет в Blade: @loop($e_list) и ~loop($e_list) (для do-while), описание смотрите ниже.

@inc(..), @require(..), @body(..) - включение файлов. Первый, @inc вставляет компилированный файл на свое место, @require компилирует файл и вставляет в код инструкцию PHP require с ссылкой на него. @body используется в основном в `layout` макетах и интегрирует файл на свое место (или инструкцию require) соответствующее файлу содержащемуся в $view->body при вызове Jet или вставляет <?php echo $STDOUT ?> (если в контроллере было сделано echo..) .

Если @inc или @require включают файл с префиксом `r_`, это включение будет содержать механизм red LABEL, например: @inc(r_counters)

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

@php, ~php - вставка на php

@pdaxt генерирует код <?php FRONT::pdaxt() ?>, смотрите узел PDAXT

@head(..), @tail генерируют код <?php FRONT::head(..) ?> и <?php FRONT::tail() ?> соответственно

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

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

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

@mime(..) меняет значение значение заголовка `Content-Type` ответа на указаный и устанавливает $sky->ajax = true; Это удобно использовать например для генерации 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 символа. На самом деле, просто кумаритнадоело писать довольно часто вот это: href="javascript:;"

@csrf() - выводит значение CSRF токена в форму: <input type="hidden" value="{{scrf_token_value}}" name="_csrf" />

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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
<?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, взглянув на функцию сразу понятно, что она используется как callback), а в ней вызовите Jet::directive() и добавьте все необходимые определения. Почему используется callback? Да потому что, большую часть времени, на production, файл main/w2/jet.php вообще не загружается (используются готовые откомпилированные шаблоны), это увеличивает производительность приложений.

Итераторы в Jet

Итераторы в Jet настолько удобны и просты, что на самом деле удобно делать почти все листинги с использованием этого механизма. Для организации итераторов Jet в шаблоне, достаточно лишь написать в цикле (используется цикл PHP - foreach()) @loop($e_list as $i => $item) или @loop($e_list). Итераторы могут работать не только с SQL запросом, но и формировать значения без обращения к БД, динамически в контроллерах или чаще в моделях.
Из контроллеров удобно передавать переменные в представление через return "массив". Также из моделей удобно передавать переменные в контроллер (и далее в представление) через return "массив", особенно "function listing()" в переменную с префиксом $e_ :

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
<?php
# модель
    ...
    function listing() {
    ...
        return [
            'title' => ...
            'query' => ... # sql query or just sql string
            'row_c' => ... # callback function for rows tuning
        ];
    }
    ...
# контроллер
    ...
    function a_page() {
        ...
        return [ # переменная с префиксом e_ автоматически преобразуется в объект SKY-итератора eVar
            'e_tbl' => $this->t_tbl->listing(),
        ];
    }
    ...
# представление
...
<h1>{{$e_tbl->title}}</h1>
@loop($e_tbl)
    {{$row->column}}
~loop
...
 

Читайте подробнее в узле VIEW.

Маркеры частей файла

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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
#.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. Часто удобно в контроллерах не менять предустановленный шаблон, который всегда указывает на часть файла, а использовать `return` для установки переменных шаблона $view->_v. В этом случае удобнее всего написать алиас для части шаблона, который будет соответствовать действию в контроллере:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
<?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.

Маркерами пользоваться удобнее, чем делать наследования или включения других шаблонов. Во-первых, часто бывает нужно много маленьких шаблонов, которые можно компактно разместить в одном файле. Во-вторых, иногда, при ajax запросах бывает нужна меньшая часть шаблона, который используется и в полном виде. Но делать верстку удобно целиком, не разделяя полный шаблон на разные файлы.

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

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

#if(..), #elseif(..), #else, #end - условные операторы препроцессора.

Последовательность обработки запросов браузера и архитектура SKY-приложений такова:

1) браузер отправляет запрос на сервер (request);
2) в зависимости от первых двух параметров адреса запроса, выбирается контроллер и действие (т.е. конкретный метод конкретного контроллера);
3) но перед этим устанавливаются значения по умолчанию используемых layout и body. Для ajax запросов layout по умолчанию выключается, а для не ajax, включается master-layout (макет по умолчанию). В body всегда записывается прототип имени контроллера. layout и body - это переменные содержащие прототипы имен шаблонов для Jet и использующиеся при каждом запуске view();
4) в контроллерах делаются вызовы к моделям, осуществляется бизнес-логика приложения;
5) в контроллерах заполняются массивы ->_v и ->_y для body и layout соответственно, корректируется если нужно значения body и layout;
6) запускается top-view, в котором парсятся шаблоны, если они не готовы, и генерируется полный ответ (respond) на запрос;

Так вот, компилированные шаблоны генерируются отдельно для каждой комбинации body и layout (а также от выбранного стиля, опции "DESIGN" и версии "мобильное/не мобильное" устройство). Т.е. в имени файла откомпилированного шаблона закодированы 5 переменных. Если у вас, например, в master-layout есть условные операторы анализирующие одну из этих переменных, то их имеет смысл сделать условиями препроцессора. Это вырежет "куски" шаблона на этапе компиляции и ускорит рендеринг ответа сервера, в то время когда будет использоваться компилированный шаблон.

Вероятно у вас могут возникнуть вопросы по поводу пункта 2. На самом деле все верно, роутинг это атавизм: в данном случае, архитектурно, следует реализовать только наиболее частый случай, а если есть ньюансы, то их нужно реализовывать "руками" в контроллерах. Иная архитектура, только лишь усложнит, запутает и сделает приложения менее производительными.
published ENERGY - 19 Jun 2018 06:20 GMT
last edit - 11 Nov 2021 02:24 GMT
 +  0  -  add comment