MixaDIOR Feodosian

Барахло/Хлам/PHP/Харьков/Феодосия/Я

LogicComposer - логика в динамике

Октябрь 23rd, 2008 in Новости

Еще с пол-года назад приходила ко мне мысль - подчинить логику динамике или просто говоря создавать сложные древовидные цепочки условий динамически, тоесть на основе опять-таки каких-либо других логических условий, полученных данных, генерируя циклически и т.п. Первые попытки я даже не выносил на всеобщее рассмотрение. Было создание Коллектора-Декоратора WHERE и HAVING условий для Zend_Db_Select. Смысл был прост - создать наборы условий в зависимости , например, от открытого сейчас раздела сайта, а далее продекорировать ими объект Zend_Db_Select в зависимости от полученных из запроса (Request) данных. Тогда работа велась с Zend_Datagrid (привет Baziak & Bashmach) и данные было удобно выгребать одним, пусть объемным(это не значит медленным), достаточно оптимизированным SQL запросом, это реально работало, но было довольно узкоспециализированным решением, так как такой подход к построению запросов не всегда себя оправдывает. На этом предистория заканчивается и начинается повествование о LogicComposer :-).

Сама идея состоит в том, чтобы создавать древовидные цепочки логических условий (AND/OR) с неограниченной степенью вложенности, ну например:

(a < b) AND ((c == 1) OR ((n < 6) AND (h > 0)) OR ((m < 0) OR ((j == 4) 
AND (((l == 'abc') AND  (k == 'sc')) OR ((s < 0) AND (i > 6))))))

Запутанно блин, запутанно и со скобками можно париться долго, считая где они начинаются, где заканчиваются, а тут еще и нифига не понятно для чего каждая ветка условий и куда к ней добавить условие. А когда еще и эти условия собираются для, например, SQL запроса на основе данных других SQL запросов и данных HTTP запроса в разных частях исходника, написанного каким-то “талантом”, а потом еще и компонуются неправильно, потому что он также как и вы с моим примером ошибся в скобках, создавая эти условия кусочно. Вот где-то в таких условиях у меня появилось желание написать LogicComposer.

Что я сделал - создал класс на PHP, который релизует GOF паттерн композит (composite) и научил его правильно компоновать на выходе, те условия которые он в себя насобирал. Начнем с примера, построим условие, написанное выше, с помощью LogicComposer:

$myLogic = new LogicComposer(); 
$myLogic->start('a < b') 
->addAND()->mark('myLogic1') 
->start('c == 1') 
->addOR()->start('n < 6')->addAND('h > 0') 
->jump('myLogic1') 
->addOR() 
->start('m < 0') 
->addOR() 
->start('j==4') 
->addAND()->mark('mySubLogic') 
->start() 
->start("l == 'abc'")->addAND("k == 'sc'") 
->jump('mySubLogic') 
->addOR() 
->start('s < 0')->addAND('i > 6');
echo $myLogic->toText();

Мне стало понятнее, о скобках и синтаксических операторах логики позаботиться сам класс, но глупо было бы изобретать свой псевдоязык описание сабжа только для этого. В участке кода, что я привел в пример, видны 2 не особо понятных метода - mark() и jump(). Эти методы позволяют маркировать любую ветку логических условий и, соответственно, возвращаться к любой из промаркированных веток для добавления в нее новых условий или веток.
Представим, что нам понадобилось добавить условие в ветку которая прибавлена ANDом после (j == 4), в нашем примере мы промаркировали ее как mySublogic, для этого нужно снова вернуться в эту ветку и добавить условие:

$myLogic->jump('mySubLogic')->addOR('f > 9'); 
echo $myLogic->toText();

Результатом будет:

(a < b) AND ((c == 1) OR ((n < 6) AND (h > 0)) OR ((m < 0) OR ((j==4) 
AND (((l == 'abc') AND (k == 'sc')) OR ((s < 0) AND (i > 6)) OR (f > 9)))))

Теперь постараюсь прояснить, то что возможно еще не всем понятно в использовании класса. Любой метод задающий условие - start(), addOR(), addAND(), вызванный без аргументов, создает новую ветку условий а вы выходе вернет уже объект этой ветки, при вызове данного метода с аргументом условия, то это условие будет добавлено в текущую ветку условий, а на выходе будет объект текущего условия. Подробнее разобрав пример вы быстро все поймете, а составив пару простеньких условий, так я думаю вы уже четко представите, как использовать этот класс в своих целях.
В процессе реализации класса я несколько раз искоренял костыли и пытался сделать использование класса логичнее и проще, но сохранить гибкость. Скажу сразу сам класс прошел испытания, так сказать “в бою” - создавал набор логических условий в поисковом sql запросе, притом переделывать логику этого поиска было нельзя, а вот компановку - пришлось, так как были явные ошибки, приводящие в тому что с обязательными условиями компановались ORом условия, которые очень часто давали true…

Также в классе учтены несколько нужных штучек:

  • Пустые группы(без стартовых условий и подусловий) рендериться не будут;
  • Если не задано стартовое условие/группа условий, то первое заданное в группе условие станет стартовым, а при определении стартового встанет на свое место с заданной для него логикой;
  • метод reset(), который позволяет полностью сбросить содержимое группы, включая стартовое условие;
  • методы addANDUnless() и addORUnless(), которые добавят условие только при выполнении условия поданного в них :) (удобно, когда сам не знаешь какая логика нужна OR или AND);
  • magic method __to String(), echo $myLogic выведет в браузер сформированное условие;
  • возможность использования адаптеров синтаксических едениц, определяющих условия. Например, использовать вместо OR ||, а вместо AND &&.

Сама идея и код класса открыты для всеобщего использования и критики.
Скачать:
http://www.feodosian.com/downloads/logiccomposer/logiccomposer-0.1.3b.zip
svn:
http://anonymous:anonymous@svn.yomoyo.net/ideas/LogicComposer/
Поглядеть исходники через web:
http://yomoyo.feodosian.com/srcFiles/LogicComposer/

Очень хотелось бы услышать или прочитать критику.

Tags: ,

5 Responses to “LogicComposer - логика в динамике”

  • Anton Shevchuk
    Октябрь 23rd, 2008 at 13:43

    Крези мен :)

    Мне не понравилось именование методов - оставь OR(), AND()

  • darkstar
    Октябрь 23rd, 2008 at 14:19

    :), изначально тоже хотел методы OR() и AND(), но это зарезервированные слова и потому таким образом именовать методы нельзя :(. Я думаю можно закрутить через __call(), но мне показалось это излишним джедайством, которое снизит читабельность кода.

  • COTOHA
    Октябрь 23rd, 2008 at 15:50

    омг. не нравится реализация.

    твоя задача переформулируется как “надо построить дерево выражения, для последующего его представления в виде php/sql/whatever-кода”

    в итоге тебе надо как-то хранить структуру типа

    expression
    |
    or
    / \
    > and
    / \ / \
    a 4 == <=
    / \ / \
    b 5 c 4

    на лабах в институте это советуют делать в польской нотации или S-expression:
    (expression (or (> (a 4))(and (== (b 5)(<= (с 4))))

    если чуток отформатировать, получается:

    (expression
    (or
    (> (a 4))
    (and
    (== (b 5)
    (<= (с 4)
    )
    )
    )

    ничего не напоминает? а если заменить круглые скобки на угловые?

    а вот тем, что получается с угловыми скобками очень удобно манипулировать, лазать по нодам, добавлять способ представления через xslt и, кстати, расширяемость бесконечная :) как бонус оно уже сериализованно.

    повторюсь - идея правильная :) щас вон Мартин Фаулер бредит DSL’ями… но вот реализация - имхо - не очень.

  • COTOHA
    Октябрь 23rd, 2008 at 15:52

    метпрограммирование, ёпт. о правильных вещах задумываешься, товарисч!

  • darkstar
    Октябрь 23rd, 2008 at 16:23

    Очень ждал такого комментария, сразу появилось несколько идей и тем для прочтения. Буду работать над алгоритмом.
    Сейчас всплыл внезапный интерес к метапрограммированию (нашел для себя BNF, Python и Pyparser), главное не перегореть как у меня это часто бывает :). Спасибо за содержательный комментарий.

Leave a Reply

Архивы