Я разрабатываю библиотеку для работы с Entity Attribute Value (репозиторий), сокращенно EAV (структура базы данных для хранения произвольных данных). В конце прошлой статьи я спросил у вас о чём мне ещё надо написать, вы попросили показать пример использования и сделать замеры быстродействия. Про замеры быстродействия статья была, эта будет о примере использования.

Назначение библиотеки

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

Для этих двух целей применяется EAV, но он существенно замедляется при увеличении объёма данных, и главная цель библиотеки это сделать скорость работы с данными независимой от объёма данных.

Это достигается за счёт использования материализованных представлений и таблиц, и главная задача которую решает библиотека, это синхронизации данных между таблицами EAV и конкретными таблицами, выделенными под каждую категорию (Entity - сущность). Конечно сущность может быть выделена в материализованное представление, библиотека оставляет выбор за пользователем.

Задачи которые решает библиотека "Универсальный каталог":

  1. Запись данных с произвольной структурой;

  2. Выборка данных по произвольным атрибутам;

Примеры использования

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

Кроме того будут приведены примеры:

  • как добавить новый атрибут в уже существующие материализованное представление или таблицу

  • как добавить новую позицию в уже существующие материализованное представление или таблицу

  • как обновить значения атрибутов существующей позиции в уже существующем материализованном представление или таблице

Предостережение

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

Весь приведённый код можно выполнить в консоли с помощью php -a, надо только подключить автозагрузчик.

$path = [
    __DIR__,
    'vendor',
    'autoload.php',
];
require_once(implode(DIRECTORY_SEPARATOR, $path));

При этом придётся указывать полные имена классов (возможно у меня не правильно настроен php.ini, поэтому я вынужден так делать), для примера:

$db = new \Environment\Database\PdoConnection($path);

Запись данных с произвольной структурой

Создадим произвольную структуру. И создадим одну позицию каталога - запишем данные в эту структуру.

Создать структуру

Структура это набор атрибутов (в терминах EAV - Attribute, в терминах каталога - характеристика), набор атрибутов это сущность - Entity / категория.

use Environment\Database\PdoConnection;
use AllThings\ControlPanel\Operator;

$pathParts = [__DIR__, 'configuration', 'pdo.env',];
$path = implode(DIRECTORY_SEPARATOR, $pathParts);

/** @var PDO $conn объект для работы с СУБД */
$conn = (new PdoConnection($path))->get();

/* класс для работы с элементами каталога: сущность, атрибут, значение */
$operator = new Operator($conn);

/* создаём строковый атрибут */
$attribute1 = $operator->createKind(
    'дискретный_символьный_атрибут',
    'word',
    'discrete',
);
/* создаём числовой атрибут */
$attribute2 = $operator->createKind(
    'аналоговый_числовой_атрибут',
    'number',
    'continuous',
);

/* создаём сущность */
$essence = $operator->createBlueprint('сущность');

/* зададим атрибуты для сущности */
$operator->attachKind(
    'сущность',
    'дискретный_символьный_атрибут',
);

$operator->attachKind(
    'сущность',
    'аналоговый_числовой_атрибут',
);

Кроме типов данных 'word' и 'number', поддерживаются так же типы 'time' и 'interval' это соответственно 'TIMESTAMP WITH TIME ZONE' и 'INTERVAL' в Postgre.

Создадим одну позицию

use Environment\Database\PdoConnection;
use AllThings\ControlPanel\Operator;

$pathParts = [DIR, 'configuration', 'pdo.env',];
$path = implode(DIRECTORY_SEPARATOR, $pathParts);
/** @var PDO $conn объект для работы с СУБД */
$conn = (new PdoConnection($path))->get();

/* класс для работы с элементами каталога: сущность, атрибут, значение */
$operator = new Operator($conn);

/* создадим позицию каталога, добавим "товар" в категорию */
$operator->createItem(
    'сущность',
    'позиция_из_категории_в_каталоге',
);

/* зададим значения для атрибутов позиции,
введём значения для характеристик "товара" */

$operator->changeContent(
    'позиция_из_категории_в_каталоге',
    'дискретный_символьный_атрибут',
    'красный'
);

$operator->changeContent(
    'позиция_из_категории_в_каталоге',
    'аналоговый_числовой_атрибут',
    '1234567890.1234'
);

На текущем этапе:

  1. создали два атрибута

  2. создали сущность

  3. задали сущности два атрибута

  4. создали позицию каталога

  5. задали значения для атрибутов этой позиции

И это самая занудная часть работы с библиотекой, дальше будет проще, будет веселей.

Выборка данных по произвольным атрибутам

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

После этого можно задать границы поиска и получить результаты.

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

Для этого необходимо поменять источник данных на "быстрый", а перед этим его необходимо создать.

  1. Создать "быстрый" источник данных и задать для сущности работу через этот источник

  2. Получить границы для условий поиска

  3. Задать условия поиска и получить результат

Создать "быстрый" источник данных и задать для сущности работу через этот источник

use Environment\Database\PdoConnection;
use AllThings\ControlPanel\Schema;

$pathParts = [__DIR__, 'configuration', 'pdo.env',];
$path = implode(DIRECTORY_SEPARATOR, $pathParts);
$pdo = (new PdoConnection($path))->get();

/* объект для работы с источником данных конкретной сущности */
$schema = new Schema($pdo, 'сущность');

/* создадим материализованное представление для сущности
 и назначим его как источник данных для сущности */
$schema->handleWithRapidObtainment();

/* создадим таблицу для сущности
и назначим её как источник данных для сущности */
$schema->handleWithRapidRecording();

Получить границы для условий поиска

use Environment\Database\PdoConnection;
use AllThings\ControlPanel\Browser;

$pathParts = [__DIR__, 'configuration', 'pdo.env',];
$path = implode(DIRECTORY_SEPARATOR, $pathParts);
$pdo = (new PdoConnection($path))->get();

/* объект для просмотра данных */
$browser = new Browser($pdo);

/* получим границы поиска, допустимые значения фильтров */
$filters = $browser->filters('сущность');

/*
$filters будет содержать:
array (
  0 => 
  AllThings\SearchEngine\DiscreteFilter::__set_state(array(
     'values' => 
    array (
      0 => 'красный',
    ),
     'attribute' => 'дискретный_символьный_атрибут',
  )),
  1 => 
  AllThings\SearchEngine\ContinuousFilter::__set_state(array(
     'min' => '1234567890.1234',
     'max' => '1234567890.1234',
     'attribute' => 'аналоговый_числовой_атрибут',
  )),
)
*/

Задать условия поиска и получить результат

use Environment\Database\PdoConnection;
use AllThings\ControlPanel\Browser;
use AllThings\SearchEngine\ContinuousFilter;
use AllThings\SearchEngine\DiscreteFilter;

$pathParts = [__DIR__, 'configuration', 'pdo.env',];
$path = implode(DIRECTORY_SEPARATOR, $pathParts);
$pdo = (new PdoConnection($path))->get();

$browser = new Browser($pdo);

$numbersFilter = new ContinuousFilter(
    'аналоговый_числовой_атрибут',
    '0',
    '999999999.9999',
);

$wordsFilter = new DiscreteFilter(
    'дискретный_символьный_атрибут',
    ['красный']
);

$filters = [$numbersFilter, $wordsFilter];

/* выполним поиск*/
$result = $browser->filterData('сущность', $filters);

/*
содержимое $result 
array (
  0 => 
  array (
    'thing_id' => 1,
    'code' => 'позиция_из_категории_в_каталоге',
    'дискретный_символьный_атрибут' => 'красный',
    'аналоговый_числовой_атрибут' => '1234567890.1234',
  ),
)
*/

Добавить новый атрибут

use Environment\Database\PdoConnection;
use AllThings\ControlPanel\Operator;
use AllThings\ControlPanel\Schema;

$pathParts = [DIR, 'configuration', 'pdo.env',];
$path = implode(DIRECTORY_SEPARATOR, $pathParts);
$pdo = (new PdoConnection($path))->get();

$operator = new Operator($pdo);

/ создаём новый атрибут */
$attribute3 = $operator->createKind(
    'дискретный_числовой_атрибут',
    'number',
    'discrete',
);

/* зададим новый атрибут для сущности */
$operator->attachKind(
    'сущность',
    'дискретный_числовой_атрибут',
);

/* объект для управления данными сущности */
$schema = new Schema($pdo, 'сущность');

/* Если мы используем в качестве быстрого источника данных
материализованное представление,
то нам необходимо его пересоздать
*/

/* пересоздадим материализованное представление */
$schema->setup($attribute3);
/* в материализованное представление добавиться новая колонка */
/* DTO атрибута - $attribute3, при работе через материализованное представление,
можно не передавать */

/* Если мы используем в качестве быстрого источника данных
таблицу,
то нам необходимо добавить колонку для нового атрибута
*/

/* Добавим колонку для нового атрибута */
$schema->setup($attribute3);
/* в таблице добавиться новая колонка */
/* DTO атрибута - $attribute3, при работе через таблицу,
лучше передать, тогда будет добавлена только одна колонка,
иначе будет пересоздана вся таблица */

/* Что бы в дальнейшем при добавление новых позиций в таблицу,
не было проблем из-за разного количества колонок в представлении и в таблице,
необходимо обновить представление.
Для этого надо поменять способ доступа к данным у объекта сущности 
и после этого обновить представление. */

/* изменяем способ доступа к данным сущности */
$schema->changeStorage('view');

/* пересоздаём представление */
$schema->setup();

/* Создадим значения для нового атрибута для всех ранее созданных моделей */
/* будет создана запись для Value - Значения - в EAV таблицах */
$operator->expandItem(
    'позиция_из_категории_в_каталоге',
    'дискретный_числовой_атрибут',
    '-1'
);
/* Если этого не сделать, то присвоить (или обновить) Значение для Атрибута
 не получиться, потому что фактически значение не было создано
 и в БД нет записи для обновления */

/* после этого надо вернуть способ доступа */
/* материализованное представление */
$schema->changeStorage('materialized view');
/* таблица */
$schema->changeStorage('table');

/* при этом пересоздавать или "освежать" источник данных не надо */

Добавить новую позицию

use Environment\Database\PdoConnection;
use AllThings\ControlPanel\Operator;
use AllThings\ControlPanel\Schema;

$pathParts = [__DIR__, 'configuration', 'pdo.env',];
$path = implode(DIRECTORY_SEPARATOR, $pathParts);
$pdo = (new PdoConnection($path))->get();

/* объект для работы с сущностями, атрибутами, значениями */
$operator = new Operator($pdo);

/* создаём новую позицию */
$thing = $operator->createItem(
    'сущность',
    'новая-позиция',
);

/* объект для управления данными сущности */
$schema = new Schema($pdo, 'сущность');

/* не зависимо от того
материализованное представление или
таблица,
выбраны в качестве способа доступа к данным сущности,
достаточно выполнить "освежение" источника данных */
$schema->refresh();
/* если способ доступа материализованное представление, то оно будет пересчитано
если способ доступа - таблица , то новая запись будет добавлена из представления,
поэтому важно, что бы в представлении и в таблице были одни и те же колонки */

Обновить значения атрибутов

use Environment\Database\PdoConnection;
use AllThings\ControlPanel\Operator;
use AllThings\ControlPanel\Schema;
use AllThings\DataAccess\Crossover\Crossover;

$pathParts = [__DIR__, 'configuration', 'pdo.env',];
$path = implode(DIRECTORY_SEPARATOR, $pathParts);
$pdo = (new PdoConnection($path))->get();

/* оператор для работы с сущностями, атрибутами, значениями */
$operator = new Operator($pdo);

/* Если способ доступа к данным сущности - материализованное представление,
то нам необходимо обновить данные в таблицах EAV.
Если способ доступа к данным - таблица,
то обновлять таблицы EAV не надо, они будут обновлены вместе с данными таблицы
*/

/* обновляем значение атрибута у конкретной позиции в таблицах EAV */
$operator->changeContent(
    'новая-позиция',
    'дискретный_числовой_атрибут',
    '0',
);

/* объект для управления данными сущности */
$schema = new Schema($pdo, 'сущность');

/* Если способ доступа к данным сущности - материализованное представление,
то его надо освежить */
$schema->refresh();

/* Если способ доступа к данным сущности - таблица,
то нам для того что бы освежить данные в таблице,
достаточно отдать массив новых значений,
для каждой позиции требуется отдельный вызов Schema::refresh(array $values = [])
*/

$value = 
    (new Crossover())
    ->setLeftValue('новая-позиция')
    ->setRightValue('дискретный_числовой_атрибут')
    ->setContent('0');

$schema->refresh([$value]);

Заключение

По большей части все операции выполняются на высоком уровне абстракции.

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

С помощью этой статьи вы легко освоитесь с использованием библиотеки "Универсальный каталог".

Пользуйтесь на здоровье :)

Комментарии (19)


  1. rPman
    08.01.2022 10:32
    +2

    Выбрал в голосовании воздержаться, хотя надо бы 'Кому это надо'?

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

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

    Помним, что DDL язык 'почти ничем не отличается' от DML в sql, те же строки в программе, их так же можно генерировать, за исключением отсутствия транзакций и некоторых проблем с репликацией. Если тебе из программы нужно создать какой то объект со своей структурой, то и создавай все необходимое прямо в таблицах — нужно новое поле — добавляй либо поле либо физически таблицу с FK (в зависимости от вида нагрузки и объемов данных), заложи логику в интерфейс и получишь то же самое но эффективнее на порядок и уж точно красивее и понятнее, а главное, вместе с этим получаешь возможность возложить контроль целостности над своими данными на sql сервер, эффективное использование индексов и просто работать с такой базой напрямую удобнее чем с кашей из данных в паре таблиц


    1. Ha0c
      08.01.2022 16:08
      +1

      Всегда нужно соблюдать баланс между универсальными структурами и строгой типизацией. И понимать на какие жертвы вы идете.


    1. rinat_crone
      08.01.2022 17:04

      Посмотрел бы я что вам ДБА ответит если вы предложите в рантайме по запросу пользователя менять схему БД. EAV — это неплохая концепция по своей сути для подобных задач, а если обвесить это еще сверху материализованными представлениями, то вообще неплохо смотрится и вполне себе эффективно для малых и средних проектов, коих большинство в интернете кмк.


      1. SbWereWolf Автор
        08.01.2022 17:31

        Посмотрел бы я что вам ДБА ответит если вы предложите в рантайме по запросу пользователя менять схему БД

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

        Мне кажется для большинства проектов в интернете, для товарного каталога, достаточно Битрикса или Magento / OpenCart.

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

        Я на днях пытался придумать где бы могла пригодиться эта библиотека - "Универсальный каталог" и особо не придумал. Возможно для каких то уникальных проектов, которым не нужна интеграция с бухгалтерией. Или там где людям нужно работать с произвольными данными, но лень заморачиваться на Битрикс.


        1. rpsv
          10.01.2022 07:41
          +1

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

          А откуда вы знаете насколько Битрикс медленее? В нем как раз таки и реализован EAV : b_iblock_element (сущности), b_iblock_property (атрибуты), b_iblock_property_element (значения).

          Запрос к категориям делается через 1 join, что мало похоже на "цать дополнительных запросов" :-)


          1. SbWereWolf Автор
            10.01.2022 15:40

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

            Я не говорю что это надо делать пользователю, это скрыто за API библиотеки Битрикса, но эти запросы выполняются, поэтому это будет медленей чем выполнить полтора запроса в случае с "Универсального каталога".


      1. rPman
        08.01.2022 17:44

        Где то в начале 200х-ых пилили проект (в забвении) по автоматической генерации целого веб-сервиса с формами просмотра, редактирования и поиска на основе описания структуры (тогда еще на основе xml и xslt, оно 'только появилось' и так красиво выглядело, сейчас бы ни за какие коврижки таким садамаза не занимался).

        Генерировалось все — sql код создания (и модификации, но до удаления не дошли, только добавление), php код форм, включая поисковые, поддерживались не только плоские атрибуты с типами (в т.ч. справочники) но и списки М-к-М и даже был задел на более сложные под-формы через html frame но не пришлось… что то похожее шло с oracle, но дико завязанное на их подсистему (дорогую и проприетарную) а тут любая база данных и красивый опенсорс, ведь код можно было включать в свой вручную написанный со своим дизайном.

        Это работало но ушло в забвении по независящим от технологий причинам


    1. SbWereWolf Автор
      08.01.2022 17:48

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

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

      Шаблонный SQL код по работе с данными (DML) и генерации схемы данных (DDL) спрятан в методы, ни какая ORM не применяется. Библиотека это только proof of concept, мне было интересно посмотреть на сколько трудно будет автоматизировать DDL и какое быстродействие будет в итоге.

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

      В итоге как оказалось, генерация DDL это совсем не трудно, и то что получилось работает достаточно быстро. Я не вижу разницы по быстродейтсвию между работой с таблицей сгенерировнной библиотекой и таблицей сгенерированной "миграцией". Если разница и есть, то она не заметна на фоне того сколько времени СУБД выполняет запрос к данным. Плюс минус миллесекунда не имеет значения, когда у нас сами данные вычитываются 20-40 мс.


    1. FanatPHP
      10.01.2022 11:50

      Мне кажется, вы не поняли смысл этой библиотеки, и считаете её "фреймворком поверх базы данных". Хотя это фреймворк поверх модели EAV, которая как раз-таки при работе с ней вручную совершенно неюзабельна. Не говоря уже о том что в контексте области применения EАV рекомендация "нужно новое поле — добавляй" выглядит довольно глупо. Ну или я бы хотел посмотреть на пример такой БД, которая хранит свойства товаров для классического ассортимента с телефонами, телевизорами, чайниками и флешками не в "паре таблиц", а следуя принципу "нужно поле — добавь его".


      1. SbWereWolf Автор
        10.01.2022 15:44

        Битрик так делает. Надо поле в карточке товара ? Добавь его ! До какой то версии всё это хранилось в одной большой таблице, гоад тр иточно как можно отдельные категории хранить в отдельных таблицах.

        На сколько ни будь существенных объёмах без кеширования тормозит. Но у крупных интернет магазинов хватает экспертизы добиться от Битрикса хорошей производительности.

        Работать с Битриксом конечно не сахар.


  1. abratko
    08.01.2022 17:26

    Основня задача каталога со стороны покупателя - это быстрый фасетный и полнотекстовый поиск. Как каталог хранит эти данные, покупателю наплевать. С этой точки зрения в статье нет никакой полезной информации.
    Сейчас этот код и метрики из предыдущей статьи не говорят ничего.

    Если Вы построите запрос на поиск по 5 атрибутам и произвольному тексту одновременно и сраните результаты с аналогиными запросами в Эластике и Сфинксе, то тогда статья будет полезной.

    Но что-то мне подсказывет, что после этого, Вы больше не будете разрабытывать библиотеку.


    1. SbWereWolf Автор
      08.01.2022 23:57
      +1

      У меня не было цели сделать data access layer для eCommerce платформы. Моей целью было посмотреть как можно ускорить EAV.

      Мои выводы:

      1. ускорить можно

      2. ускорить не сложно

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

      На мой вкус результаты позитивные.

      Полнотектовый поиск с опечатками Эластика или Сфинкса я не собираюсь повторять а уж тем более превосходить.

      По части поиска по атрибутам, я всеми руками и ногами только за. Напишите бенчмарк на NoSQL или jsonb, я подготовлю данные, получим метрики, будет о чём поговорить и о чём подумать.

      У меня все каникулы ушли на то что бы написать бенчмарк для EAV. Я считаю что я свою часть работы сделал. Если хотите свои слова подтвердить делом, то пожалуйста, у вас есть такая возможность.

      Буду очень рад.


  1. piton_nsk
    08.01.2022 21:31
    +3

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

    P.S.

    Где реальные запросы к БД?


    1. SbWereWolf Автор
      08.01.2022 23:43

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

      Прототипы запросов можно посмореть в статьях: https://habr.com/ru/post/323498/ https://habr.com/ru/post/343776/ . В действительности запросы попроще на пару джоинов.

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

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


  1. Ha0c
    08.01.2022 22:54

    Данные технологии были актуальные лет 5 назад. Сейчас в СУБД можно использовать колонки не ограниченой длинны плюс позволяют навешивать на них идексы. Дальше выбор на вкус JSON, XML, AVRO и т.п. Как показывает практика СУБД надо подбирать под данные. Подобные инструменты хороши для молодых команд на начальном этапе реализовать идею не погружаясь в подробности. 1с так живет и развивается.


  1. dopusteam
    09.01.2022 10:51
    +1

    Меня смущает, что, судя по всему, все сущности привязываются по строковому значению. Ну вот пример

    $attribute3 = $operator->createKind(
        'дискретный_числовой_атрибут',
        'number',
        'discrete',
    );
    
    /* зададим новый атрибут для сущности */
    $operator->attachKind(
        'сущность',
        'дискретный_числовой_атрибут',
    );
    
    /* объект для управления данными сущности */
    $schema = new Schema($pdo, 'сущность');

    Почему, например, attachKind не принимает вместо строковых значений две сущности, в духе

    /* зададим новый атрибут для сущности */
    $operator->attachKind(
        entity,
        attribute3
    );

    А сейчас, получается, что createKind возвращает объект, который нигде дальше не используется.

    Ну и вдогонку, чтоб IDE помогала, не лучше ли сделать addAttribute(Entity, Attribute) вместо attachKind(string, string)?
    И так по всему коду, завязка на строки. Можете сказать, почему так?

    И ещё вот такая штука смутила

    $wordsFilter = new DiscreteFilter(
        'дискретный_символьный_атрибут',
        ['красный']
    );

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

    Почему бы не сделать фильтр вида new DiscreteFilter(['красный'])?


    1. SbWereWolf Автор
      09.01.2022 12:38

      Спасибо за вопросы.

      Библиотека это наследние разработки 2016 года.

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

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

      Поэтому в коде где то используются DTO, где то строки. Просто ещё не успел причесать код.

      Сейчас я считаю, что DTO это представление данных для внутренней работы библиотеки и на высоком уровне, для внешних интерфейсов нужны строки.

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

      Про нейминг, вы вопроса не задали, но я отвечу.

      На каждом уровне абстракции свой язык. Атрибуты это низкий уровень, на высоком уровне - характеристики. Характеристика слово длинное, поэтому используется короткий аналог - kind.

      Аналогично с глаголами, на каждом уровне свой: insert для уровня sql; combine скомбинировать для уровня хэлпера / attach - стыковать для верхнего уровня внешнего интерфейса.

      Про фильтры.

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

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

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

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

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


    1. SbWereWolf Автор
      09.01.2022 15:47

      Кстати, результат создания нового атрибута $attribute3 = $operator::createKind(..); может быть использован для обновления источника данных: $schema->setup($attribute3);

      / создаём новый атрибут */
      $attribute3 = $operator->createKind(
          'дискретный_числовой_атрибут',
          'number',
          'discrete',
      );
      
      /* зададим новый атрибут для сущности */
      $operator->attachKind(
          'сущность',
          'дискретный_числовой_атрибут',
      );
      
      /* Добавим колонку для нового атрибута */
      $schema->setup($attribute3);
      /* в таблице добавиться новая колонка */

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


  1. CyaN
    10.01.2022 11:24

    Пользоваться - а зачем, когда есть готовые MDM системы? Каталог - это не только информация о сущностях и их атрибутах, но и общирная функциональность, необходимая для управления, включая процессы, хранение истории, DQ, etc.