У меня есть приложение, в котором мне нужно проверить рабочие часы, чтобы убедиться, что они не перекрываются в CakePHP 3. Все записи можно изменить в одной форме. Данные выглядят примерно так:
Теперь, когда я изменю первое время открытия на 17:00, это будет недопустимо, потому что оно будет перекрываться со второй строкой. Но когда я изменяю второе время открытия на 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 Спасибо, что указали на это. Я попытался уточнить свой вопрос. Запрос дает правильные результаты, но он проверяет одну запись по базе данных и не принимает во внимание другие изменения.
Логически вы можете сохранять только один объект за раз, не только на уровне приложения, но и на уровне СУБД, поэтому правило правильно говорит вам, что данные недействительны, поскольку оно проверяет только один объект за раз.
Я думаю, вам нужно либо какое-то состояние, чтобы справиться с этим, либо вам понадобится какое-то сочетание того, для чего предназначены правила проверки и приложения, т.е. вам нужно будет проверять данные без состояния, а также данные с состоянием/сохраняемые , не только все часы в несохраняемом наборе данных должны быть действительными по отношению друг к другу, они также должны быть действительными по отношению к уже сохраненным данным в базе данных.
Я думаю, есть много места для споров о том, как лучше всего решить эту проблему, в правилах приложений еще нет интерфейса, который позволил бы проверять несколько объектов, поэтому, как бы вы ни решили проблему, это, вероятно, будет какой-то компромисс/обходной путь, так или иначе .
Поскольку, наконец, речь идет о данных в БД, вся проверка должна происходить в транзакции, поэтому, я думаю, было бы предпочтительнее хранить вещи в правилах приложений. Один грязный способ, о котором я мог подумать, состоял бы в том, чтобы передать идентификаторы всех объектов в процесс сохранения, и в вашем правиле исключить все идентификаторы, которые еще не были проверены (и, следовательно, сохранены), таким образом, последующие проверки будут выполняться против предыдущих объекты, которые уже были сохранены, что означает, что в конце концов вы проверили бы все записи на достоверность друг против друга.
Вот пример — я не знаю вашу настройку ассоциации, поэтому я просто предполагаю, что вы сохраняете 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);
}
Так что это не проверено, в идеале это должно работать, но это в первую очередь предназначено для иллюстрации того, о чем я говорю. Также обратите внимание, что я уменьшил условия времени открытия/закрытия, предполагая, что время закрытия должно быть больше, чем время открытия, эта проверка теоретически должна охватывать все возможные перекрытия.
Так в чем проблема с вашим кодом? Ваш вопрос не указывает на что-то конкретное. Или, более конкретно, ваша проблема связана с тем, что запрос не дает правильных результатов, или это связано с тем, что происходит потом, то есть с «пометкой вещей недействительными»? Или, может быть, оба?