toxaw / array-builder
Requires
- php: >=7.1
This package is auto-updated.
Last update: 2025-05-23 22:52:52 UTC
README
Конструктор вложенностей массивов.
Manual for used
Какую решает задачу
Нам нужно сформировать response для плана в следующей структуры: [отделы] в них [юзеры] у которых есть [план на месяц] в котором есть параметр отвечающий за [отпуск] и [выходной день]. Каждом [дне плана] есть [поставленные задачи] и [отметки времени по задачам]. В [поставленных задачах] есть параметры, отвечающие за [бюджет задачи].
Один из нормальных вариантов - это сбилдить запрос в базу в 5-7 джойнами к другим сущностям. Да, уже не плохо, но больно.
А минусы следующие:
- Много данных, джойны только ухудшают работу;
- Костылякать дикие и непонятные foreach`и;
- Как мы прикрутим в структуру результат сущности, которой нет([дни месяца] - виртуальная сущность)?
Чтобы решить проблему с многоджойнстом, мы разбиваем обращение к каждой сущности на каждый запрос, в котором передающтся результаты другой, других сущностей, т.е.:
- выборка отделов;
- выборка юзеров по отделам;
- foreach`ек на все дни месяца;
- выборка отпусков по по границам месяца;
- выборка выходных дней по граница месяца;
- выборка задач по юзерам и по граница месяца;
- выборка отметов времени задач по юзерам и по граница месяца.
Всё, многоджойнство декомпозировано.
Дальше пишем монолит из foreach`ей и всяких условий и непонтым кодом, в котором легко запутаться и все сломать.
К сожалению, данный этап исключается из цепочки и в дело вступает [Конструктор вложенностей], пример (код не полностью дописан):
// Набор подготовленных сущностей
$department = new Department();
$user = new User(null, $department);
$absence = new Absence(null, $user, $timeRange);
$holiday = new PlanHoliday(null, $timeRange);
// Карта response
$map = new Element('deps');
$map->addChild('users')->notEmpty()->addChild('planTimeMonth')->notEmpty()->inMerged();
$map->getChild('users')->addChild('plan')->notEmpty()->addChild('holiday')->inMerged();
$map->getChild('users')->getChild('plan')->addChild('absence')->inMerged();
// Просмотр карты визуально
//VisualizerMap::visuald($map);
// Навешивание сущностей и привязок + щепотка магий
$compiler = new Compiler($map);
// Заполняем отделы
$compiler->element('deps')->fill($department->getByIds());
// Заполняем юзеров
$compiler->element('users')->fill($user->getByIds())->addUnsetField('time')
->addNode()
->setForeign('departmentId')->unsetForeign()
->setParent('deps')->setPrimary('departmentId')
->condition()->primaryInForeign();
// Заполняем время месячный плана
$compiler->element('planTimeMonth')->fill($planYear->getByIds())
->addNode()
->condition()->all();
// Заполняем месячный план
$compiler->element('plan')->fill($timeRange->getRange())->addUnsetField('dateTime')
->addCallable(static function (&$element, $parent) {
$element['time'] = $parent['merged']['users']['time']*60;
return true;
});
// Заполняем выходные дни
$compiler->element('holiday')->fill($holiday->getByIds())
->addNode()
->setForeign('dateStart')
->setParent('plan')->setPrimary('dateTime')
->condition()->callable(static function ($dateStart, $dateTime) {
if ($dateStart->getTimestamp() <= $dateTime->getTimestamp()) {
return true;
}
})
->tie()
->addNode()
->setForeign('dateFinish')
->setParent('plan')->setPrimary('dateTime')
->condition()->callable(static function ($dateFinish, $dateTime) {
if ($dateFinish->getTimestamp() >= $dateTime->getTimestamp()) {
return true;
}
})
->tie()
->addCallable(static function (&$element) {
$element = [];
$element['isDayOff'] = true;
return true;
});
// Заполняем отпуска
$compiler->element('absence')->fill($absence->getByIds())
->addNode()
->setForeign('userId')
->setParent('users')->setPrimary('userId')
->tie()
->addNode()
->setForeign('dateStart')
->setParent('plan')->setPrimary('dateTime')
->condition()->callable(static function ($dateStart, $dateTime) {
if ($dateStart <= $dateTime) {
return true;
}
})
->tie()
->addNode()
->setForeign('dateFinish')
->setParent('plan')->setPrimary('dateTime')
->condition()->callable(static function ($dateFinish, $dateTime) {
if ($dateFinish >= $dateTime) {
return true;
}
})
->tie()
->addCallable(static function (&$element) {
$element = [];
$element['isVacation'] = true;
return true;
});
// Смотрим результат
echo '<pre>';
die(print_r($compiler->compile()));
echo '</pre>';
// А тут его отдаем ответом
return response()->json(
$compiler->compile(false)
);
На данном этапе все понятно как строятся привязки.
Как курить конструктор
Билдер карты
Класс Element
При иницилизации объекта принимает параметром алиас корня.
Пример: $map = new Element([root alias name]);
Методы:
- addChild([alias name]) - Добавляет объект вложенности, в параметрах алиас, возвращает новый Element;
- getChild([alias name]) - Возвращает объект вложенности, в параметрах алиас, возвращает Element;
- getParent() - Возвращает родительский объект вложенности, без параметров, возвращает Element;
- one() - Указывает, что текущий объект вложенности не является набором, без параметров, возвращает текущий Element;
- notEmpty() - Указывает, что текущий объект вложенности не может быть пустым, без параметров, возвращает текущий Element;
- inMerged() - Указывает, что текущий объект вложенности будет добавлен в родительский объект вложенности(примержен(merge)), без параметров, возвращает объект Merged, по умолчанию будет примержен первый элемент в наборе;
- setGroupField() - Указывает, то что поля элементов влженности будут сгрупированы, в виде массива, без параметров, ничего не возвращает;
- setGroupFieldIfMore() - Указывает, то что поля элементов влженности будут сгрупированы, в виде массива, если количество элементов будет больше одного, без параметров, ничего не возвращает.
Правила постройки карты:
- Имя алиаса объекта вложенности должно не совпадать с именами родителей;
- У наследников одного родителя имена алиасов объектов вложенности должны быть уникальными;
- Объект вложенности с установленной опцией inMerged() не может иметь наследников.
Визуализатор карты
Класс VisualizerMap
VisualizerMap имеет статические методы:
- visual([map object]) - Визуально показывает как построена карта, в параметрах передается объект корневого Element;
- visuald([map object]) - visual() + die().
Компилятор
Класс Compiler
При иницилизации объекта принимает параметром объект карты (Element).
Пример: $compiler = new Compiler($map);
Компилятор состоит из контейнеров (объекты класса Container, иницилизируются методом element([alias name])).
Контейнеры в себе содержат массив (заполняются методом fill([array])).
Также контейнеры содержат наборы привязок (объекты класса Node, иницилизируются методом addNode()) и/или содержат кастомные условия и действия, которые иницилизируются методом addCallable([callable function]).
Привязка содержит выставленные внешний ключ, родитель с его внутренним ключом и тип условия (объект класса Condition, иницилизируется методом condition()).
Методы Compiler
- element([alias name]) - Иницилизация контейнера (объект Container), который закрепляется к объекту вложенности карты, принимает имя алиаса вложенности объекта, возрващает созданный объект Container;
- fill([array]) - Заполняет в контейнер массив, принимает массив ((array), если установлен one() в Element, то единичный массив, иначе набор однородных массивов), возрващает объект Container;
- setName([key name for array]) - Задает ключ в родительском массиве в котором будет вложен(ы) массив(ы), принимает имя ключа для массива (массивов), возрващает объект Container;
- addUnsetField([key name]) - Задает ключ из массива, который должен изчезнуть после компиляции, принимает имя ключа из массива, возрващает объект Container;
- setSaveKeys() - Задает опцию, которая означает, что при при выборке из набора массивов, сохранялись значения ключей, без параметров, возрващает объект Container;
- addCallable([callable function]) - Задает функцию обратного вызова, в параметрах функции первым передается ссылка на текущий элемент , вторым массив состоящий из двух массивов [merged] - набор родителских элементов в виде массива ключей и [modified] - анологично с merged, разница в том, что в modified передаются ссылки родительских измененных элементов, а в merged передаются родительские элементы, созданные на основе $item - измененный родительский элемент + $itemOrigin - изначальный родительский элемент. Функция обратного вызова должа вернуть true или false. Возрващает объект Container;
- addNode() - Иницилизирует привязку вложенного массива по его родителям, без параметров, возрващает созданный объект Node;
- setForeign([foreign key name]) - Устанавливает ключ из элемента по которому будет осуществляться привязка, принимает имя ключа, возрващает объект Node;
- unsetForeign() - Анологично с addUnsetField(), только ключом будет установленный foreign элемента, без параметров, возрващает объект Node;
- setParent([parent alias name]) - Задает родительский объект вложенности по которому будет осуществляться привязка, принимает имя родительского объекта вложенности, возрващает объект Node;
- setPrimary([parent primary key name]) - Устанавливает ключ из родитеского элемента (родительский элемент определяется по setParent()) по которому будет осуществляться привязка, принимает имя ключа, возрващает объект Node;
- unsetPrimary() - Анологично с unsetForeign(), только для родительского элемента, без параметров, возрващает объект Node;
- condition() - Иницилизирует условие по которому будет проверяться привязка, без параметров, возрващает созданный объект Condition. Объект Condition по умолчанию инцилизируется на этапе метода addNode();
- all() - Устанавливает всегда истинное условие, без параметров, возрващает объект Node. При установки данного условия нет смысла устанавливать setForeign(), setParent(), setPrimary();
- equally() - Устанавливает условие, которое будет истинно в том случает, если значение под ключом foreign и primary одинаково (используется строгое сравнение). При иницилизации объекта Condition, данное условие устоваливается по умолчанию. Без параметров, возрващает объект Node;
- primaryInForeign([strict true/false]) - Устанавливает условие, которое будет истинно в том случает, если значение под ключом primary содержится в наборе значений елемента под ключом foreign, принимает параметр который указывает на строгое (по умолчанию) или нестрогое сравнение, возрващает объект Node;
- foreignInPrimary([strict true/false]) - Анологично с primaryInForeign(), только foreign в primary, принимает параметр который указывает на строгое (по умолчанию) или нестрогое сравнение, возрващает объект Node;
- callable([callable function]) - Задает кастомное условие через использование функции обратного вызова, в параметрах этой функции первым передается значение под ключом foreign , вторым значение под ключом primary, результат функции должен вернуть true - при котором условие будет истинный или false. В параметрах метода принимается функция, и метод возрващает объект Node;
- tie() - Логически завершает построение привязки (объект Node) и иницилизирует новую привязку, без параметров, возрващает созданный объект Node;
- compile([verification true/false]) - Финальный метод, который запускает компилятор, в параметрав принимает проверку правильности постороения контейнеров и привязок (по умолчанию true - проверка включена). Метод возвращает скомпилировнный массив (при опции one()) или набор массивов.
Правила заполнения контейнеров (Container):
- Корневой контейнер не может иметь то что устанавливает метод setName();
- Корневой контейнер не может иметь привязок (набор объектов Node);
- Все контейнеры должны быть заполнены массивами;
- Все контейнеры, кроме корневого, должны иметь хотя-бы одну из привязок (метод addNode()) или хотя-бы одну установленную функцию обратного вызова (метод addCallable());
- Каждому объекту вложенности из карты должен быть прикреплен (иницилизирован) один контейнер;
- При иницилизации контейнера, нужно передавать существующее имя алиаса объекта вложенности из карты.
Правила растановки привязок (Node):
Если не используется условие condition()->all(), то:
- Обязательно должен быть указан существующий ключ foreign (метод setForeign());
- Обязательно должен быть указан существующий родительский объект вложенности (метод setParent());
- Обязательно должен быть указан существующий ключ primary (метод setPrimary()).
Правила исполнения условий:
Заполнение произойдет только в том случае, если:
- Все привязки истинны, при методе callable() обратная фунция вернет true;
- Все иницилизированные функции обратного вызова (метод контейнера addCallable()) вернет true;
- При использовании метода addCallable() в функции обратного вызова, при внесении изменений в modified -> родительского эелемента, изменение в любом случае произойдут независимо от того что вернет функция обратного вызова (true/false, либо ничего).
p.s...
- p.s. в планах исправить говнокод и расширить сие детище для коллекций и объектов;
- p.s.s. очень прошу конструктивной критики на сие творение, и хочется узнать насколько у меня это получился велосипед (искал аналоги - не нашёл).