toxaw/array-builder

v1.0.0 2020-03-23 11:28 UTC

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. очень прошу конструктивной критики на сие творение, и хочется узнать насколько у меня это получился велосипед (искал аналоги - не нашёл).