Архитектура CMS. Модель данных. Часть 3

    В предыдущей статье на примере создания объектной модели простого сайта производились одиночные загрузки сущностей из базы данных по их идентификаторам конструкцией Object::Create($id), при этом мы знали, у какой сущности (чаще всего класса), какой идентификатор, так как сами создавали эти сущности и в крайнем случаи могли просто заглянуть в базу данных. На практике загружать сущности по идентификатору проблематично, если нас интересуют сущности, о существовании которых можно только догадываться, то есть, не имея информации об их идентификаторах. Более того существует необходимость загружать несколько сущностей разом, отвечающих некоторым условиям.

    В магазине, например, мы не выбираем товар по его серийному номеру или штрих-коду, не зная при этом, что он означает – мы смотрим на свойства товара интересующие нас. На главной странице сайта, опять же для примера, необходимо выводить последние новости, что сводится к выборке из базы данных (объектной модели данных) 10 объектов класса «Новость» с сортировкой по дате их создания. Для осуществления подобных запросов необходим гибкий способ описания условий выборки сущностей – условий поиска с учетом особенностей объектной модели. На основе условия необходимо создавать SQL код для непосредственной выборки из БД идентификаторов сущностей удовлетворяющих условию, имеем идентификаторы – имеем сущности.

    Для создания условия поиска используется объектный подход. Из объектов представляющих логические и другие функции создаётся иерархия, теоретически её можно интерпретировать в подобие математической функции. Ниже пример условия для получения сущностей класса «news». Расшифровывается следующим образом: выбрать сущности (Q::Entity), принадлежащие классу (Q::IsClass), у которого атрибут 'sys_name' (Q::Attrib) равен значению 'news' (Q::Comp).

    $cond = Q::Entity(  
            Q::IsClass(  
                Q::Comp(Q::Attrib('sys_name'), '=', 'news')  
        )  
    );
    

    Условие используется для создания объекта запроса Query. Объектом Query выполняется интерпретация условия в SQL код и его выполнение при вызове метода Execute(). Результатом выполнения запроса является массив найденных сущностей либо, если условие начиналось с функции Count, результатом будет целое число – количество сущностей удовлетворяющих условию. Поиск сущностей можно ограничить по количеству и выполнить смещение как это делается параметром LIMIT в SQL, ограничение осуществляется вторым и третьим аргументом при создании запроса, либо использованием методов SetCount() и SetStart() объекта запроса.

    // Объект запроса с условием $cond и ограничением результата количеством не более 10   
    // начиная с первой (0) сущности  
    $q = new Query($cond, 10, 0);  
      
    // Выполнение запроса. Результат – массив объектов данных Object  
    $list = $q->Execute();  
      
    if (sizeof($list)>0){  
        echo $list[0]->getP('head')->getA('value'); // заголовок первой новости  
    }
    

    Условие создается из объектов классов CondAttrib, CondClass, CondComp, CondCount, CondEntity, CondIsExist, CondLink, CondLog, CondNotExist и CondParam, но с целью упрощения синтаксиса вместо непосредственного использование оператора new и классов Cond* используется статический класс Q. С помощью его статических методов (фабричных методов) создаются объекты условия.

    Условие включает в себя аргументы (сущности, атрибуты, связи), функции проверки наличия/отсутствия атрибутов или свойств у сущности, функцию подсчета количества свойств у сущности или самих сущностей, логические функции Or и And, выполняющие ещё роль скобок, и функцию сравнения значений атрибутов или результата функции подсчета количества с использованием операций '=', '>', '<','<=', '>=','<>','like'.

    В отличие от SQL, при выборке не нужно указывать, откуда выбирать (из каких таблиц), не нужны также объединения и группировка результатов. В запросе с помощью условия просто указывается ЧТО выбирать. Так как в объектной модели абсолютно всё является объектами, то и выбирать кроме объектов нечего, единственное можно вместо поиска объектов узнать количество объектов удовлетворяющих условию. Поэтому условие всегда начинается с аргумента сущности или функции подсчета сущностей.

    //Условие - все сущности  
    $cond1 = Q::Entity( );  
    //Условие - количество сущностей  
    $cond2 = Q::Count(Q::Entity( )); 
    

    В первом варианте будет возвращен массив с абсолютно всеми сущностями объектной модели (все объекты, классы и связи), конечно, если не будет ограничений по количеству искомых сущностей в запросе Query. Во втором случаи будет возвращено число – общее количество сущностей.

    Нас редко интересуют абсолютно все сущности, поэтому аргумент-условие сущности Q::Entity($cond) дополняется условием $cond, его ещё можно назвать фильтром. В нём определяется, какие атрибуты и свойства должны быть или отсутствовать у искомых сущностей, как бы продолжая условие: «Сущности, у которых…»

    Атрибуты


    Условие можно поставить на значение атрибута сущности. Для этого используется аргумент-условие обозначающий атрибут Q::Attrib и функция сравнения Q::Comp. Нижеприведенное условие означает: «Сущности, у которых атрибут sys_name равен значению link»

    $cond = Q::Entity(    
            Q::Comp(Q::Attrib('sys_name'),'=','link')  
    );
    

    Условие можно дополнить логическими функциями «И» и «ИЛИ» (Q::LogAnd и Q::LogOr), если проверка на значение или общее условие на сущность неоднозначны. Логические функции «И» и «ИЛИ» играют также роль скобок, с помощью которых можно создавать сложные условия любой вложенности.

    Запрос с нижеприведенным условием вернет сущности, у которых атрибут sys_name равен значению link или если атрибут sys_name равен значению label, а атрибут is_define равен 0.

    $cond = Q::Entity(  
            Q::LogOr(  
                Q::Comp(Q::Attrib('sys_name'),'=','link'),  
                Q::LogAnd(  
                    Q::Comp(Q::Attrib('sys_name'),'=','label'),  
                    Q::Comp(Q::Attrib('is_define'),'=',0)  
                )  
            )  
    );
    

    В объектной модели, создание которой было рассмотрено в предыдущей статье, атрибут sys_name есть у классов и связей, поэтому результатом приведенного условия будет класс link и связи с системными именами label, не определяющие свойства. Результатом будут разнотипные сущности. Хотя приведенное условие вряд ли имеет практическое применение, но оно хорошо демонстрирует безразличность к типам сущностей при поиске, заметьте, в условии нет уточнений о существовании атрибутов, значения которых сравниваются. Поиск выполняется практически так же, как это делает человек. Например, найти всё, что красное среди разнотипных объектов: фонарь, закат, трактор, мяч, кровь, молоко, флаг CCСР :) – поиск осуществляется без проблем, главное, чтоб красными были.

    Параметр вместо значения


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

    $cond = Q::Entity(  
            Q::Comp(Q::Attrib('sys_name'),'=', Q::Param('par1'))  
    );  
    // Создаем запрос  
    $q = new Query($cond);  
    // Устанавливаем значение параметра  
    $q->SetValue('par1', 'news');  
    // Выполняем запрос  
    $list1 = $q->Execute();  
    // Устанавливаем новое значение параметра  
    $q->SetValue('par1', 'name');  
    // Выполняем запрос  
    $list2 = $q->Execute();
    

    Кроме сравнения значения атрибутов, можно просто поставить условие на их наличие функцией-условием Q::IsExist(). Нижеприведенное условие определяет сущности, у которых есть атрибут value, результатом будут все объекты строк и чисел.

    $cond = Q::Entity(  
            Q::IsExist(Q::Attrib('value'))  
    );
    

    Свойства


    К атрибутам ещё вернемся, когда речь пойдет о сортировке, а сейчас про условия на свойства сущности. Свойство – это совокупность связи и сущности, с которой выполнена связь. Условие в запросе можно просто поставить на отсутствие/наличие свойства или на количество свойств у сущности. Но самое интересное от того что свойство – это объекты (повторяюсь) и поэтому условие можно поставить на атрибуты и свойства самой связи или сущности с которой выполнена связь. Только вдумайтесь, ведь связь и объект, с которым выполняется связь, могут иметь такие же условия на свои атрибуты и самое важное на свои свойства, как у искомой сущности, что позволяет создавать всеохватывающие условия. Например, можно искать сущности, у которых есть некое свойство, при этом объект являющийся свойством должен иметь связь (свойство) с объектом, который в свою очередь должен иметь атрибут value содержащий некий фрагмент текста. При всём этом в условии даже можно не уточнять что за свойство – его системное имя, в частности. Поиск будет оперировать только тем, что известно из условия. В общем, абстрактность, универсальность и гибкость.

    Итак, начнем с условия проверки наличия свойства у искомых сущностей. На самом деле использовать функцию-условие Q::IsExist не нужно, так сам факт указания свойства в условие устанавливает наличие свойства.

    $cond = Q::Entity(  
            Q::Property( )  
    );
    

    То же самое:

    $cond = Q::Entity(  
            Q::IsExist(Q::Property())  
    ); 
    

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

    Обратным условием является проверка отсутствия свойства. Для определения отсутствия свойства используется функция-условие Q::NotExist. Результатом нижеприведенного условия будут все сущности, не имеющие свойств.

    $cond = Q::Entity(  
            Q::NotExist(Q::Property())  
    );
    

    Как поставить условие на наличие конкретного свойства? Q::Property($link_cond, $entity_cond) имеет два аргумента для определения соответственно условия на связь и условия на сущность, с которой выполнена связь. Опять же, так как связь тоже является объектом, условия для неё возможны точно такие же, как и для сущности. Например, условие на атрибут.

    Нижеприведенное условие использует условие на атрибут у связи свойства. Запрос с этим условием возвратит сущности, имеющие множественное свойство (size=0 означает множественность), а оно есть только у новостей. Если быть точнее сама связь с атрибутом size равным нулю есть у класса новости («news»), как определяющей комментарии, и у первой новости, имеющей два комментария, вторая новость комментариев не имеет (смотрите схему в предыдущей статье).

    $cond = Q::Entity(  
            Q::Property(  
                Q::Comp(Q::Attrib('size'),'=',0))  
    );
    

    Теперь добавим условие на сущность, с которой выполнена связь – поставим условие на свойство сущности, с которой выполнена связь. Нижеприведенное условие возвратит сущности, у которых есть множественное свойство, в свою очередь сущность являющаяся свойством имеет своё свойство без уточнений какое, но связанное с сущностью, атрибут value которой содержит фрагмент текста «Первый». Условие получилось абстрактным. Результатом его на самом деле будет первая новость, так как объект новости имеет комментарий, заголовок которого начинается со слова «Первый». Взаимосвязь между объектом новости и комментария как раз подходит приведенному условию

    $cond = Q::Entity(  
        Q::Property(  
            Q::Comp(Q::Attrib('size'),'=',0), //множественное свойство  
            Q::Entity( // условие на сущность, с которой выполнена связь  
                Q::Property(  
                    null, // условия на связь нет  
                    Q::Entity(Q::Comp(Q::Attrib('value'),'like','%Первый%'))  
                )  
            )  
        )  
    ); 
    

    Продемонстрируем пример условия с подсчетом количества свойств. Нижеприведенное условие возвратит сущности, у которых более 4 свойств. Ими будут первая новость и класс категории. У новости имеется связь на категорию, заголовок, текст и два комментария – всего 5 свойств. У класса категории имеется название, описание и четыре связи, определяющие свойства для объектов – всего 6. В условии мы не уточняли, какие связи учитывать.

    $cond = Q::Entity(  
            Q::Comp(Q::Count(Q::Property()), '>', 4)  
    );
    

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

    Сущность, являющаяся свойством


    На практике часто необходимо выполнять поиск сущностей, являющимися свойствами других сущностей. Например, поиск комментариев, принадлежащих конкретной новости. Для этих целей используется аргумент-условие Q::IsProperty подобное аргументу-условию свойства, разница только в том что, им определяется связь и сущность-владелец связи. В остальном все также – условия на связь, условия на сущность. Можно даже также посчитать, сколько сущностей ссылаются на искомую и поставить условие, что должно быть не мене 2 владельцев.

    $cond = Q::Entity(  
            Q::Comp(Q::Count(Q::IsProperty()), '>=', 2)  
    ); 
    

    Приведенное условие возвратит классы «string», «long_string», «category» и объект корневой категории с названием «Мероприятия» — все эти сущности имеют более одного владельца.

    Принадлежность к классу


    Ещё одним важным и часто используемым условием является принадлежность к классу Q::IsClass. Принадлежность к классу – это фактически свойство объекта, но оно требует особого способа проверки. Необходимо учитывать иерархию наследования, то есть проверка принадлежности к классу не сводится к банальной проверке сущности, с которым выполнена связь. Например, объект новости принадлежит к классу «news», но также принадлежит и классу «content» и базовому классу «id». Нижеприведенное условие возвратит все комментарии.

    $cond = Q::Entity(  
            Q::IsClass(  
                Q::Comp(Q::Attrib('sys_name'), '=', 'comment')  
            )  
    );
    

    Класс тоже является объектом, поэтому можно уточнять условие класса, также как сущности – проверять атрибуты и свойства.

    Применение логических функций Q::LogAnd и Q::LogOr продемонстрировано только на сравнении значений атрибутов, а ведь они могут включать в себя аргументы-условия свойств Q::Property, Q::IsProperty, принадлежности к классу Q::IsClass и функции Q::IsExist и Q::NotExist обеспечивая необходимую гибкость.

    Сортировка


    Поиск без сортировки никуда не годится. Используя сортировку можно находить актуальные темы, популярные товары и многое другое и применять с целью создания востребованного содержимого главных страниц сайта. Но как выполнять сортировку искомых сущностей, учитывая все возможные варианты условий, когда условие может даже ничего не уточнять о структуре сущностей? На самом деле определение сортировки осуществляется достаточно просто, а сама она выполняется непосредственно в СУБД при исполнении поискового SQL запроса.

    Сортировать естественно можно только по скалярным значениям – по значениям атрибутов. Параметры сортировки указываются в аргументе-условии атрибута Q::Attrib. Таким образом, совмещается определение сортируемого атрибута и параметры сортировки (по убыванию или возрастанию, и порядок сортировки – если сортируемых атрибутов не один, то можно указать последовательность сортировки), и условие существования атрибута у искомых сущностей.

    Сортировать можно по любому атрибуту, не обязательно принадлежащему искомым сущностям, можно хоть по атрибуту сущности, которая только косвенно связана чередой взаимосвязей с искомой. Помните, условие на атрибут можно поставить у связей и объектов являющимися свойствами? Так что можно даже сортировать по их атрибутам. Выходит, новости на сайте можно одновременно сортировать по дате создания и рейтингу автора новости. Или это все равно, что сортировать купленную продукцию по названию магазинов, в которых они были куплены – по свойству, не имеющего прямого отношения к продукции.

    Теоретически возможна сортировка и по количеству свойств сущностей, например по количеству комментариев новости, но временно эта возможность не реализована.

    Ещё следует заметить, что в параметрах сортировки указывается атрибут (его имя), но при этом уточнений о типе значения атрибута не требуются. Атрибут value, в частности, имеется у объектов разного класса и, самое важное, существуют различия в типах его значений у разных классов. Строки разной длины, числа целые и действительные. Но даже при этих обстоятельствах сортировка будет работать. Нижеприведенное условие возвратит отсортированный список объектов имеющих атрибут value. Сортировка будет происходить по атрибуту value по возрастанию. Второй аргумент (true) определят сортировку по данному атрибут, третий (1) – порядок сортировки (актуально при нескольких сортируемых атрибутах) и последних атрибут вид сортировки (false – по возрастанию, true – по убыванию)

    $cond = Q::Entity(  
              Q::IsExist(Q::Attrib('value', null, true, 1, false))  
    );  
    // Запрос без ограничений количества искомых сущностей  
    $q = new Query($cond,0,0);  
    $list = $q->Execute();
    


    Заключение


    Предложенный подход создания условия поиска может показаться сложным, но только от того, что все мы привыкли к строковому описанию условий, как это делается в SQL и языках программирования. Объектный подход удобен, во-первых, для программного анализа и автоматического генерирования SQL запроса, во-вторых, для него будет нетрудно сделать осмысленный GUI интерфейс.

    Ещё большую озабоченность должен вызвать вопрос о производительности и, какие SQL запросы получаются из этих мега гибких условий? Запросы получаются не слишком страшными, но применением нескольких LEFT JOIN-нов грешат и некоторыми другими конструкциями, без которых сложно достичь гибкости.

    Сайт проекта: boolive.ru

    UPD. Спустя год, предлагаю познакомиться с итогами работы над моделью данных в статье Объектная БД в реляционной СУБД.

    Развитие проекта пошло другим путем, осознав важность простоты :)
    Поделиться публикацией

    Похожие публикации

    Комментарии 17

    • НЛО прилетело и опубликовало эту надпись здесь
        0
        А где применяется? И в каких моментах отличия?
        • НЛО прилетело и опубликовало эту надпись здесь
        +1
        с теоретической точки зрения — все отлично,
        с практической — «ебануться» — слишком наворочено а оттуда сложный learning curve — no profit
          0
          :)) достаточно уметь представлять предметную область (данные) объектно, чтоб условия для поиска составлять. Тогда всё само собой понимается. ;)
            0
            Такой же подход построения условий используется в Hibernate. И нет никаких сложностей.
              0
              Поставил плюс.
              Однако в NHibernate это — только как один из вариантов, используемый для достаточно простых запросов. Для более сложных гораздо приятнее не городить кучу синтаксического мусора, а писать прямо на HQL.
            0
            Я думаю, что сказав А, стоит сказать и Б :) Показанная в посте система построения запросов — это пример QBA — «Query By API»:
            … запросы конструируются путем использования объектов-запросов, обычно примерно в такой форме:
            Query q = new Query();
            q.From(«PERSON»).Where( new EqualsCriteria(«PERSON.LAST_NAME», «Smith»));
            ObjectCollection oc = QueryExecutor.execute(q);

            Здесь запрос основывается не на пустом «шаблоне» выбираемого объекта, а на наборе «объектов-запросов», которые совместно используются для определения объекта в стиле команды, предназначенной для выполнения над базой данных. Несколько критериев комбинируется путем использования некоторой конструкции, обычно соединяющей через «And» и «Or» объекты, каждый из которых содержит уникальный объект-критерий, задающий часть условия выборки. К концу запроса могут быть добавлены вызовы объектов фильтрации/манипулирования, такие как «OrderBy(field-name)» или «GroupBy(field-name)». В некоторых случая эти вызовы методов в действительности ведут в объекты, конструируемые программистом и явно связываемые между собой...
            Отсюда
              0
              Тогда ещё не подумал об этом, а ведь использование шаблнчиков условий будет даже очень удобным да и в целях оптимизации тоже. Но почему-то вместо развития объектного подхода к составлению условий, думаю сейчас в направлении создания текстового описания — а-ля SQL3 :)
                +1
                Я думаю, что ваш путь с текстовым описанием будет куда сложнее. Оно вам надо? :)

                Парсер «синтаксис -> AST», а затем AST-процессор — это не та штука, которую можно написать и забыть — как правило, это одна из самых сложно тестируемых частей. Даже у MS в ее T-SQL встречаются баги разбора SQL-команд.

                Куда проще и понятнее Query Objects, которые позволяют строить AST напрямую, пользуясь возможностями самого языка программирования. Понятно, что для сложных запросов этот процесс будет выглядет монстровато, но на практике 90% — более-менее простые запросы.

                Далее, Query Object может отлично скрыть за своим API манипуляции нижнего уровня, paging, самостоятельно оптимизировать запросы и т.п.

                Я бы сначала пошел таким путем, а затем прикрутил к нему text query, если понадобилось бы, благо одно хорошо уживается с другим. Рекомендую вам посмотреть на HQL и Criteria API из Hibernate.

                Тем не менее, решать-то вам :) В любом случае, удачи.
                  0
                  Первый путь уже пройден, разве что только самой оптимизацией его заниматься со стороны его использования и генерации sql.
                  Я с вами не спорю, делать парсер непросто да и средствами php не эффективно. Но проработав текстовое описание запроса, можно выявить более оптимальный и более гибкий подход к описанию средствами языка программирования. Надеюсь :) Hibernate же через чур мудреный, чтоб с него брать пример.
                    0
                    Как мне кажется, текстовая форма — это только одна из форм, причем не самая удачная (с точки зрения программиста). Хотя ее проработка может быть хорошим «трамплином», чтобы перейти к более общему пониманию формы и механизма запросов в репозиторий объектов.

                    Второе. В Hibernate есть все, что стоит иметь в хорошем ORM уровня Enterprise :) Совершенно необязательно переносить это все к себе, но кое-что иметь крайне полезно.

                    К сожалению, обсуждение этого выходит за рамки поста, потому как тут вы поднимали тему исключительно паттерна Query-Object.

                    Спасибо за предметный разговор.
                      0
                      Не будем разворачивать новых тем, только хочется от вас узнать, на что именно стоит обратить внимание у Hibernate?
                        0
                        HQL — корпоративный стандарт в мире Java (кроме Hibernate, как минимум TopLink и EclipseLink его поддерживают).

                        На что конкретно вам смотреть, не могу сказать, потому что не очень хорошо представляю, что вы хотите найти; но вот тут в блоге code-inside-out.blogspot.com/2009/01/25-ormapper.html перечислено то, что поддерживается в NHibernate и полезно иметь в ORM.
                          0
                          Сорри, отправилось раньше.

                          Обратите внимание на NHibenrate Criteria API
                +1
                О майн год! Даз ит Битрикс?! %)))

                Вы бы эту ссылочку поперёд всего выкатили: multy.sql. Битрикс отдалённо похожие страшилища генерит. Ещё немного подкрутите — будет как там. Универсальность — это конечно здорово. Но, как всегда, с производительностью будет так себе.

                Пробема в том, что для крупных сайтов нужна производительность, а для мелких не нужна гибкость. Я до сих пор не понял, на кого рассчитана эта система. Как баловство — очень здорово, логично и мне всё нравится. Но на практике я бы с таким столкнуться не хотел.
                  0
                  Про производительность я не спорю, но и не молчу, на форуме всё сказано и предложны варианты оптимизации. На счёт гибкости – не на пустом месте всё создаётся, не было бы этого и подобных проектов, если бы вы (подавляющее большинство) и я не желали бы гибкости в условиях сегодняшнего рынка :)…

                  При всём этом мною вопросы производительности не забываются.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое