Сложная, сложная EAV струкутра

191
14 марта 2018, 07:12

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

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

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

В принципе ничего сложного. Сложное начинается дальше:

Есть 3 основных задачи что нужно сделать: - вывести объявления по группам (id группы в основной таблице) - фильтровать объявлениям по параметрам в группе - добавить объявление в группу, где предопределены выбираемые параметры.

А дальше начинается лютая жесть. 1. Вывести объявления по группам - легко, сделано. 2. Фильтровать объявления по параметрам. То на чём я пока застрял:

Эта задача делиться на 2 части: 1. Сформировать страницу с фильтрами для выбранной группы. Пользователь нажимает на кнопку "фильтр" в нужной группе объявлений, посылается ajax запрос и начинается формироватся страница с фильтрами: - смотрим на все активные объявления в группе - смотрим какие параметры определены у каждого объявления в активных объявлениях и их значения - группируем параметры убирая дубли - забираем json массив и генериуем на php страницу с фильтрами. (крутость то что показываются только те фильтры и значения фильтров, которы присвоены активным объявлениям в группе, то есть не показываем лишние_

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

    Вот тут я застрял.

    В чём именно проблема. Я пытаюсь написать универсальный обработчик запрос-ответ. То есть посылаем выбранные параметры, отбираем MySql запросом нужные объявления и показываем.

    Количество групп фильтров (цвет, цена, размер и т.д.) заранее не известны для будущего MySql запроса. Это будет известно когда прийдет запрос от обработчика фильтров.

    Было бы не сложно, если бы способ фильтрации был одного типа (например чекбоксы), но фильтры бывают разные. Например типа integer, вес в кг, где пользователь выбирает на фронтэнде диапазон от и до.

    Получается что на страницу обработчика перед выборкой SELECT мы получаем данные из формы, где будет известно:

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

    То есть можно ли сделать один универсальный обработчик на водящие данные, что бы выбрать нужные объявления? Для наглядности пример двух входящий форм, что бы понять можно ли вообще написать универсальный обработчик фильтров.

пришедшая форма от группы объявлений № 1

  • param1 - Цвет: серый, белый (в базе: серый, белый, жёлтый, синий)
  • param2 - Стоимость: от 100 до 500 (в базе: 20, 80, 120, 400, 800)

пришедшая форма от группы объявлений № 2

  • param4 - Вкус: кислый (в базе: сладкий, солёный, кислый)
  • param2 - Стоимость: от 100 до 500 (в базе: 20, 80, 120, 400, 800)
  • param8 - Размер: xs, l (в базе: xs, xm, l, xl, xxl)

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

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

Кто знает как мне помочь?

Answer 1

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

/**
 * Получение списка id пользователей по условиям
 *
 * @param array $filters Фильтры
 * @param array $order   Порядок сортировки
 *
 * @throws InvalidArgumentException
 *
 * @return array
 */
public function filter(array $filters, array $order = [])
{
    $fields  = $this->c->dbMap->users; // список имен полей таблицы пользователей с их типом
    $orderBy = [];
    $where   = ['u.id>1']; // нужное мне ограничение при любых фильрах
    // проверка и построение порядка вывода результата
    foreach ($order as $field => $dir) {
        if (! isset($fields[$field])) {
            throw new InvalidArgumentException('No sorting field found');
        }
        if ('ASC' !== $dir && 'DESC' !== $dir) {
            throw new InvalidArgumentException('The sorting order is not clear');
        }
        $orderBy[] = "u.{$field} {$dir}";
    }
    if (empty($orderBy)) {
        $orderBy = 'u.username ASC';
    } else {
        $orderBy = \implode(', ', $orderBy);
    }
    $vars = [];
    // проверка и построение условий запроса
    foreach ($filters as $field => $rule) {
        if (! isset($fields[$field])) {
            throw new InvalidArgumentException('No sorting field found');
        }
        switch ($rule[0]) {
            case '=':
            case '!=':
                $where[] = "u.{$field}{$rule[0]}?{$fields[$field]}";
                $vars[]  = $rule[1];
                break;
            case 'LIKE':
                $where[] = "u.{$field} LIKE ?{$fields[$field]}";
                $vars[]  = \str_replace(['*', '_'], ['%', '\\_'], $rule[1]);
                break;
            case 'BETWEEN':
                ...  // пишем свои обработки
                break;
            case ... // пишем свои обработки
                ...
                break;
            default:
                throw new InvalidArgumentException('The condition is not clear');
        }
    }
    $where = \implode(' AND ', $where);
    $sql = "SELECT u.id
            FROM ::users AS u
            WHERE {$where}
            ORDER BY {$orderBy}";
    $ids = $this->c->DB->query($sql, $vars)->fetchAll(PDO::FETCH_COLUMN);
    return $ids;
}

На вход подаем массив $filters в виде

$filters = [
    'username' => ['LIKE', 'Vi%'],
    'group'    => ['BETWEEN', 1, 10],
];

и массив $order в виде

$order = [
    'username' => 'DESC',
];

На выходе получаем id'шки записей удовлетворяющие фильтрам в нужном порядке.

Answer 2

Я не буду кидать сюда код, а просто изложу некие мысли. Описанный ниже подход успешно реализован, но код показать не могу.

  1. Фильтр есть объект, он может принимать одно или несколько выбранных значений, а также может генерировать по этим значениям SQL-условие попадания записи под этот фильтр
  2. Фильтры бывают разных типов, но у всех есть общая часть - принимать значение и генерировать SQL-условие
  3. Создав базовый класс фильтр и расширяя его для каждого из типов фильтров, получим набор классов, которые умеют принимать значения и генерировать SQL-условие
  4. (звездочка) Дополнительно может быть удобно сделать внешний вид фильтра также в классе фильтра. Тогда он не только будет принимать и хранить значение, но и взаимодействовать с пользователем. Но этот пункт возможно сделать не на всех моделях/архитектурах. Я его сделал, поскольку не использую CMS

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

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

Рисуем фильтры с выбранными значениями, ловим выбор пользователя, получаем набор экземпляров класса фильтр, собираем со всех фильтров условие SQL, объединяем условия SQL через AND между собой и получаем большущий такой WHERE для SQL запроса. Сохраняем в сессии, лимитируем, выдаем постраничный результат, все довольны.

Плюсы:

  1. Удобно отлаживать каждый тип фильтр отдельно в рамках класса
  2. Удобно модифицировать каждый тип фильтра - поменять внешний вид, поменять значения, брать значения из базы или из списка, или оттуда и оттуда
  3. Удобно добавлять новые фильтры
  4. Можно добавлять несколько фильтров одного типа, без всяких проблем

Минусы:

  1. Довольно сильная абстрактная модель с иерархией классов может напугать неподготовленного программиста
  2. Решение на AJAX может потребовать дополнительных знаний и навыков в работе с сессиями на стороне сервера, чтобы кэшировать выбор пользователя, плюс нужна хорошая подготовка в JS

Если текстом описание не понятно, я могу набросать небольшой пример с парой типов фильтров, но я надеюсь, что вы сами попробуете, это не трудно

READ ALSO
Обнуляются файлы на хостинге

Обнуляются файлы на хостинге

Доброе время сутокВозникла такая проблема

178
Как добавить drag'n'drop компонент в iblock.element.add.form. Bitrix

Как добавить drag'n'drop компонент в iblock.element.add.form. Bitrix

Добрый деньМожет кто нибудь из гуру битрикса знает, как добавить drag'n'drop в редактирование формы iblock

161
Как правильно парсить XML в PHP?

Как правильно парсить XML в PHP?

Здравствуйте, коллеги!

166
Загрузка файла на сервер [PHP]

Загрузка файла на сервер [PHP]

Не понимаю почему не работает загрузка картинки на сервер

155