Всем привет. У меня было несколько публикаций по теме Entity Attribute Value, сокращенно EAV (паттерн программирования для хранения произвольных данных). За прошедшее время я допилил библиотеку для работы с EAV, и хотел бы поделиться с вами своими наработками. В библиотеке реализован базовый сценарий: Создать сущность Создать атрибут Задать для сущности определённые атрибуты Задать значение для атрибутов (создать предмет) Сформировать хранилище для предметов в формате представления, материализованного представления, таблицы Вычислить значения фильтров для поиска по предметам Выполнить поиск по предметам Добавить новый предмет Добавить новый атрибут для сущности Изменить значение атрибута (изменить характеристику предмета) Изменения данных в пунктах 8-10, автоматически отражаются в хранилище данных не зависимо от формата выбранного в пункте 5. Если кто то чувствует в себе интерес к этой теме, то предлагаю присоединиться к разработке. Если вам хочется получить опыт в программировании чуть больше чем очередной HellowWord, то для вас тоже можно придумать интересный челендж. Для тех кто не в курсе, EAV это когда нам не надо в ручную проектировать и создавать новую таблицу в БД приложения для хранения информации о новой бизнес сущности, потому что у нас уже есть три таблицы и нам этого хватает для всего.  В моей реализации шесть таблиц. Если хочется хранить данные не только как строки, то тогда это будет семь таблиц и плюс по одной таблице на каждый тип данных, но не в этом суть статьи, речь не столько о реализации, сколько о proof of concept , который можно потрогать руками. Предыдущие публикации на эту тему: Идеальный каталог, набросок архитектуры Идеальный каталог, вариант реализации Идеальный каталог, оптимизация выборки данных Для работы библиотеки потребуется СУБД PostgreSQL9+ и PHP7.4+ (в теории код можно упростить до PHP5, но тогда не будет type hitting, что не камильфо) Скачать код из репозитория storage-for-all-things Развернуть СУБД с помощью sql скрипта из файла configuration/install-tables-and-roles.sql.example Настроить соединение с СУБД - отредактировать файл configuration/db_test.php.example Переименовать configuration/db_test.php.example в configuration/db_test.php Выполнить тест в файле tests/Integration/AutomatedProcessTest.php Если все тесты прошли успешно, значит вы всё настроили без ошибок. Если какие то тесты упали, то значит мне надо править баги :) Буду благодарен за ваши баг репорты. Как использовать библиотеку вам будет понятно из кода и комментариев теста AutomatedProcessTest. Наружу из библиотеки торчит три ручки, три класса: Operator AllThings\ControlPanel\Operator Browser AllThings\ControlPanel\Browser Schema AllThings\ControlPanel\Schema  Класс Operator работает непосредственно с данными.  Класс Browser позволяет просматривать данные.  Класс Schema создаёт объект СУБД для хранения данных и для сохранения согласованности данных (обновления по необходимости). О какой согласованности речь ? Собственно данные хранятся в трёх таблицах, но для удобства доступа к данным их можно хранить в трёх разных формах: представление, материализованное представление или таблица. Отсюда возникает задача согласования данных в исходных трёх таблицах и в той форме которая была выбран для хранения. Если, с представлением, синхронизация происходит автоматически на уровне СУБД, то с материализованным представлением и таблицей, необходимы дополнительные действия. За их выполнение отвечает класс Schema. Этот класс позволяет: 1 Создать сущность (чертёж для создания предметов) 2 Создать атрибут (характеристика предмета) 3 Для сущности добавить атрибут 4 Создать предмет 5 Задать значение для атрибута (задать значение для характеристики предмета) 6 Добавить предмету новый атрибут (задать значение для новой характеристики предмета) Используя эти шесть функций, мы можем вести базу данных нашего Универсального каталога, базу данных предметов. Но когда мы затевали наш Универсальный каталог, нашей задаче было не только создание базы данных с характеристиками предметов, прежде всего мы хотим наши данные отдавать на просмотр, в этом нам поможет класс Browser. Вычислить параметры поиска Выполнить поиск по заданным параметрам 1 Получить фильтры для выполнения поиска Фильтры вернуться в формате массива, который придётся разбирать руками, ни каких DTO для облегчения понимания не было сделано, извините, такие издержки у proof of concept. 2 Выполнить поиск по заданным параметрам Данные вернуться как массив, что делать с этим массивом пусть решает вызывающий код. Фильтры передаются как массив экземпляров классов AllThings\SearchEngine\ContinuousFilter и AllThings\SearchEngine\DiscreteFilter, это ещё два класса которые торчат из библиотеки, можно было конечно обойтись массивом со своей системой индексов, но в этом случае я не поленился и сделал DTO. Как задать параметры фильтрации можно посмотреть в тесте tests/Integration/AutomatedProcessTest.php. Я рассказал о том как задать данные и как их выгрузить, теперь мне осталось рассказать о том как данные быстро выдать и как данные быстро изменить. Всё это задачи класса Schema. Основное назначение этого класса, это: Создать на основе сущности (на основе атрибутов) объект СУБД для хранения значений Обновить значение в объекте СУБД при изменении значения атрибута сущности (конкретного предмета) 1 Создать хранилище для значений атрибутов Что будет сделано ?  Будет проверено какая форма был выбрана для хранения данных, это может быть: Storable::DIRECT_READING - view  Storable::RAPID_OBTAINMENT - materialized view Storable::RAPID_RECORDING - table Далее будет создан соответствующий объект СУБД, и он будет заполнен данными в соответствии с тремя базовыми таблицами. 2 Обновить значение при изменении этого значения в базовой таблице Тут мы видим ещё один класс, который просочился наружу из нашей библиотеки.  ICrossover это интерфейс который хранит значение атрибута для конкретного предмета. Что произойдёт при вызове этого метода ? будет определена форма хранения данных и эти данные будет соответствующим способом обновлены. Если мы хотим что бы было обновлено только это значение, то в ICrossover следует передать новое значение атрибута, если значение не передать, то хранилище значений будет обновлено полностью. Последний нюанс о котором надо обязательно упомянуть, это то как мы сообщаем системе о том в какой форме мы хотим хранить наши данные для быстрого доступа. 3 Задать форму хранения данных Как было сказано выше, формой может быть одна из следующих: Storable::DIRECT_READING - view  Storable::RAPID_OBTAINMENT - materialized view Storable::RAPID_RECORDING - table В статье Идеальный каталог, оптимизация выборки данных я приводил показатели быстродействия по всем формам. К этому можно добавить, то что форма представления и материализованного представления возможна только для хранения данных внутри одной БД. В то время как форма таблицы делает возможным хранение данных в отдельной СУБД. Сейчас конечно библиотека не даёт такой возможности, но это только proof of concept, который можно развить как угодно. Не будет замеров. Конечно без них статья сильно теряет в ценности. Но что бы написать тесты с профилированием потребуется один полноценный рабочий день и прямо сейчас я не обладаю такой роскошью. Откладывать публикацию очень не хочется и так вся эта история растянулась на три года :) Как вы могли заметить в статье речь идёт только о добавлении данных: добавить сущность, добавить атрибут, добавить значение, ни разу не было речи о том что бы что то удалить. Удаление на самом деле не реализовано, сделать не долго, но для proof of concept не требуется. Для использования в работе в библиотеке много не хватает, поэтому я для себя не вижу смысла доводить её до ума. Если кому то захочется, то пожалуйста, давайте сделаем. Мне для самого себя это не нужно. Мои контакты у меня в профиле и в репозитории. В будущем возможно соберусь и подробней распишу использование библиотеки с конкретным примером, или напишу тесты и померяю время выполнения операций.  Зависит конечно от реакции и выбора сообщества. Спасибо за внимание. программист эникейщик