Как проверить несколько сущностей на самих себя, а также на существующие строки в базе данных?

У меня есть приложение, в котором мне нужно проверить рабочие часы, чтобы убедиться, что они не перекрываются в CakePHP 3. Все записи можно изменить в одной форме. Данные выглядят примерно так:

идентификатор день время открытия время закрытия 1 1 08:00:00 13:00:00 2 1 16:00:00 22:00:00

Теперь, когда я изменю первое время открытия на 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 17.12.2020 20:46

@ndm Спасибо, что указали на это. Я попытался уточнить свой вопрос. Запрос дает правильные результаты, но он проверяет одну запись по базе данных и не принимает во внимание другие изменения.

jbe 18.12.2020 08:24
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
2
197
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Логически вы можете сохранять только один объект за раз, не только на уровне приложения, но и на уровне СУБД, поэтому правило правильно говорит вам, что данные недействительны, поскольку оно проверяет только один объект за раз.

Я думаю, вам нужно либо какое-то состояние, чтобы справиться с этим, либо вам понадобится какое-то сочетание того, для чего предназначены правила проверки и приложения, т.е. вам нужно будет проверять данные без состояния, а также данные с состоянием/сохраняемые , не только все часы в несохраняемом наборе данных должны быть действительными по отношению друг к другу, они также должны быть действительными по отношению к уже сохраненным данным в базе данных.

Я думаю, есть много места для споров о том, как лучше всего решить эту проблему, в правилах приложений еще нет интерфейса, который позволил бы проверять несколько объектов, поэтому, как бы вы ни решили проблему, это, вероятно, будет какой-то компромисс/обходной путь, так или иначе .

Поскольку, наконец, речь идет о данных в БД, вся проверка должна происходить в транзакции, поэтому, я думаю, было бы предпочтительнее хранить вещи в правилах приложений. Один грязный способ, о котором я мог подумать, состоял бы в том, чтобы передать идентификаторы всех объектов в процесс сохранения, и в вашем правиле исключить все идентификаторы, которые еще не были проверены (и, следовательно, сохранены), таким образом, последующие проверки будут выполняться против предыдущих объекты, которые уже были сохранены, что означает, что в конце концов вы проверили бы все записи на достоверность друг против друга.

Вот пример — я не знаю вашу настройку ассоциации, поэтому я просто предполагаю, что вы сохраняете 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);
}

Так что это не проверено, в идеале это должно работать, но это в первую очередь предназначено для иллюстрации того, о чем я говорю. Также обратите внимание, что я уменьшил условия времени открытия/закрытия, предполагая, что время закрытия должно быть больше, чем время открытия, эта проверка теоретически должна охватывать все возможные перекрытия.

Другие вопросы по теме