Подобие LINQ на PHP для EAV модели хранения данных

от автора

Увидев пост о LINQ на PHP, я решил незамедлительно поделиться своими наработками в этой области.
Моей реализации далеко до полноценного LINQ, но в ней присутсвует наиболее заметная черта технологии — отсутвие инородной строки запроса.

Зачем?

Моя деятельность, как рабочая так и не очень, связана с БД, которая имеет EAV модель хранения данных. Это значит, что при увеличении количества сущностей, количество таблиц не увеличивается. Вся информация хранится всего в двух таблицах.
image
Таблицы с данными в EAV модели
Естественно, что для того чтобы получить «запись» из такой структуры, необходимо написать запрос совершенно непохожий на аналогичный запрос при обычной структуре БД.
Например:

SELECT field_1, field_2, field_3 FROM object 

и в EAV

SELECT f1.value_bigint, f2.value_bigint, f3.value_bigint  FROM objects ob, attributes_values f1, attributes_values f2, attributes_values f3  WHERE ob.ID_type="object"  AND f1.ID_object = ob.ID_object AND f1.ID_attribute = 1 AND f2.ID_object = ob.ID_object AND f2.ID_attribute = 2  AND f3.ID_object = ob.ID_object AND f3.ID_attribute = 3 

Как говорится — почувствуйте задницу разницу.
Ситуация осложняется тем, что многие объекты связаны между собой отношениями, которые аналогично раздувают запрос.

Генератор запросов

В один прекрасный момент мне надоело писать плохочитаемую лапшу, которая содержит 50% — 70% вспомогательного кода. Тогда и появилась идея генерировать запрос автоматически. Так на свет появилася IQB — Irro Query Builder. Его концепция была навеяна тем, как устроено взаимодействие с БД в Drupal.
Вышеописанный запрос в IQB будет выглядеть следующим образом:

$q = new IQB(); $query = $q->from(array(new IQBObject('ob','object'),                           new IQBAttr('f1',1,INT),                           new IQBAttr('f2',2,INT),                           new IQBAttr('f3',3,INT)                 ))         ->where('f1','with','ob')->where('f2','with','ob')->where('f3','with','ob')         ->select('f1')->select('f2')->select('f3')         ->build(); 

Количество кода не уменьшилось, но читаемость, как мне кажется, повысилась.
В этом запросе использованы все основные методы для генерации запроса.
Метод from() принимает класс или массив классов представляющих собой таблицы БД. Таблиц всего две, так что и классов такое же количество. Конструктор класса таблицы принимает псевдоним таблицы, её условный тип и тип данных, если это таблица атрибута.
Псевдоним таблицы используется во всех остальных методах генератора запросов. Условный тип, для таблицы объектов, является названием сущности, среди которых ведётся поиск, а для таблицы атрибутов, условный тип необходим просто чтобы различать атрибуты одного объекта. Тип данных, говорит из какого поля таблицы брать данные. Это необходимо т.к. атрибут объекта является структурой с 4 полями под данные, из которых используется только одно, и в каком именно поле хранятся данные надо указывать явно.
Метод where() накладывает условия на выборку. Принимает всегда 3 аргумента: псевдоним таблицы, условие, значение. В зависимости от условия, в качестве значения может быть передан псевдоним другой таблицы, значение или массив значенией с которым сравнивается поле таблицы.
Например:

$q->where('attr','with','object'); 

задаст условие

attr.ID_object = object.ID_object 

из такого выражения

$q->where('attr','=','object'); 

получится похожее, но совсем другое выражение

attr.value_bigint = object.ID_object 

а если таблица object не была объявлена во from(), то получится вот это (если ещё тип данных атрибута изменить на string)

attr.value_ntext = "object" 

В качестве условий можно использовать строки ‘=’, ‘!=’, ‘>=’, ‘<=’, ‘>’, ‘<‘, ‘like’ и ‘with’ — принадлежность атрибута конкретному объекту.
Метод select() указывает генератору, значения каких таблиц должны попасть в выборку. Кроме того можно «обернуть» это значение в функцию, передав в метод третьим аргументом строку вроде «SUM($)», и вместо доллара в функцию подставится поле таблицы. Вторым аргументом можно передать псевдоним поля в выборке.
Вместе с методами groupBy() и orderBy() этого хватает для построения среднестатистическиз запросов на чтение.

Однако не всё так просто.
Объекты, как и сущности в обычных БД, могут быть связаны отношениями.
Связь, как это ни странно — тоже объект. С атрибутами. И чтобы получить объект Б, который является дочерним у объекта А, необходимо проделать следующие манипуляции:

$q->from(array(          new IQBObject('b','B'),         new IQBAttr('parent',23,INT),         new IQBAttr('child',24,INT)     ))     ->where('parent','=',123456) // ID_object объекта А     ->where('child','with','parent')     ->where('child','=','b') 

Многовато для простого «взять Б дочерний у А». Чтобы автоматизировать связывание объектов, в IQB сущетвует метод linked().
Метод принимает ID_object или псевдоним известного объекта, псевдоним дочернего/родительского и «флаг разворота» т.е. указание — искать дочерние объекты или родительские. Таким образом вышеизложенный код можно зписать так:

$q->from(new IQBObject('b','B'))->linked(123456,'b');// по умолчанию ищется дочерний объект. 

Можно было бы на этом и закончить, но периодически попадаются задачи, для которых генератор запросов оказывается несколько ограниченным. Например, с некоторых пор начали попадаться объекты, у которых какой-то атрибут может отсутсвовать. Для решения этой проблемы был добавлен метод joinTo() который делает LEFT JOIN таблицы атрибута к таблице объекта.
А для совсем уж экзотических запросов есть rawWhere() и rawSelect() которые позволяют вводить произвольные куски запроса.

Заключение

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

ссылка на оригинал статьи http://habrahabr.ru/post/209682/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *