Валидация ввода для API

Добавление предварительной валидации данных команд для красивого отображения ошибок в формате JSON для отображения на фронтенде. Централизованный вывод ошибок валидации в middleware.

  • 00:00:32 - Неудобство исключений для валидации
  • 00:02:15 - Постановка задачи
  • 00:03:04 - Формат вывода ошибок
  • 00:04:32 - Как коммитить недоделанные тесты
  • 00:06:04 - Разные способы валидации
  • 00:09:08 - Библиотека Symfony Validator
  • 00:12:22 - Подключение валидатора
  • 00:13:08 - Добавление правил для полей команд
  • 00:14:47 - Исправление загрузки аннотаций
  • 00:16:53 - Вызов валидации в экшенах
  • 00:20:14 - Проблема дублирования кода
  • 00:21:29 - Централизованный отлов ошибок
  • 00:22:17 - Формирование исключения валидации
  • 00:29:15 - Отлов ошибок в Middleware
  • 00:32:13 - Обзор результата
  • 00:33:24 - Тесты для мультиязычности
Скрытый контент
Комментарии (17)
Denis

Добрый день, спасибо за урок. А будет как - то реализованы коды ошибок? Чтобы фронт знал как необходимо реагировать на ту или иную ошибку.

Ответить
Дмитрий Елисеев

Пока все ошибки у нас либо доменные, либо валидации. Для кастомного кода можно возвращать напрямую $exception->getCode().

Ответить
Дмитрий

Дмитрий здравствуйте, не могу понять как работает

/**
 * @Assert\NotBlank
 */

Как вообще могут комментарии влиять на код? Здесь получается мы вызвали метод NotBlank? Как это вообще работает?

Ответить
Дмитрий Елисеев

Непосредственно в PHP это просто комментарии и нативно они никак не работают..

Валидатор использует библиотеку doctrine/annotations. А она уже просто обходит поля через рефлексию и парсит эти строки.

Ответить
Arunas

Спасибо. Очень интересно.

Ответить
Роман

Дмитрий, после коммита Extracted validation handling ElisDN, psalm стал ругаться на ошибку ERROR: UndefinedDocblockClass в ValidationExceptionHandler::errorsArray на строку $violation->getMessage(). Решил путём установки полифилла composer require symfony/polyfill-php80 --dev чтобы был доступен \Stringable интерфейс

Ответить
Дмитрий Елисеев

Да, в свежем валидаторе на будущее добавили Stringable, который появится только в PHP 8.0.

Ответить
Евгений

Как поступить в случае, если формат запроса не совпадает с форматом команды? Тогда сообщения об ошибках валидации будут кодироваться неправильными полями. И пользователю будет непонятно к какому полю относится ошибка.

Ответить
Дмитрий Елисеев

В случае если пользователь пришлёт в email вместо строки массив? Тогда да, вылетит ошибка типов. Можно либо добавить приведение (string)($data['email'] ?? ''), либо воспользоваться сериализатором вроде symfony/serializer и отлавливать его исключения.

Ответить
Евгений

Нет. Я имел в виду разные названия полей. У меня недавно был случай когда нужно было к одной команде интегрировать два клиента, но переписать их я не мог. В итоге один присылал email в поле "email", а второй в виде "clientEmail". Если валидацию сделать на уровне команды, то второму клиенту уйдёт сообщение, что email неверный, а он ожидает clientEmail. В итоге я сделал объект формы для валидации ввода с фабричным методом создания команды. Но таким образом оставил команду без валидации. После этого видео задумался, что это не правильно.

Ответить
Евгений

Дмитрий, вы привели пример с валидацией email в объекте Email и поля email в команде. Валидатор email практически стандартный. Но что если нужен кастомный валидатор? Получается его логику придётся дублировать в инициализации объекта/типа и в проверке правильности команды. Подозреваю, что если возникнет ещё один слой с DTO, то придётся и там проверять ещё раз. Например перед записью в хранилище. Но это слабый пример, так как мы доверяем нашей модели.

Ответить
Дмитрий Елисеев

Если сложный кастомный, то да, либо дублировать, либо оставить только в одном месте.

Ответить
Андрей

Добрый день. Заметил один момент - в некоторых фабриках для контейнера Вы используете static function (mailer, errors, http), в других просто function. Выбор того или иного варианта чем-то обусловлен?

Ответить
Дмитрий Елисеев

Здесь не обусловлен. По возможности PhpStorm с плагином EA Extended везде советует объявлять анонимные функции как static function для микрооптимизации.

А в маршрутизации в routes.php не дописываем, так как там фреймворк экшены в виде анонимных функции привязывает к объекту приложения через bind($app), чтобы оно попало к ним в $this. А статические функции нельзя привязать к объекту, поэтому там получим ошибку.

Ответить
Андрей

Спасибо!

Ответить
Владимир

Дмитрий, во всем проекте довольно-таки простые команды, они содержат 1-3 поля. Однако в реальном мире, API может принимать большие структуры данных. Например, могут быть вложенные сущности, массивы вложенных сущностей:

{
  "doctor": {
    "first_name": "",
    "last_name": ""
  },
  "patient": {
    "first_name": "",
    "last_name": ""
  },
  "medications": [
    {
      "name": "",
      "count": ""
    },
    {
      "name": "",
      "count": ""
    }
  ]
}

Как быть в таких ситуациях?

Ответить
Дмитрий Елисеев

Делать классы для вложений:

class Command {
    public Doctor $doctor;
    public Patient $patient;
    public array $medications;
}

class Doctor {
    public string $firstName;
    public string $lastName;
}

class Patient {
    public string $firstName;
    public string $lastName;
}

class Medication {
    public string $name;
    public int $count;
}
Ответить
Зарегистрируйтесь или войдите чтобы оставить комментарий

Или войти через:

Google
GitHub
Yandex
MailRu