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

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

@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.

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

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

В 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

PHP дает возможность программистам работать с итераторами или генератором yield для составления отложенных вызовов и перебора рядов например в цикле foreach. Но во первых работать с ними неудобно и сложно, во-вторых yield работает с версии PHP 5.5. Что имеется ввиду под "отложенными вызовами": представьте что вам нужно вывести в браузер большой листинг из 1000 или более строк. Можно считать массив из БД в контроллере в переменную и в шаблоне перебрать строки и вывести. Но.. когда массив большой, это становится затратно по ресурсам сервера. Гораздо эффективнее в контроллере выполнить запрос к БД, но делать "FETCH" рядов из БД в самом шаблоне (отображении) и сразу же выводить их в STDOUT.

Итераторы в Jet настолько удобны и просты, что на самом деле удобно делать почти все листинги с использованием этого механизма. Для организации итераторов Jet в шаблоне, достаточно лишь написать в цикле (используется цикл PHP - while()) @for($e_list : $item) или @for($e_list) или @do .. ~do($e_list), читайте подробнее в узле VIEW.

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

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

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

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

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

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

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

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

1) браузер отправляет запрос на сервер (request);
2) в зависимости от первых двух параметров $_GET запроса, выбирается контроллер и действие (т.е. конкретный метод конкретного контроллера);
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. На самом деле все верно, роутинг это атавизм: в данном случае, архитектурно, следует реализовать только наиболее частый случай, а если есть ньюансы, то их нужно реализовывать "руками" в контроллерах. Иная архитектура, только лишь усложнит, запутает и сделает приложения менее производительными.

Критика Laravel Blade

На русско-язычном сайте, посвященном Laravel, я прочитал: Два основных преимущества использования Blade — наследование шаблонов и секции, мне стало смешно.. В чем собственно преимущество, не разъясняется, по-видимому, просто в том, что наследование и ООП это круто :-)

Главная цель любого кода для повторного использования, - сделать программирование проще. Написать мало, а получить много. Это достигается за счет только лишь одного принципа: делать код по умолчанию. Даже само наследование в ООП, это возможность использования кода по умолчанию родительского класса! Тот-же принцип. Вот jQuery - "молоток", у меня почти нет к нему претензий, имеет девиз: Write less, do more и он следует девизу.

Чаще всего веб-приложения, используют один master-layout для многих страниц. Это значит, что по-умолчанию должен быть реализован этот наиболее частый случай. Т.е. для ajax запросов, по умолчанию, следует выключать layout, а для не ajax запросов, включать один и тот-же master-layout. Тогда ни в контроллерах ни в шаблонах, чаще всего,  просто не придется писать что-то по этому поводу, все уже будет подготовлено. В Blade же требуется всегда явно указывать @extends(master-layout), писать один и тот же код для многих child-шаблонов, хотя если это частый случай, это должно делаться автоматически. И передавать переменные в шаблон изнутри другого шаблона, это тоже круто (в обратном смысле слова). Шаблоны должны определяться там, где заполняется массив переменных для них - в контроллерах, например, это естественно и просто. Так, что "turn off the lights", господа.
опубликовано ENERGY - 19 Jun 2018 06:20 GMT
последнее редактирование - 3 Jul 2018 11:28 GMT
 +  0  -  комментировать