На Хабре, было несколько статей о Yaml, но мне кажется все они однобоки и не раскрывают его истинную природу. Я попробую это исправить и рассказать о Yaml в положительном контексте. Не буду вновь описывать детали синтаксиса стандартного Yaml, в Интернете есть много материалов на эту тему. Их можно найти и на Хабре, в том числе, по ссылкам из этой статьи. Материал ориентирован на тех, кто знаком с Yaml, но возможно чувствует неприязнь к формату.
За последние 30 лет активно развивающего IT, устоялись три основных формата для представления иерархических данных, т.е. деревьев:
XML/HTML - особенность формата в том, что узлами являются объекты, а это архи необходимо, в первую очередь, для кодирования богатых "человеческих визуализаций". Отсюда и постфикс "ML" - язык разметки. Этот формат используется для веб-сайтов или, например, для хранения данных MS Excel. Узлы представляются объектами из-за наличия атрибутов тегов, а также необходимостью прохода по дереву во всех направлениях. В начале 2000-х годов, XML более широко использовался для передачи данных между удаленными хостами, но часто был вытеснен JSON, так как последний намного более прост, а мощь XML (как и сложность) оказались не востребованы.
JSON - также представляет иерархические данные, но узловыми ключами не могут быть объекты, а только скаляры: строки или целые числа. Особенность формата в том, в том, что он имеет минимальное количество правил, необходимых для практической реализации парсеров. Отсюда его популярность, в том числе, для обмена сообщениями между удаленными хостами. JSON часто не читают люди, а кодирование и декодирование происходит автоматически.
YAML - также не может содержать в узловых ключах объекты, а только скаляры. Этот формат включает, как подмножество, JSON, часто используется для конфигураций, и ориентирован в большей степени для чтения-записи человеком, чем JSON. В этой статье, сравниваются два последних формата, но имхо, нужно акцентировать, что Yaml предназначен для человека, а JSON не только. И ставить вопрос "кто круче" не очень корректно.
Ситуация с Yaml, наверно самая сложная и запутанная, например в этом посте-нытье рассказывается как всё плохо. Но негативные обстоятельства можно и нужно обратить вспять. Для этого следует всё расставить по своим местам. С момента появления Yaml прошло 23 года, но он активно используется в различных языках программирования для конфигураций, в том числе, флагманском PHP-framework Symfony. По-моему этого факта достаточно, чтобы утверждать, что Yaml имеет свой "рай", просто его нужно понять. Наверно до Yaml нужно дорасти, я например, написал свой парсер Yaml и многое понял из того о чём пишу, только в прошлом году, хотя занимаюсь программированием уже давно.
Я считаю, в стандартной архитектуре Yaml, действительно присутствует удивительный факап. Первичная идея - гениальная, но конечные детали - неудовлетворительны. Как говорится "начали за здравие, закончили за упокой"... Тем не менее, за 23 года жизни Yaml устоялся факт его локального применения. Жизнестойкость и локальность объясняется так: он действительно красив, минималистичен и выразителен, но его парсер всегда будет более сложен, объёмен и менее эффективен чем у JSON. Производительность - не проблема, так как компилированные данные всегда можно поместить в кэш. Я назвал Yaml королем мета-описаний, потому что, считаю синтаксис Yaml таким, который вобрал в себя все самые мощные способы представления иерархических структур, которые будут выглядеть красиво и выразительно для человека. По сути, Yaml удобно использовать не только для конфигураций, но и для программирования. Но об этом позже.
Я не нашел в Wikipedia четкого термина для "мета-описаний", поэтому опишу, что я под этим подразумеваю. Иногда, в программировании, бывает эффективно придумать какой-то собственный формат представления данных, который будет отражать алгоритмические особенности парсера, работающего с таким форматом. Данные в таком произвольном формате, я и называю мета-описаниями. Например, среди моих прошлых работ, имеется PHP имплементация CSS framework Tailwind. Для генерации всех(!) утилит Tailwind используется до 200 мета-описаний. Это пример одного мета-описания для генерации 42-х утилит "Grid Column Start / End":
- MetaName: col #grid
ForMenu: 2col span-start-end
Body: |
auto|span-full|start-auto|end-auto|@num
grid-column: auto
.auto
grid-column: 1 / -1
.span-full
grid-column-start: auto
.start-auto
grid-column-end: auto
.end-auto
span-~num!=: span {0} / span {0}|start-~num!=-start: {0}|end-~num!=-end: {0}
grid-column{@}
.@num
В консоли можно протестировать как Vesper (это моя имплементация Tailwind) генерирует утилиты, вот три примера:
>php vendor/bin/sky venus tw col-auto
.col-auto {
grid-column: auto;
}
>php vendor/bin/sky venus tw col-span-2
.col-span-2 {
grid-column: span 2 / span 2;
}
>php vendor/bin/sky venus tw col-start-2
.col-start-2 {
grid-column-start: 2;
}
Кстати, эта статья включает работу программиста на PHP. Для тех, кто хочет подробнее ознакомиться с моей работой, доступно к установке пустое демо-приложение "Hole". Предостережение: Coresky - это экспериментальный фреймворк, подробнее в спойлере:
Hidden text
Это значит, что я использую фреймворк только для разработки новых архитектурных решений и не рекомендую его использовать для публичной части Интернета. Проделана огромная работа благодаря только лишь эндорфинам и удовольствию творчества. Много кропотливой, не творческой работы не сделано и вы легко найдете ошибки. Отчасти они присутствуют из-за того, что я всё еще продолжаю делать обратно несовместимые изменения новых версий к старым. Тем не менее то, о чём я пишу в этой статье - отлично работает. Большая часть кода имеет статус release-candidate.
>composer create-project coresky/hole
# и можно сразу запустить PHP-DEV-WEB сервер:
>cd hole
>php vendor/bin/sky s
Исправим стандартный Yaml
Смотрите, для передачи данных между хостами существуют JSON и XML, а Yaml мы будем использовать только локально. Поэтому нет проблем, чтобы написать свой собственный парсер. Давайте исправим архитектурный факап, чтобы минимизировать досадные "подводные камни" и с другой стороны, раскроем всю потенциальную мощь Yaml. Конечно, необходимо стараться максимально сохранить стандартный Yaml, это упростит изучение Coresky модификации. Пакет Composer symfony/yaml как и многие другие популярные имплементации также не поддерживают стандарт полностью. Например, обычно, не поддерживается множественный Yaml документ. В Coresky, парсер Yaml это один файл, содержащий 577 строк. Я использую технику разбивки на токены, тогда как в Symfony используются регулярные выражения.
Неявная типизация. С проблемой Норвегии - это ерунда какая-то.. Давайте оставим рабочими только три литерала, которые являются стандартными в JSON и соответствуют по типу в PHP: true
, false
, null
. Целые числа оставим только десятичные, а также с плавающей точкой float, в полном соответствии с синтаксисом PHP. Двоичные, шестнадцатеричные INT и всю остальную неявную типизацию убираем. Для этого дела лучше применить функции трансформации или операторы. Об этом ниже.
Явная типизация. Здесь, нужно расширить стратегию и использовать соответствующие термины "оператор" или "функция преобразования". В Coresky имеется собственный компилятор представлений - Jet, который является потомком Laravel/Blade, в нём используются операторы, начинающиеся с символа амперсанд. Используем такой же подход и в Yaml. Для имплементации большинства функций трансформации требуется буквально пару строк кода на PHP. То как трансформируются значения записано в нотации стандартного JSON:
# @base64 тоже что и !!binary в стандартном Yaml
img: @base64 |
R0lGODdhDQAIAIAAAAAAANnZ2SwAAAAADQAIAAACF4SDGQ
ar3xxbJ9p0qa7R0YxwzaFME1IAADs=
array: @csv(:) a:b:c # трансформируется в массив: ["a", "b", "c"]
hi: @hex2bin > # трансформируется в строку: "Hello word!"
48 65 6C 6C 6F 20 77 6F 72 64
21
# десятичные числа в спец-нотации:
card: @dec 1234_5678_9012_3456 # >>> 1234567890123456
big-number: @dec 1 000 000 000 # >>> 1000000000
phone: @dec 2-777-222-22-22 # >>> 27772222222
# двоичное число:
five: @bin 101 # >>> 5
# Search-And-Replace, используется функция PHP preg_replace(..)
sar: @sar(|=+| is ) a========1, b==2 # >>> "a is 1, b is 2"
# @left(string), @right(string) - дописать "string" слева или справа
key: @left(left-) value # >>> "left-value"
# и так далее ..
Операторы можно указывать только вначале значений Yaml или JSON нотаций. Для одного значения, можно указать несколько операторов и они могут применяться каскадно для всех значений под-массива. Порядок выполнения - справа налево и снизу вверх. Для частной отмены каскадного выполнения, можно использовать @deny:
one: @right( RR)
two: @bin
- 0b11
- 101 # можно и без префикса 0b
- @deny @left(LL ) 1010
three: four
color: @left(#) @each @bang(. ) @sar(| +| ) > # not in cascade
aliceblue.f0f8ff antiquewhite.faebd7
beige.f5f5dc bisque.ffe4c4
# для кодирования цветов мы указали только информационную "соль"
# вот почему Yaml - король метаопределений
{
"one": {
"two": ["3 RR", "5 RR", "LL 1010"],
"three": "four RR"
},
"color": {
"aliceblue": "#f0f8ff",
"antiquewhite": "#faebd7",
"beige": "#f5f5dc",
"bisque": "#ffe4c4"
}
}
Якоря и ссылки. Не будем расширять синтаксис, можно использовать функцию трансформации @path
:
one:
two: [1, 3]
three: @path(one.two.1) # this value will eq. to 3
Множественный Yaml. Должен иметь именованные части! Тогда можно будет свободно компоновать Yaml-данные и применять принцип DRY. Компилятор Jet поддерживает маркеры частей файлов. Используем эту же стратегию и в Coresky Yaml:
#.run =========================
- @inc(.test) # @inc это include
- 2
#.run
#.test =========================
+ 123
#.test
<?php
# функция yml(..) выполняет inline-yaml, может кешировать компилированный PHP
# и передавать переменные, подобно как это делается в Jet (Blade)
print_r(yml('+ @inc(run) filename.yml'));
# stdout: array(0 => 123, 1 => 2)
Гибридные массивы PHP. В стандартной Yaml нотации есть одна неразбериха: для того чтобы указать значение со строковым ключом на нижнем уровне иерархии - обязательно нужен отступ, а для перечислений отступ необязателен:
aaa:
bbb: # { "aaa": { "bbb": null } } отступ ==> глубина
aaa:
bbb: # { "aaa": null, "bbb": null } <--- здесь глубина не изменилась
aaa:
- bbb # { "aaa": [ "bbb" ] } отступ ==> глубина
aaa:
- bbb # { "aaa": [ "bbb" ] } <--- здесь глубина изменилась хотя нет отступа!!!
# Исправим синтаксис для предыдущего примера в Coresky вот так:
aaa:
- bbb # { "aaa": null, "0": "bbb" }
Yaml компилируется в PHP массив, а массивы в PHP могут быть гибридными. Разрешим совмещать в Yaml нотации строковые ключи и ключи перечислений (дефис). Это расширит потенциал использования Yaml. Ниже представлен действующий код для генерации HTML формы системной конфигурации Coresky для Root-Admin-Section:
#.system
- <fieldset><legend>Primary settings</legend>
- ['', [[<b><u>Production</u></b>, li]]]
trace_root: [Debug mode on production for `root` profile, chk]
trace_cli: [Use X-tracing for CLI, chk]
error_403: [Use 403 code for `die`, chk]
empty_die: [Empty response for `die`, chk]
gate_404: [Gate errors as 0.404 (soft), chk]
log_error: [Log ERROR, radio, [Off, On]]
log_crash: [Log CRASH, radio, [Off, On]]
- [Hard cache, {
cache_act: ['', radio, [Off, On]],
cache_sec: ['Default TTL, seconds', number, style="width:100px", 300]
}]
- </fieldset>
- <fieldset><legend>"Visitor's & users settings"</legend>
- [Cookie name, {
c_name: ['', '', '', sky],
c_upd: ['Cookie updates, minutes', number, style="width:100px", 60]
}]
visit: ['One visit break after, off minutes', number, '', 5]
reg_req: [Users required for registrations, radio, [Both, Login, E-mail]]
- </fieldset>
#.system
Coresky Yaml - младший брат Jet
В Coresky имеется функционал для генерации HTML форм из массивов PHP. Теперь формы можно определять с помощью Yaml. Добавим операторы @php
и @preflight
, ниже пример кода Yaml и соответствующий компилированный PHP кэш-файл:
#.test =======================================
+ @preflight($v_1, &$v_2) |
return SomeClass::method_1($v_1, $v_2);
- string
- @php OtherClass::method_2($__return)
- {a: b, c: @php(OtherClass::method_3([1, $v_3])), x: y} # json notation
#.test
<?php
# preflight code
$__return = call_user_func(function() use ($v_1, &$v_2) {
return SomeClass::method_1($v_1, $v_2);
});
# other yaml after compile
return array(
0 => 'string',
1 => OtherClass::method_2($__return),
2 => array(
'a' => 'b',
'c' => OtherClass::method_3([1, $v_3]),
'x' => 'y'
),
);
В операторе @php
, код PHP можно указать или в параметре (в скобках) или в значении (после оператора). В первом примере выше, в последней строке, подсветка синтаксиса Yaml неверно сработала: всё что указано в скобках оператора @php
, не является данными JSON нотации, а является кодом PHP. Если @preflight
имеет скобки, то код будет выполняться в изолированной области видимости, которая организуется с помощью Closure и call_user_func
, а если скобок нет - то без изоляции.
Вызов такого кода, и передачу переменных, можно выполнить с помощью функции Coresky yml(..)
:
<?php
$array = yml('cache_filename', '+ @inc(test) filename.yaml', [
'v_1' => $var_1,
'v_2' => $var_2,
'v_3' => $var_3,
]);
В примерах выше, область применения Coresky Yaml, определенно коррелирует с компилятором представлений Jet. Представьте бухгалтерское приложение с огромным количеством форм. Теперь, рядом с папкой mvc
, можно сделать папку forms
и разместить в ней все формы приложения. Имена файлов содержащих yaml-формы, можно назвать в соответствии с именами контроллеров и разместить по несколько штук в одном файле разделив маркерами, в соответствии с именами действий, где используются эти формы. Представляете как это разгрузит код моделей, контроллеров и представлений? Как сказал автор статьи "Некоторые приемы YAML", да пребудет с нами KISS и DRY, а еще актуально: "разделяй и властвуй".
Формы на Yaml это не панацея, посмотрите, например этот файл. Довольно часто бывает нужно "прожонглировать" кусками кода и данных, чтобы сгенерировать финальный код. Раньше в подобных случаях, я использовал возможности Jet. Теперь это удобнее делать в Yaml. В приложении "Hole", которое вы можете установить с помощью Composer, есть несколько примеров форм на Yaml, в том числе, с передачей переменных в компилированный кэш.
Вы вероятно заметили "плюс" в Yaml нотации, в примерах выше, в местах где обычно бывает дефис. Это еще одно новшество в синтаксисе Coresky Yaml. Есть и другие нововведения, читайте подробнее в документации. Я старался показать хорошую сторону и скрытый потенциал Yaml, хотя, действительно в стандарте есть недостатки. Часто написать веб-приложение можно и без Yaml, но, например, в этом проекте без него было бы туго. Yaml в Coresky - это еще один мощный механизм для программирования и упрощения кода. Если у вас в проекте имеется более-менее значительный массив данных, который поместить в БД не рационально, у вас есть возможность использовать Yaml.
Другие интересные идеи в Coresky
Я напишу пару слов о каждой и помещу текст в спойлеры, так как это немного не по теме статьи. Если не интересно - вы можете пропустить эту часть.
Планы и продукты:
Hidden text
Нет велосипедам! Система продуктов в Coresky - это попытка сделать так, чтобы повторное использование функционального кода было максимально простым. Продукты бывают трёх типов: prod
(функциональный код), dev
(это по сути просто плагины для инструментов разработчика), view
(дополнительные наборы шаблонов Jet и стилей CSS). Имя продукта основного приложения всегда main. Продукты похожи на модули Zend/Laminas, но в последнем нет никаких специальных средств для их интеграции в приложение, а также специальных системных средств для их поддержки. Продукты устанавливаются с помощью веб-интерфейса инструментов разработчика, в это время может произойти специальная инициализация продукта: создание таблиц в БД и т.д.
Планы - это надстройка над сущностями приложений. Стандартные планы, которые имеются в любом Sky-приложении: app, cache, mem, gate, jet. Управляет планами, по сути, движок абстрактного кэша.
Sky Gate - небесные врата
Hidden text
В Coresky нет привычной системы роутинга. SkyGate - это утилита с веб-интерфейсом, в которой настраиваются входные внешние данные для контроллеров. Первая часть адреса запроса определяет контроллер, а вторая действие в нем. Если нужна нестандартная адресация, необходимо использовать Coresky Rewrites.
Компилятор преставлений Jet
Hidden text
За основу была взята идея Laravel/Blade. В Jet много новшеств: препроцессор, бинарная идея блоков, включающая операторы @block
, @use
, #use
, маркеры частей файла, три типа генерации визуализаций: top
, sub
, block
. Имеется ввиду, что каждая визуализация запускает действие в контроллере, подготавливает переменные и генерирует визуализацию с помощью компилированного шаблона Jet.
Заключение
Если вы работаете со значительным объемом, особенно иерархических данных и не сильно воспринимаете Yaml: потрудитесь его изучить получше. Ему уже 23 года и он прекрасно себя чувствует, несмотря на плохие слухи. Если это так, значит он кому-то нужен, может нужен и вам? Когда вы, как Джек Салли скажете "Yaml, я тебя вижу..", вас от него уже будет не оторвать.
У стандартного Yaml есть скрытый потенциал, и я в этой статье старался показать как его раскрыть.
Не забывайте про подводные камни, если вы "не видите Yaml", будьте осторожны.