Идеальный каталог, пример использования

от автора

Я разрабатываю библиотеку для работы с 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();  /* после этого надо вернуть способ доступа */ /* материализованное представление */ $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, с созданием других объектов, то останется от двух до пяти строк действительно работающего кода.

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

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Будете пользоваться?
3.33% Можно попробовать 1
20% Возможность работы с произвольными данными? кому это надо? 6
30% NoSQL лучше (можно в комментариях не уточнять чем) 9
46.67% PostgreSQL + JSONB решение всех проблем 14
Проголосовали 30 пользователей. Воздержались 22 пользователя.

ссылка на оригинал статьи https://habr.com/ru/post/599639/


Комментарии

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

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