toxaw/array-builder

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

This package is auto-updated.

Last update: 2024-09-23 21:05:23 UTC


README

数组嵌套构造器。

使用手册

解决的问题

我们需要形成响应,用于计划的结构如下:[部门] 中有 [用户] 拥有 [月计划],其中包含 [休假] 和 [休息日] 参数。每个 [计划日] 有 [分配的任务] 和 [任务时间标记]。在 [分配的任务] 中存在对应 [任务预算] 的参数。

一个正常的例子是将查询构建到数据库中,使用 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([别名名称]) - 初始化容器(Container对象),该容器绑定到地图的嵌套对象,接受嵌套对象的别名名称,返回创建的Container对象;
    • fill([数组]) - 向容器填充数组,接受数组(如果Element中设置了one(),则返回单个数组,否则返回一组相同类型的数组),返回Container对象;
    • setName([数组键名]) - 在父数组中设置要嵌套的数组(数组)的键,接受数组(数组)的键名,返回Container对象;
    • addUnsetField([数组键名]) - 设置在编译后应消失的数组键,接受数组键名,返回Container对象;
    • setSaveKeys() - 设置选项,表示在从数组集合中提取时保存键值,无参数,返回Container对象;
    • addCallable([可调用函数]) - 设置回调函数,函数参数中首先传入当前元素的引用,其次是一个包含两个数组的数组[merged] - 以键形式表示的父元素集合和[modified] - 与merged类似,但modified中传入的是父元素的引用,而merged中传入的是基于$element(更改后的父元素)和$elementOrigin(原始父元素)创建的父元素。回调函数应返回true或false。返回Container对象;
    • addNode() - 根据其父元素初始化嵌套数组的绑定,无参数,返回创建的Node对象;
      • setForeign([外键名]) - 设置通过哪个元素键进行绑定,接受键名,返回Node对象;
      • unsetForeign() - 与addUnsetField()类似,但使用的是已设置的外键元素,无参数,返回Node对象;
      • setParent([父别名名称]) - 设置要绑定的父嵌套对象,接受父嵌套对象的名称,返回Node对象;
      • setPrimary([父主键名]) - 设置在父元素(通过setParent()确定)中的键,通过该键进行绑定,接受键名,返回Node对象;
      • unsetPrimary() - 与unsetForeign()类似,但针对父元素,无参数,返回Node对象;
      • condition() - 初始化用于检查绑定的条件,无参数,返回创建的Condition对象。默认情况下,Condition对象在addNode()方法阶段初始化;
        • all() - 设置始终为真条件,无参数,返回Node对象。设置此条件时,无需设置setForeign()、setParent()和setPrimary();
        • equally() - 设置条件,当外键和主键的值相同时(使用严格比较)为真。初始化Condition对象时,默认设置此条件。无参数,返回Node对象;
        • primaryInForeign([严格true/false]) - 设置条件,当主键的值包含在元素外键的值集合中时为真,接受一个参数,指定是严格比较(默认)还是非严格比较,返回Node对象;
        • foreignInPrimary([严格true/false]) - 与primaryInForeign()类似,但将foreign和primary对调,接受一个参数,指定是严格比较(默认)还是非严格比较,返回Node对象;
        • callable([可调用函数]) - 通过使用回调函数设置自定义条件,函数参数中首先传入外键的值,其次传入主键的值,函数结果应返回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,或无返回值)。

备注...

  • 备注:计划修复糟糕的代码并扩展这个项目以支持集合和对象;
  • 备注:强烈请求对此作品的构造性批评,并想知道我的这个“自行车”做得如何(寻找类似物 - 没找到)。