【问题标题】:How to validate multiple entities against themselves as well as against existing rows in the database?如何针对自身以及数据库中的现有行验证多个实体?
【发布时间】:2021-03-28 06:38:19
【问题描述】:

我有一个应用程序,我需要验证工作时间以确保它们在 CakePHP 3 中不重叠。所有记录都可以在一个表单中更改。数据看起来有点像这样:

id day opening_time closing_time
1 1 08:00:00 13:00:00
2 1 16:00:00 22:00:00

现在,当我将第一个 opening_time 更改为 17:00 时,这将是无效的,因为它会与第二行重叠。但是当我以相同的形式将第二个 opening_time 更改为 18:00 时,它应该是有效的。

我尝试使用 buildRules:

public function buildRules(RulesChecker $rules)
{
    $rules->add($rules->existsIn(['store_id'], 'Stores'));

    $rules->add(
        function (BusinessHour $entity, array $options): bool {
            $conditions = [
                'id !=' => $entity->id,
                'store_id' => $entity->store_id,
                'day' => $entity->day,
                'OR' => [
                    [
                        'opening_time <=' => $entity->opening_time,
                        'closing_time >=' => $entity->opening_time,
                    ],
                    [
                        'opening_time <=' => $entity->closing_time,
                        'closing_time >=' => $entity->closing_time,
                    ],
                    [
                        'opening_time >=' => $entity->opening_time,
                        'opening_time <=' => $entity->closing_time,
                    ],
                    [
                        'closing_time >=' => $entity->opening_time,
                        'closing_time <=' => $entity->closing_time,
                    ]
                ]
            ];

            return !$options['repository']->exists($conditions);
        },
        'overlapping',
        [
            'errorField' => 'opening_time',
            'message' => __('Business hours may not overlap.'),
        ]
    );

    return $rules;
}

但它会根据数据库中的数据检查第一条记录并标记为无效,即使第二行的更改使其有效。例如,当数据库中的数据如上所述并且我有以下发布数据时,它应该是有效的,但不是。

$data['business_hours'] = [
    (int) 0 => [
        'day' => '0',
        'opening_time' => [
            'hour' => '16',
            'minute' => '30'
        ],
        'closing_time' => [
            'hour' => '17',
            'minute' => '00'
        ]
    ],
    (int) 1 => [
        'day' => '0',
        'opening_time' => [
            'hour' => '18',
            'minute' => '00'
        ],
        'closing_time' => [
            'hour' => '20',
            'minute' => '00'
        ]
    ],
];

我应该如何处理这个问题?

【问题讨论】:

  • 那么你的代码有什么问题?你的问题并没有真正指向任何具体的东西。或者更具体地说,您的问题是查询没有获得正确的结果,还是之后发生的事情,即“标记无效”?或者两者兼而有之?
  • @ndm 谢谢指出。我试图澄清我的问题。查询得到了正确的结果,但它会根据数据库检查单个条目,并且不会考虑其他更改。

标签: php validation cakephp orm cakephp-3.x


【解决方案1】:

从逻辑上讲,您一次只能保存一个实体,不仅在应用程序级别,而且在 DBMS 级别,因此规则告诉您数据无效是正确的,因为它一次只验证一个实体.

我认为您需要某种状态来处理这个问题,或者您需要混合验证和应用程序规则的用途,即您需要验证无状态数据和有状态数据/persisted 数据,不仅非持久化数据集中的所有小时都需要彼此有效,它们还需要相对于数据库中已经持久化的数据有效。

我想有很多空间可以讨论如何最好地解决这个问题,应用程序规则中还没有允许验证多个实体的接口,所以无论你解决问题,它可能是某种妥协/解决方法之一方式或其他方式。

由于这最终是关于数据库中的数据,所有验证都应该在事务中进行,所以我猜最好将内容保留在应用程序规则中。我能想到的一种肮脏方式是将所有实体的 ID 传递到保存过程中,并在您的规则中排除所有尚未检查(因此保存)尚未的 ID,即后续检查将针对已经持久化的先前实体运行的方式,这意味着最终您将检查所有记录的有效性。

这是一个例子 - 我不知道你的关联设置,所以我假设你正在保存 Stores 和关联的 BusinessHours,但确切的设置并不重要,但它应该只是展示如何构建和传递自定义选项:

// ...
$store = $this->Stores->patchEntity($store, $this->request->getData());
$businessHourIds = new \ArrayObject(
    collection($store->business_hours)
        ->extract('id')
        ->filter()
        ->indexBy(function ($value) {
            return $value;
        })
        ->toArray()
);
// $businessHourIds would represent an array like this, where both
// the key and the value would hold the ID: [1 => 1, 2 => 2, 3 => 3, ...]
if ($this->Stores->save($sore, ['businessHourIds' => $businessHourIds])) {
    // ...
}
// ...

重要的是它是一个ArrayObject 实例,这样它的状态才能在每个营业时间实体的多个不同save() 调用中保持不变。然后,您的规则可以按照以下方式做一些事情:

function (BusinessHour $entity, array $options): bool {
    if (!isset($options['businessHourIds'])) {
        return false;
    }
    
    // If the entity isn't new, its ID must be present in the IDs list
    // so that it can be excluded in the exists check
    if (
        !$entity->isNew() &&
        !isset($options['businessHourIds'][$entity->id]
    ) {
        return false;
    }

    // the current entries opening time must be smaller than its closing time
    if ($entity->opening_time >= $entity->closing_time) {
        return false;
    }

    $conditions = [
        'id NOT IN' => (array)$options['businessHourIds'],
        'store_id' => $entity->store_id,
        'day' => $entity->day,
        'closing_time >' => $entity->opening_time,
        'opening_time <' => $entity->closing_time,
    ];
    
    // remove the current entity ID from the list, so that for the next
    // check it is not being excluded, meaning that all following business
    // hours will have to be valid according to this entity's data
    unset($options['businessHourIds'][$entity->id]);

    return !$options['repository']->exists($conditions);
}

所以这是未经测试的,理想情况下它会起作用,但主要是为了说明我在说什么。另请注意,我已经减少了打开/关闭时间条件,假设关闭时间必须大于打开时间,这个检查理论上应该涵盖所有可能的重叠。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-07
    相关资源
    最近更新 更多