Pull to refresh

Mooha — нодовый интерфейс для PHP

Reading time 11 min
Views 26K


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

Идея графического управления логикой программы мне всегда казалась очень элегантным, а в некоторых случаях, единственным удачным решением. Позже, когда помимо музыки и видео я увлекся программированием (в основном PHP, так уж сложилось), мне захотелось попробовать, пусть даже в качестве эксперимента, создать графическую оболочку для выполнения тех нехитрых задач, с которыми я сталкивался в своей работе.

Мое знакомство с нодовым интерфейсом (или графическим программированием) началось с программы Plogue Bidule. Это приложение относится к семейству модульных программ для управления звуком в реальном времени. В свое время эта программа помогала мне решать достаточно нетривиальные задачи, касающиеся обработки звука и синхронизации сценического оборудования на живых выступлениях нескольких моих музыкальных проектов.

Интерфейс программы Plogue Bidule выглядит следующим образом:



В основе работы — создание логических цепочек. Как мне кажется, такой подход вполне естественно вписывается в работу со звуком, хотя кому-то может показаться сложным с первого взгляда. Звук в программе — это непрерывный поток, который появляется из источника (входящий канал звуковой карты, аудио файл или генератор сигнала), и после определенной обработки чаще всего отправляется на выход звуковой карты или на запись в файл.

Главные управляющие конструкции интерфейса — ноды (или модули, блоки, узлы и т.д.), которые по сути являются функциями. По аналогии с музыкальным оборудованием — это приборы, соединенные между собой проводами.



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

Мне повезло впервые познакомиться с нодовым интерфейсом именно в музыкальной программе, поскольку поток звука, на мой взгляд, отличная аналогия для потока выполнения процедурной программы.

Нодовые редакторы широко используются в графических пакетах для создания 3D графики. В таких программах «черные ящики» описывают сложнейшие структуры и функции, а интерфейс позволяет сохранить свободу и гибкость в построении логики выполнения. Самая интересная, на мой взгляд, реализация нодовой системы в программе Houdini.



Так а что же с веб-программированием? Поиск похожих систем не дал результатов. Я подумал, что, возможно, в такой технологии просто отсутствует необходимость. И что идея моя родилась скорее от некомпетентности и наивности. Но желание попробовать создать такой инструмент оказалось сильнее моих сомнений.

Так родился проект Mooha.

PHP, MySQL, HTML


Каким же образом можно соединить эти три технологии в одной нодовой системе, чтобы получить удобный графический инструмент? В программах для создания 3D графики в нодовых редакторах часто используется разделение на контексты, которые обычно плохо совместимы друг с другом. Но в нашем случае структуры данных не настолько сложны и разнородны, чтобы плодить отдельные редакторы. Поэтому я решил создать единое пространство, разделив ноды на категории по технологиям:

  • HTML/XML ноды (для вывода HTML/XML тэгов)
  • PHP ноды (управление логикой)
  • Ноды запросов к базе данных MySQL


Идея состояла в том, чтобы переход между технологиями был незаметен для пользователя. Чтобы соединение нод разных категорий между собой создавало единый поток выполнения программы.

Рассмотрим по порядку все три категории нод.

HTML/XML ноды




Соединяя такие ноды между собой мы получаем простейшую структуру HTML документа. По-умолчанию HTML/XML нода имеет по одному входу и одному выходу. Вход (IN) — то, что помещается внутрь тэга, выход (OUT) — результирующая строка. Таким образом из последнего выхода ноды «HTML» мы получим строку:

<html>
  <head>
    <meta />
    <script />
  </head>
  <body>
    <div>
      <div />
      <div />
    </div>
  </body>
</html>


Служебная нода «Merge» используется для возможности повлиять на сортировку входящих соединений. Сортировка доступна в свойствах самой ноды. В последней ноде «HTML» два входящих соединения конкатенируются в том порядке, в котором произошло соединение. Т.е. если сначала подключили «HEAD», а потом «BODY», то в такой последовательности они и появятся в конечной строке.



Атрибуты тэга мы можем прописать в свойствах ноды, но при желании можем выводить их в виде коннекторов для входящих соединений. Например, добавив ноде «my div» коннекторы для атрибутов id и class мы получим такой вид:



<div id="my-id" class="my-class">
<div>my text</div>
</div>


Переменные со строковыми значениями «my-id» и «my-class» попадают в наши атрибуты. Переменная «my text» становится содержимым первого тэга div. Переменные — это уже PHP ноды, о которых речь пойдет чуть ниже.

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

<h1>Hello, %user%</h1>


И если шаблон %user% выведен как коннектор, то теперь мы можем заменить этот шаблон переменной следующим образом:



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

<h1>Hello, Mooha</h1>




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

Например, у нас есть файл template.tpl, который содержит два шаблона для замены: %title% и %content%. Содержимое файла следующее:

<html>
  <head>
  </head>
  <body>
    <h1>%title%</h1>
    <div>%content%</div>
  </body>
</html>


Чтобы подключить файл и заменить шаблоны переменными, достаточно построить вот такую схему:



В результате получим:

<html>
  <head>
  </head>
  <body>
    <h1>My article title</h1>
    <div>My article content</div>
  </body>
</html>


Так как мы уже вышли за рамки описания только HTML нод, перейдем к следующему типу — к PHP нодам.

PHP ноды





Это простейший пример арифметических вычислений и вызова встроенной функции abs() с помощью PHP нод. Названия нод произвольные и могут меняться в свойствах каждой ноды. Для удобства я переименовал их так, чтобы были видны значения переменных и названия арифметических операций.
Первые три ноды «1», «2» и «5» — это переменные, значения которых соответствуют названиям.
Т.е. в результате мы получим такой скрипт:

<?php
  print abs((1+2)-5);
?>


А на выходе просто число 2.



Я постарался реализовать необходимые логические конструкции, обычно используемые в нодовых системах. Например, вот так выглядит сравнение двух переменных (или результатов двух потоков):



Результат будет true (1!=2).

А вот так можно делать выбор потока, исходя из условий:



Здесь каждому красному коннектору в свойствах ноды присвоены значения, с которыми сравнивается значение, поступившее в коннектор оранжевого цвета. Если значения совпадают, то переключатель Switch пропускает соответствующий поток. В этом примере красные коннекторы имеют значения 1, 2 и 3. То есть результат такой конструкции будет «it was 2».

Во всех цепочках в соединениях передаются конечные значения всех предыдущих вычислений потока. Таким образом в следующем примере у нас результат будет тоже «it was 2», но результирующая строка собирается из нескольких действий:



Генерируется примерно следующий код:

$data = 2;
switch ($data)
{
  case "1": // заданное в первом коннекторе значение для сравнения
  $result = "it was 1";
  break;
  case "2":// заданное во втором коннекторе значение для сравнения
  $result = str_replace("foo", "2", "in was foo");
  break;
  case "3": // заданное в третьем коннекторе значение для сравнения
  $result = "it was 3";
  break;
  default:
  $result=false;
}
var_dump($result);


Отдельного внимания заслуживает нода Copy, которая по значениям из первого соединения размножает шаблон из второго. Это полезно, например, в том случае, когда нам необходимо размножить HTML код и заменить в нем шаблоны значениями из массива. В самом простом случае с одномерным массивом такая схема выглядит следующим образом:



Для наглядности я назвал ноды значениями их переменных. В первый коннектор (для значений) мы запускаем JSON массив, во второй — HTML код с шаблоном для замены. Все ноды (или цепочки нод), подключенные к коннектору шаблона будут скопированы столько раз, сколько имеется элементов в массиве, подключенному к первому коннектору. При этом у каждой ноды слева от такого соединения появится специальная настройка, какие именно данные в ней заменять элементом из массива. В нашем случае в ноде с параграфом настроена замена шаблона %number%. Т.е. результатом этой схемы будет следующий код:

<p>first paragraph</p>
<p>second paragraph</p>
<p>third paragraph</p>




Вот пример сложнее, с массивом объектов и с несколькими нодами:



В переменной «JSON» следующий массив объектов:

[
  {"title":"first title","content":"first content"},
  {"title":"second title","content":"second content"}
]


В ноде c тэгом
 шаблону %title% в качестве источника для копирования указан объект "title" массива. В ноде с тэгом  шаблону %content% в качестве источника для копирования указан объект "content" массива. В результате генерируется следующий код:

<div> <h1>first title</h1> <p>first content</p> </div> <div> <h1>second title</h1> <p>second content</p> </div>




По умолчанию нода Copy имеет тип foreach и работает так, как я описал в двух примерах выше. Но мы можем выбрать тип for, после чего появится возможность настроить стартовое значение счетчика, конечное значение, шаг и тип инкремента (возрастающий или убывающий).

Ноды запросов к таблицам базы данных MySQL





Базовая нода для запросов — это таблица, а точнее — оператор SELECT, который выполняется по-умолчанию при подключении к любому полю. Присоединившись к последнему выходу, обозначенному символом "*", мы выполним запрос:

SELECT * FROM `authors`


Результат получим в виде массива объектов в формате JSON. Это подключение "поймает" следующий набор:

[
  { "id": "1", "Name": "Iain Menzies Banks", "Birthday": "1954-02-16" },
  { "id": "2", "Name": "Charles Michael Palahniuk", "Birthday": "1962-02-21" }
]


Чтобы получить массив только имен авторов, соединение будет выглядеть следующим образом:



На выходе мы получим обычный одномерный массив:

["Iain Menzies Banks", "Charles Michael Palahniuk"]


На данный момент в редакторе есть несколько служебных SQL нод, с помощью которых можно выполнять несложные выборки. Например:



В первом случае (слева) мы выполним запрос:

SELECT * FROM `authors` WHERE id=1


И получим массив с одним объектом:

[
  { "id": "1", "Name": "Iain Menzies Banks", "Birthday": "1954-02-16" }
]


Во втором случае (справа) запрос будет таким:

SELECT * FROM `authors` WHERE Name LIKE '%Banks%'


А вот запрос с несколькими условиями и сортировкой:



SELECT * FROM `authors` WHERE (`Birthday` < '1990-01-01' AND `Birthday` > '1920-01-01') ORDER BY `Name` ASC


Служебная нода AND выступает здесь не только в качестве логического оператора в условии WHERE, но и в качестве соединения команд в одном запросе (в нашем случае, добавление к запросу команды ORDER BY)



Запросы INSERT, UPDATE и DELETE вызываются подключениями к таким же нодам-таблицам, но только с соответствующими настройками.

Простой INSERT выглядит так:



INSERT INTO `authors` (`id`, `Name`, `Birthday`) VALUES ('1', 'Iain Menzies Banks', '1954-02-16')


UPDATE:



UPDATE `authors` SET `Name`='Iain Banks', `Birthday`='1954-02-17' WHERE `id`='1'


DELETE:



DELETE FROM `authors` WHERE `Birthday`<'1954-02-17'


В процессе построения генерируются запросы с использованием PDO.

С помощью нодовых соединений можно построить почти любые типы запросов, в том числе организовать JOIN объединения. Однако не для всех сложных запросов я нашел идеальные графические решения, поэтому оставил возможность вписать текст SQL инструкции в специальную ноду. Немного забегая вперед, скажу, что работу с базой данных можно построить в системе и другими способами, не только с помощью MySQL нод.

"А теперь вместе!"


И, наконец, пример объединения всех трех технологий (MySQL, PHP и HTML) в одной цепочке, может выглядеть следующим образом:



Здесь у нас имеется заготовленный шаблон library.tpl для вывода книг из домашней библиотеки. Содержимое примерно следующее:

<div>
  <select>
    <option value=''>all authors</option>
    %author-list%
  </select>
</div>
<br />
<div>%book-list%</div>


У нас есть два шаблона для замены: %author-list% и %book-list%. В первый нам нужно вывести тэги option, соответствующие всем авторам. Во второй — все книги, а именно — размножить блок "book block", в котором содержится HTML код вывода одной книги. Первый список нам нужен для того, чтобы фильтровать книги по автору (для этого действия мы будем использовать функцию jquery в шаблоне library.tpl).

Для первого шаблона %author-list% мы добавляем ноду Copy ("Author Copy"), которая по данным из таблицы "authors" клонирует тэг "option", при этом внутрь тэга помещает имя автора, а атрибуту "value" присваивает значение id. На выходе ноды "Author Copy" мы получим следующий код:

<option value='1'>Iain Menzies Banks</option>
<option value='2'>Charles Michael Palahniuk</option>
<option value='3'>Mikhail Bulgakov</option>


Нода "book block" содержит следующий код:

<div class='author-%author-id%'>
  <img width='100' src='images/%image%' align='left' />
  <b>%title%</b> by <b>%author%</b>
  <br />
  ISBN: %isbn%<br />
  <small>%description%</small>
  <br clear='all'/>
</div>


Шаблоны в этом блоке заменяются данными книги из объединенной выборки двух таблиц "books" и "authors". Таблица "authors" здесь нужна для вывода в книге имени ее автора. Сам блок клонируется столько раз, сколько у нас записей в таблице "books".

Вот что у нас получится в результате:



Заключение


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

  • Создание групп — специальных нод, внутрь которых можно прятать целые схемы и выводить только необходимые входящие и выходящие коннекторы. Глубина вложенности групп неограниченна.
  • Возможность выводить переменные из любой вложенной схемы в качестве параметров для группы-родителя.
  • Сохранение логических схем или их частей в качестве сниппетов, для повторного использования в других скриптах.
  • Возможность написания своих функций, которые можно выводить в редакторе в виде нод.
  • Создание своих или подключение готовых классов, возможность вывода методов классов в качестве нод.
  • Подключение скриптов (include), как созданных в самой системе, так и скриптов из других файлов.
  • Возможность редактирования файлов, используя встроенный редактор.
  • Инструменты для создания и редактирования таблиц MySQL.


Результат работы редактора — набор генерированных PHP файлов. Каждая сцена — это отдельный скрипт, сохраненный в специальной папке. Такой скрипт может выполняться без помощи самого редактора. Кроме того, в настройках системы можно выбрать стартовый сценарий, который будет включать в себя главную логику работы всего проекта, исходя из полученных данных (строка запроса или передаваемые параметры).

Цель написания этой статьи — понять, насколько такая система может быть интересна для разработчиков. Я слишком долго и увлеченно занимался этим проектом и у меня изменилось отношение к нему с того момента, как я начал над ним работу. Поэтому я буду крайне признателен за отзывы и комментарии, за вопросы и предложения, а также за участие в тестировании.

На данный момент проект почти закончен и идет процесс отладки. Совсем скоро я буду готов выложить бета-версию. Для того, чтобы быть в курсе процесса разработки и узнать о выходе проекта — нужно просто подписаться на получение новостей любым удобным способом на сайте Mooha.net.



Техническая информация


В проекте использованы следующие инструменты:
  • HTML5 Canvas и Javascript (нодовый редактор)
  • JQuery 2.0 (панели свойств и настроек)
  • Codemirror (встроенный редактор файлов)
  • CKEditor (расширенный редактор полей таблиц базы данных)
  • Spectrum Colorpicker (изменение цвета элементов нод)
  • jQuery File Tree Plugin (браузер файловой системы)

Серверная часть тестировалась в таких условиях:
  • Apache 2.2.22
  • PHP 5.4.3
  • MySQL 5.5.24
Tags:
Hubs:
+56
Comments 42
Comments Comments 42

Articles