QuadBraces III +9


Доброго всем здравия!

Прошло практически ровно два года с момента моей первой публикации о парсере QuadBraces — альтернативе MODX Evolution для простейших проектов, требующих шаблонизации. Это могут быть одностраничники с типовыми публикациями, портфолио, сайты-визитки, состоящие из почти статических страниц, и прочее подобное. С тех пор я обновлял свою разработку и незаметно для сообщества дополз до третьей версии. Нынешняя версия парсера QuadBraces содержит столько изменений, что я просто обязан опубликовать подробный туториал. Итак…

Теги


Начнём с обработчиков тегов. В QuadBraces 3 обработчики тегов вынесены в отдельные классы. Теперь разработчик волен определять свои типы тегов, не меняя код самого парсера. Для этого необходимо создать потомок класса QuadBracesTagPrototype, где нужно определить следующее:

  • $_name — название-идентификатор тега
  • $_start — начальная последовательность тега
  • $_rstart — начальная последовательность тега для режима синтаксиса MODX Revolution. Без квадратных скобок.
  • $_finish — конечная последовательность тега
  • $_order — опционально; порядок обработки
  • function main(array $m,$key='') — функция, возвращающая результат обработки тега. $m — данные PCRE-регулярки, ключ (самое важное) — ключ найденного тега.

На примере обработчика тегов константы:

class QuadBracesTagConstant extends QuadBracesTagPrototype {
  protected $_name   = 'constant';
  protected $_start  = '\{\*';
  protected $_rstart = '\/';
  protected $_finish = '\*\}';
  protected $_order  = 5;

  public function main(array $m,$key='') {
    $v = '';
    if (empty($key) || !defined($key)) {
      $this->_error = 'not found';
    } else { $v = constant($key); }
    return $v;
  }
}

Ага, точно — всё именно настолько просто. Хотя меня, как программиста старой закалки жутко бесит необходимость хранить файлы микроскопических размеров. Но это уже дело личное, как говорится.

Для тех, кто не читал предыдущую публикацию. Синтаксис тегов QuadBraces един для всех типов тегов:

  1. Начальная последовательность символов. Например, "{{".
  2. Идентификатор сущности. Должен состоять из латиницы, цифр, «бревна», тире и/или точки. Например, «my-chunk». Обратите внимание! Точка в идентификаторе играет роль разделителя уровня вложенности. То есть, забегая вперёд, например, переменная «my.var» будет располагаться по факту здесь — $parser->data['my']['var']. О чанках, сниппетах и шаблонах — ниже.
  3. Некоторое количество расширений-обработчиков (опционально). Каждое представляет собой конструкцию вида ": некий_идентификатор" или ": некий_идентификатор=`некие_данные`" (без пробела между двоеточием и идентификатором). Расширения-обработчики работают с конечным результатом обработки тега. То есть, например, получили мы переменную, содержащую номер телефона, а потом отформатировали её для человекопонятности.
  4. Для совместимости с синтаксисом MODX далее может следовать знак вопроса.
  5. Некое количество аргументов (опционально). Каждый аргумент — конструкция вида "&некое_имя=`некое_значение`". Да, аргументы могут быть даже у переменных. Да, они работают. В большинстве случаев переданное в аргументах используется в локальных плейсхолдерах.
  6. Конечная последовательность символов. Например, "}}".

В третьей версии парсера, как вы уже, наверное, поняли, есть режим синтаксиса MODX Revolution. В этом режиме теги начинаются с двух открывающих квадратных скобок, далее следует некий символ, который и определяет тип тега. А закрывается тег всегда двумя закрывающими квадратными скобками.

Пример тега некоего чанка:

{{news-item? &title=`Мухи съели мера города` &date=`15-09-17` &url=`/news/150917.html`}}

Если в чанке будет:
<article>
  <h3>[+title+]</h3>
  <span class="date">[+date+]</span>
  <a href="[+url+]">Читать далее</a>
</article>

Тогда в итоге на месте чанка будет выведено:
<article>
  <h3>Мухи съели мера города</h3>
  <span class="date">15-09-17</span>
  <a href="/news/150917.html">Читать далее</a>
</article>

Кстати, о мухах. Не они ли скушали пункт HTML из меню «исходный код» редактора на нашем Уютненьком?

Из коробки QuadBraces поддерживает следующие виды тегов (указаны начальная и конечная последовательности для двух режимов):

  • [+ +] или [[+ ]]Локальные переменные. В большинстве случаев используются для переменных сниппетов, в циклах. При установке аргументов любого элемента, кроме сниппетов, локальные плейсхолдеры элемента заменяются значениями из аргументов.
  • {{ }} или [[$ ]]Чанки. Куски HTML-кода. В одном файле содержится один чанк. Один из основных элементов шаблонизации.
  • {[ ]} или [[- ]]Однострочные библиотечные чанки. Куски HTML-кода. В одном файле содержится несколько чанков. На каждой строке располагается один чанк.
  • {( )} или [[= ]]Многострочные библиотечные чанки. Куски HTML-кода. В одном файле содержится несколько чанков. Чанки разделяются конструкцией: "<!-- tags:splitter -->"
  • {* *} или [[/ ]]Константы PHP. Выводят определённые в системе (в т.ч. пользователем) константы PHP.
  • [( )] или [[++ ]]Настройки. Переменные из массива SETTINGS. Обычно служат для вывода переменных CMS, использующей парсер.
  • [* *] или [[* ]]Переменные. Переменные парсера. Один из основных элементов шаблонизации.
  • [^ ^] или [[^ ]]Отладочные данные. Отладочные данные парсера.
  • [% %] или [[% ]]Языковые переменные. Словарно-языковые переменные. Заменяются в зависимости от текущего языка при включённой языковой системе.
  • [! !] или [[! ]]Сниппеты. По сути куски PHP-кода.
  • [[ ]] или [[ ]]Сниппеты с флагом кеширования. То же, что и выше, только с установленным флагом кеширования. Грубо говоря в функцию execute парсера передаётся аргумент $cached со значением true.
  • [~ ~] или [[~ ]]Ссылки из идентификаторов ресурсов. Превращают помещённое внутрь число в ссылку на ресурс. О ресурсах ниже.
  • [: :] или [[: ]]Кастомные обработчики переменных. О них отдельно.

В нынешней версии парсера есть возможность впихнуть в объект парсера набор т.н. ресурсов. По сути это примерно то же самое, что и ресурсы в MODX. Грубо говоря в базе данных конечного проекта хранится таблица, в которой хранятся записи с постами для блога. Главное — помнить, что каждая запись должна содержать численный ID, ID родителя или NULL и псевдоним для создания URL'а. При установке свойства resources объекта парсера устанавливается свойство idx объекта парсера. Оно представляет собой индекс ветвей дерева ресурсов. Это позволяет работать со структурой конечного набора ресурсов. Собственно, со всей этой фигнёй функциональностью и работает тег ссылок.

Кастомные обработчики переменных — это эдакие микропарсеры, произвольно обрабатывающие переменные парсера. Например, если в некоей переменной парсера (назовём её, скажем, top-menu) содержится массив с URL'ами вида:

array(
  array('url' => '/','title' => 'Глагне'),
  array('url' => '/about.html','title' => 'О сайте'),
  array(
    'url' => '/news/','title' => 'Новости','children' => array(
      array('url' => '/news/10-09-17.html','title' => 'Мухи прилетели в город'),
      array('url' => '/news/15-09-17.html','title' => 'Мухи съели мера города')
    )
  ),
  array('url' => '/contacts.html','title' => 'Контакты'),
);

Из него можно сделать меню. Для этого достаточно поместить в нужном месте конструкцию:

[:menu@top-menu:]

На её месте будет выведено меню при помощи маркированных списков. В лучших традициях Wayfinder'а, так сказать.

Вообще по кастомным обработчикам, думаю, нужно будет написать отдельную статью.

Отдельно про одну важную фишку шаблонов, а именно про метаполя. Едва ли не с первой версии существует возможность в коде шаблонов устанавливать метаполя. Оные по сути представляют собой переменные, определяемые в самом шаблоне вот такой конструкцией:

<!-- FIELD:название_поля? [аргументы] -->

Аргументы — примерно то же самое, что и в тегах парсера. Вне зависимости от того, какие аргументы будут указаны в поле, конечное поле будет содержать три атрибута:

  • default (текст) — значение по умолчанию
  • type (целое число) — тип
  • caption (строка) — заголовок

Все метаполя будут доступны через свойство fields парсера.

Не менее важной фишкой являются метазначения. Представим себе, что у нас в парсере определена переменная somevar. Метазначение в установленном шаблоне может переопределить текущее значение переменной. Метазначения определяются конструкцией:

<!-- DATA:название_переменной `значение переменной` -->

При установке шаблона и то, и другое удаляется из конечного кода шаблона.

API класса


Свойства класса (наиболее важные)

:
  • owner (объект) — объект-владелец (только чтение; установка при инициализации);
  • paths (массив) — пути шаблонов; можно устанавливать массивом, можно строкой, где через запятую перечислены пути;
  • fields (массив) — поля шаблона (только чтение);
  • data (массив) — переменные парсера;
  • settings (массив) — «настройки»;
  • debug (массив) — отладочные данные;
  • language (строка) — текущий язык; сигнатура согласно общепринятым стандартам;
  • loadLanguage (флаг) — автозагрузка языка (по умолчанию false);
  • dictionary (массив) — текущий словарь; можно устанавливать raw-текстом, разделённым на строки;
  • maxLevel (целое число) — максимальный уровень вложенности (по умолчанию 32);
  • level (целое число) — текущий уровень;
  • notice (массив) — уровень уведомлений; понимает значения «strict» — все элементы, «common» (по умолчанию) — чанки и сниппеты.
  • template (текст) — шаблон; при установке указывать название, возвращает содержимое;
  • templateName (строка) — название текущего шаблона (только чтение);
  • autoTemplate (флаг) — флаг автоматического извлечения и установки шаблона из устанавливаемого содержимого; грубо говоря, если со включённым флагом скормить парсеру контент с метаполем template, парсер автоматически установит шаблон (по умолчанию false);
  • content (текст) — содержимое; вообще по сути переменная под названием «content»;
  • resources (массив) — массив ресурсов;
  • idx (массив) — индекс ветвей дерева ресурса (только чтение);
  • SEOStrict (флаг) — соответствие стандартам SEOStrict (по умолчанию false);
  • MODXRevoMode (флаг) — переключение в режим синтаксиса MODX Revolution (по умолчанию false);

Методы

:
  • registerTag(QuadBracesTagPrototype $o) — регистрация тега; на вход принимает инициализированный тег; возвращает зарегистрированный тег;
  • parseStart($m) — начало обработки; на вход принимает данные от функции preg_match_callback; возвращает ключ элемента;
  • parseFinish($m,$t,$k,$v) — конец обработки; на вход принимает данные от регулярки, тип тега, ключ тега, текущее значение обработки; возвращает обработанный результат;
  • setting($key,$value=null) — чтение/запись настройки; при значении null производится чтение;
  • variable($key,$value=null) — чтение/запись переменной; при значении null производится чтение;
  • search($type,$name) — поиск файла элемента; учитывает локализацию; возвращает имя найденного файла;
  • setTemplate($v) — установка шаблона по имени; очень нужный метод при работе с callback'ами установки шаблона;
  • getChunk($key,$type='chunk') — получение содержимого чанка; возвращает содержимое найденного чанка или false;
  • execute($name,$args=array(),$input='',$cached=false) — выполнение сниппета или расширения; на вход принимает имя сниппета, аргументы, входной текст и флаг кеширования; возвращает результат обработки;
  • parse($t='',$d=null,$e='',$k='') — основной метод парсера — обработка; для обработки всего шаблона аргументы устанавливать не нужно; аргументы нужны для внутренних нужд или дискретной обработки (кусочек кода QuadBraces); входные аргументы — входной код, данные переменных, элемент, ключ элемента; на выходе — обработанные данные;
  • sanitize($t='') — санитизация входного кода от QuadBraces;
  • extensions($v,$e) — выполнение расширений; на входе — текущий код и строка с расширениями;
  • registerEvent($n,$f) — регистрация события; на входе — имя события и функция;
  • registerMethod($n,$f) — регистрация метода API; на входе — имя метода и функция;
  • invoke() — вызов события; первый аргумент — имя события, остальное — аргументы вызова;

Где что искать?


Одно из важнейших свойств парсера QuadBraces является свойство paths. Оно определяет, где парсер будет искать чанки, сниппеты, расширения, шаблоны. Языковая система работает немного обособлено. В процессе поиска элемента парсер по очереди перебирает все зарегистрированные пути. Каждый раз поисковая функция добавляет к выбранному пути папку в зависимости от вида элемента. Чанки — chunks, шаблоны — templates, сниппеты — snippets. И уже там ищется искомый элемент. Напомню, что точка в ключах чанков, сниппетов и шаблонов по факту заменяет разделитель директорий.

Пример:

$parser->paths = 'D:/projects/mysite/content,D:/repo/templates/default';
$fn = $parser->search('snippet','basis.snipcon');

По факту парсер в данном случае будет проверять существование следующих файлов:

D:/projects/mysite/content/snippets/basis/snipcon.php
D:/repo/templates/default/snippets/basis/snipcon.php


Будет возвращён последний найденный файл. При включённой языковой системе фактически в поиск добавляются пути с языковыми сигнатурами. Допустим, установленный язык — Русский, то есть сигнатура — «ru». Таким образом список файлов следующий:

D:/projects/mysite/content/snippets/basis/snipcon.php
D:/projects/mysite/content/snippets/basis/ru/snipcon.php
D:/repo/templates/default/snippets/basis/snipcon.php
D:/repo/templates/default/snippets/basis/ru/snipcon.php


Всё прочее получается через свойства и методы.

Языковая система


Во-первых, сразу оговорюсь, что языковую систему нужно включать явно. Это делается подключением класса QuadBracesLang. Делается это простейшим кодом до инклуда основного класса:

define("QUADBRACES_LOCALIZED",true);

Лучше всего включить флаг loadLanguage. Тогда словари будут подгружаться автоматически. В принципе по умолчанию при установке путей парсера устанавливаются пути языковой системы. По сути языковая система ищет по тому же принципу свои файлы, что и поисковая система элементов. Только папка языковой системы — lang.

Пример:

$parser->paths = 'D:/projects/mysite/content,D:/repo/templates/default';
$parser->language = 'ru';

Будут сканироваться папки:

D:/projects/mysite/content/lang/ru/
D:/repo/templates/default/lang/ru/


В оных будут искаться все файлы с расширением «lng». Каждый файл считывается построчно. На каждой строке располагаются четыре элемента — языковой ключ, заголовок, описание, плейсхолдер. Языковой ключ — то, что потом используется в языковом теге. Заголовок (caption) — замена языкового тега по умолчанию. Описание (description) — обычно используется для подсказок или описаний полей. Плейсхолдер (placeholder) — чаще всего используется для соответствующего атрибута элементов ввода.

События


Объект парсера поддерживает события. Обработчики, как я писал выше, устанавливаются методом registerEvent.

Поддерживаемые события (везде $p — объект парсера):


  • init($p) — Завершение конструктора;
  • beforeChangeData($v,$p) — До изменения данных переменных; $v — устанавливаемые переменные;
  • changeData($v,$p) — Изменение данных переменных; $v — устанавливаемые переменные;
  • beforeChangeSettings($v,$p) — До изменения данных настроек; $v — устанавливаемые настройки;
  • changeSettings($v,$p) — Изменение данных настроек; $v — устанавливаемые настройки;
  • beforeLoadDictionary($v,$p) — До загрузки словаря; $v — словарь;
  • loadDictionary($v,$p) — Загрузка словаря; $v — словарь;
  • methodNotFound($n,$p) — Метод не найден; $n — Название метода;
  • beforeSetLanguage($v,$p) — До установки языка; $v — язык;
  • setLanguage($v,$p) — При установке языка; $v — язык;
  • setContent($v,$p) — При установке контента; $v — контент;
  • setResources($v,$p) — При установке ресурсов; $v — данные ресурсов;
  • invalidHandler($n,$a,$p) — При отсутствии обработчика событий; $n — название обработчика, $a — переданные аргументы;
  • setResources($v,$p) — При установке ресурсов; $v — данные ресурсов;
  • defaultTemplate($p) — Дефолтный шаблон (попытка установить пустой шаблон);
  • loadTemplate($v,$p) — При установке шаблона; $v — название шаблона;
  • templateMotFound($v,$p) — При отсутствии шаблона; $v — название шаблона;
  • templateFields($v,$p) — Получение полей шаблонов; $v — поля шаблона;
  • templateData($v,$p) — Получение данных шаблона; $v — данные шаблона;
  • beforeLocalParse($v,$d,$p) — До локальной обработки; $v — обрабатываемый код, $d — данные;
  • beforeParse($v,$p) — До обработки; $v — обрабатываемый код;
  • localParse($v,$p) — После обработки локального шаблона (до санитизации); $v — обработанный код;
  • parse($v,$p) — После обработки шаблона (до санитизации); $v — обработанный код;


Каюсь, по незнанию напоролся на одну особенность PHP. Если, допустим, назначить функцию на обработчик события templateNotFound, а в ней попытаться установить свойство template, ничего не произойдёт. Это обусловлено ограничениями рекурсии при вызове аксессоров для свойств объекта PHP. Если кратко, то нельзя устанавливать свойство через аксессор и аксессора этого же свойства. Никак. От слова «совсем». Так что если захотите сделать 404-ю через обработчик события templateNotFound, вызывайте внутре него неонку метод setTemplate.

Расширения-обработчики


Все теги поддерживают обработчики конечного значения. Например, получили мы переменную, а она пустая. Обработчик notempty позволяет задать выводимый текст на этот случай. У некоторых расширений есть дополнительные «расширения». Например, у базовой логики есть расширения «then» и «else».

Поддерживаемые обработчики


  • is, eq — равенство (сравниваемое значение, then, else)
  • isnot, neq — неравенство (сравниваемое значение, then, else)
  • lt — меньше, чем (сравниваемое значение, then, else)
  • lte — меньше, чем, или равно (сравниваемое значение, then, else)
  • gt — больше, чем (сравниваемое значение, then, else)
  • gte — больше, чем или равно (сравниваемое значение, then, else)
  • even — признак чётности (сравниваемое значение, then, else)
  • odd — признак нечётности (сравниваемое значение, then, else)
  • empty — признак пустого значения (значение «тогда», else)
  • notempty — признак непустого значения (значение «тогда», else)
  • null, isnull — является ли значение NULL (значение «тогда», else)
  • notnull — является ли значение не NULL (значение «тогда», else)
  • isarray — является ли значение массивом (значение «тогда», else)
  • for — целочисленный итератор (количество итераций, start, splitter)
  • foreach — индексный итератор (список индексов через запятую, splitter)
  • js-link — превращает значение в ссылку на скрипт
  • css-link — превращает значение в ссылку на таблицу стилей
  • import — превращает значение в ссылку на таблицу стилей (@import для CSS)
  • link — превращает значение в ссылку (заголовок)
  • link-external — превращает значение во внешнюю ссылку (заголовок)
  • links — преобразует все URL-ы в значении в ссылки (атрибуты ссылок)
  • ul, ol — превращает многострочное значение в список (шаблон элемента списка)

Расширение ul/ol имеет два внутренних плейсхолдера: [+classes+] — классы элемента (first, last), [+item+] — собственно строка. Для расширения for доступен внутренний плейсхолдер [+iterator+], содержащий номер текущей итерации. Для расширения foreach доступны внутренние плейсхолдеры: [+iterator.index+] — номер позиции текущей итерации, [+iterator+] — текущий индекс.

Практикум


Собственно, работать с классом парсера очень легко. Базовый пример:

<?php
  require 'quadbraces/parser.php';
  $parser = new QuadBracesParser('некий/путь/к_шаблонным_данным');
  $parser->template = 'my-template';
  echo $parser->parse();
?>

В нём мы подключаем класс парсера, инициализируем объект с передачей пути к шаблонным данным, устанавливаем шаблон и парсим. Само собой до обработки можно передать в парсер «настройки», переменные. А до включения класса парсера включить языковую систему, как было написано выше. Более практический пример:

<?php
  define("QUADBRACES_LOCALIZED",true);
  require 'quadbraces/parser.php';
  $parser = new QuadBracesParser('D:/projects/foo/content/template');
  $parser->language = 'ru';
  $parser->data = array(
    'pagetitle' => 'Мухи съели всё',
    'date' => '20-09-17',
    'image' => 'content/images/flies.jpg'
  );
  $parser->template = 'news.single';
  echo $parser->parse();
?>

Шаблон будет искаться в следующих файлах:

D:/projects/foo/content/template/templates/news/single.html
D:/projects/foo/content/template/templates/news/ru/single.html


Итог-дисклеймер


Сразу прошу прощения за своё неумение писать туториалы и документацию! Если сообществу будет по-настоящему интересно, наиболее интересные моменты будут раскрываться в последующих публикациях. Задавайте вопросы — с радостью отвечу. Если заметите ошибку — пишите issues на гитхабе сюда.




К сожалению, не доступен сервер mySQL