Когда приложение получает неоднозначные данные, принудительно относить их к категории «действительные» или «недействительные» — обычно значит скрывать проблему, а не решать её. Такое может произойти с данными из форм, интеграций или результатами, предложенными автоматизированной системой: контекста недостаточно, чтобы безопасно принять или отклонить запись. В таких случаях проверка человеком неоднозначных данных в PHP должна быть состоянием предметной области с явными правилами, а не неформальной меткой или импровизированной очередью.
Проектирование должно давать ответы на четыре вопроса: что было получено, что предлагается, кто должен принять решение и что произойдёт дальше. Разделение этих обязанностей упрощает управление процессом, исправление ошибок и объяснение того, почему данные были обработаны именно так.
Классифицируйте проблему до создания состояний

Не все данные, которые не могут продвинуться дальше, требуют проверки человеком. Как минимум, различайте следующие ситуации:
- Недействительные данные: не соответствуют проверяемому правилу, например имеют формат, который невозможно интерпретировать. В зависимости от источника данных и продукта их следует отклонить или запросить исправление.
- Неполные данные: отсутствует необходимое поле. Можно запросить данные, сохранить черновик или дождаться дополнительного источника.
- Неоднозначные данные: возможны несколько трактовок или имеющихся данных недостаточно для автоматического решения. Может потребоваться оценка человеком.
Проверка не должна становиться универсальным пунктом назначения для всех ошибок. Если значение нарушает однозначное ограничение, отправка его в очередь на проверку лишь добавит работы. Но когда решение зависит от контекста или суждения, состояние, допускающее проверку, позволяет приостановить обработку, не объявляя данные преждевременно правильными или неправильными.
Также стоит отличать этот процесс от карантина импорта. Карантин обычно изолирует входящие данные, которые не должны попасть в систему, например из-за провала технических проверок или проверок безопасности. Проверка разрешает неоднозначность бизнес-данных. Одна и та же запись может пройти через оба механизма, но их цели, права доступа и критерии выхода не взаимозаменяемы.
Определите небольшой и явный жизненный цикл
Начните с состояний, необходимых для описания процесса, а не со списка всех возможных будущих вариантов. Базовый жизненный цикл может выглядеть так:
pending_review: ожидает решения.confirmed: человек подтвердил пригодное для использования значение.rejected: человек решил, что данные не следует обрабатывать.reopenedили новая запись на проверку: ранее принятое решение снова поставлено под вопрос.
Определите допустимые переходы. Например, ожидающий решения случай можно подтвердить или отклонить; уже решённый случай можно открыть повторно только при наличии соответствующих полномочий. Не позволяйте произвольному запросу напрямую менять состояние на любое значение. Для перехода нужно задать исходное и целевое состояния, допустимого исполнителя и связанные эффекты.
Сохраняйте причину решения и данные о том, кто его принял. Если данные отклонены, укажите полезную причину, а не только логическое значение. Если трактовка подтверждена, сохраните одобренное значение и, когда это уместно, основания, на которых базировалось решение. Определите, когда причина обязательна, исходя из выполняемого действия, и не включайте в свободные заметки избыточные персональные данные.
Отделяйте входные данные, предложение и подтверждённое значение
Надёжное проектирование не перезаписывает полученные данные трактовкой автоматизированного процесса или исправлением человека. Разделяйте следующие понятия:
- Полученные данные: исходный ввод и его происхождение с надлежащими мерами контроля доступа и хранения.
- Предлагаемая интерпретация: рекомендация, сформированная правилами, интеграцией или вспомогательной системой, вместе с информацией, необходимой для её оценки.
- Подтверждённое значение: значение, которое приложение использует после принятия решения уполномоченным лицом.
Такое разделение не позволяет случайно принять предложение за истину и помогает восстановить путь к текущему значению. Не нужно без разбора дублировать каждое поле: сохраняйте данные, необходимые для аудита и поддержки, соблюдая политики хранения и защиты данных. Если предложение содержит оценку или автоматизированное объяснение, рассматривайте их как контекст, а не как решение.
Записывайте изменения в виде событий или записей истории, неизменяемых в ходе обычной работы приложения: идентификатор дела, действие, предыдущее и новое состояния, пользователь, дата и причина. Для удобства просмотра очереди модель может также хранить текущее состояние. Исправление оформляйте как новое решение или повторное открытие, связанное с историей; удаление предыдущего решения лишает возможности проследить историю изменений.
Реализуйте принятие решений на уровне предметной области
В PHP инкапсулируйте переходы в сервисе приложения или сущности предметной области, а не полагайтесь на то, что каждый контроллер применит одинаковые правила. Упрощённый пример:
final class ReviewDecisionService
{
public function confirm(ReviewCase $case, User $actor, string $value, string $reason): void
{
if (!$actor->canReview($case)) {
throw new DomainException('Доступ запрещён');
}
if (!$case->isPending()) {
throw new DomainException('Дело не ожидает решения');
}
$case->confirm($value, $actor->id(), $reason);
}
}
Этот фрагмент показывает, где размещать правила, но не является полной реализацией. Также проверяйте формат и ограничения значения, подтверждайте, что исполнитель уполномочен работать с этим делом, и записывайте решение. Не смешивайте аутентификацию — кто пользователь — с авторизацией — что ему разрешено делать с этой записью. Если предусмотрены роли или разделение обязанностей, выражайте эти ограничения в виде правил продукта.
Если обновление дела и запись истории должны быть согласованы, сохраняйте их в одной транзакции базы данных. Если решение запускает внешние эффекты, например вызов другого API, не считайте, что локальная транзакция охватывает и эту систему. Используйте надёжный механизм доставки, например transactional outbox, если этого требует архитектура, и проектируйте обработчики так, чтобы они выдерживали повторные попытки.
Спроектируйте очередь, помогающую принять решение
Полезная очередь позволяет расставлять приоритеты и находить дела, не скрывая их контекст. Добавьте уместные фильтры — по состоянию, дате, типу или назначенному исполнителю — и показывайте только информацию, необходимую для понимания дела. Если есть предложение, ясно обозначьте, что это именно предложение. Показывайте происхождение данных, причину проверки и доступные действия, не предоставляя пользователю элементы управления, которыми ему не разрешено пользоваться.
Назначение исполнителя может сократить дублирование работы, но не заменяет авторизацию и защиту от одновременных решений. Один человек может открыть дело, а другой — принять по нему решение до отправки ответа первым. Чтобы устаревшая страница не перезаписала недавнее изменение, используйте оптимистичный контроль конкурентного доступа: при сохранении сравнивайте версию или временную метку и отклоняйте устаревшую операцию, чтобы пользователь перезагрузил дело.
Если процессу нужны временные блокировки, определите срок их действия и поведение после истечения срока. Не используйте постоянное назначение исполнителя в качестве случайной блокировки. Регистрируйте значимые действия, чтобы можно было установить, кто проверял дело, и выявлять записи, которые остаются в ожидании дольше допустимого для операционной работы срока.
Исправляйте и открывайте повторно, не удаляя прошлое
Решение может оказаться ошибочным или измениться после появления новых данных. Определите, кто может открыть дело повторно, какую причину он должен указать и становится ли подтверждённое значение недоступным до завершения новой проверки. Ответ зависит от влияния значения на последующие процессы; например, может потребоваться остановить новые действия, но не удалять автоматически уже возникшие последствия.
Сохраняйте предыдущее решение в истории и создавайте новую версию или событие проверки. Если исправление данных требует пересчёта зависимой информации, явно определите эти эффекты и установите идемпотентную процедуру, чтобы избежать дублирования. Изменение состояния не должно создавать видимость того, что внешняя операция уже отменена.
Проверяйте правила, права доступа и последующие эффекты

Тесты должны охватывать весь процесс и его граничные условия, а не только сохранение формы. Как минимум, проверьте:
- Что для ожидающего решения дела разрешены предусмотренные переходы, а остальные отклоняются.
- Что пользователи без необходимых прав не могут принимать решения, повторно открывать дела или просматривать данные с ограниченным доступом.
- Что для каждого решения сохраняются пользователь, причина, а также предыдущие и новые значения в соответствии с заданной политикой.
- Что два параллельных изменения не позволяют устаревшему решению перезаписать другое.
- Что сбой записи истории не оставляет обновлённое состояние без возможности проследить историю изменений.
- Что повторный запуск последующего эффекта не дублирует операции или уведомления.
Используйте этот список как итоговую проверку проектирования: у неопределённости должна быть понятная причина; исходные данные, предложение и подтверждённое значение должны различаться; для каждого перехода должны быть определены права и причина; очередь должна предоставлять достаточно контекста; повторное открытие должно сохранять историю и учитывать операционные последствия. Если что-либо из перечисленного зависит от неформальных договорённостей, значит, в системе ещё не хватает правила.



