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

Я разрабатываю библиотеку для работы с 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();
/* в материализованное представление добавиться новая колонка */ /* Если мы используем в качестве быстрого источника данных
таблицу,
то нам необходимо добавить колонку для нового атрибута
*/ /* Добавим колонку для нового атрибута */
$schema->setup($attribute3);
/* в таблице добавиться новая колонка */ /* Что бы в дальнейшем при добавление новых позиций в таблицу,
не было проблем из-за разного количества колонок в представлении и в таблице,
необходимо обновить представление.
Для этого надо поменять способ доступа к данным у объекта сущности и после этого обновить представление. */ /* создаём менеджер для управления свойства объекта сущности */
$manager = new EssenceManager( 'сущность', 'essence', $pdo,
); /* создаём объект свойств сущности */
$essenceEntity = (Essence::GetDefaultEssence());
$essenceEntity->setCode('сущность');
$essenceEntity->setStorageKind('view'); /* задаём объект с новыми свойствами объекта сущности */
$manager->setSubject($essenceEntity); /* обновляем БД с новыми свойствами */
$manager->correct('сущность'); /* пересоздаём представление */
$schema->setup(); /* после этого надо вернуть способ доступа на
материализованное представление или
таблицу,
при этом пересоздавать или "освежать" источник данных не надо */

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

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, с созданием других объектов, то останется от двух до пяти строк действительно работающего кода.

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

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

Читайте так же: