Добавление предварительной валидации данных команд для красивого отображения ошибок в формате JSON для отображения на фронтенде. Централизованный вывод ошибок валидации в middleware.
- 00:00:32 Неудобство исключений для валидации
- 00:02:16 Постановка задачи
- 00:03:05 Формат вывода ошибок
- 00:04:33 Как коммитить недоделанные тесты
- 00:06:05 Разные способы валидации
- 00:09:12 Библиотека Symfony Validator
- 00:12:25 Подключение валидатора
- 00:13:12 Добавление правил для полей команд
- 00:14:50 Исправление загрузки аннотаций
- 00:16:55 Вызов валидации в экшенах
- 00:20:15 Проблема дублирования кода
- 00:21:29 Централизованный отлов ошибок
- 00:22:16 Формирование исключения валидации
- 00:22:53 Инкапсуляция валидации
- 00:29:11 Отлов ошибок в Middleware
- 00:32:09 Обзор результата
- 00:33:19 Тесты для мультиязычности
Скрытый контент (код, слайды, ...) для подписчиков.
Открыть →Чтобы не пропускать новые эпизоды подпишитесь на наш канал @deworkerpro в Telegram
Добрый день, спасибо за урок. А будет как - то реализованы коды ошибок? Чтобы фронт знал как необходимо реагировать на ту или иную ошибку.
Пока все ошибки у нас либо доменные, либо валидации. Для кастомного кода можно возвращать напрямую
$exception->getCode()
.Дмитрий здравствуйте, не могу понять как работает
Как вообще могут комментарии влиять на код? Здесь получается мы вызвали метод NotBlank? Как это вообще работает?
Непосредственно в PHP это просто комментарии и нативно они никак не работают..
Валидатор использует библиотеку doctrine/annotations. А она уже просто обходит поля через рефлексию и парсит эти строки.
Спасибо. Очень интересно.
Дмитрий, после коммита
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 может принимать большие структуры данных. Например, могут быть вложенные сущности, массивы вложенных сущностей:
Как быть в таких ситуациях?
Делать классы для вложений:
Познакомили с новой технологией, спасибо Дмитрий!
Применяя Symfony Validation + используя рассмотренный подход (Command=DTO), я сталкиваюсь с парой концептуальных проблем. Просьба растолковать, как их избежать.
Проблема первая:
Такой Command после валидации уже нет возможности использовать в качестве полноценной DTO в дальнейшей доменной валидации и дальнейшей обработке.
Почему? Потому что поля объекта Command в нашем сценарии уже не могут быть типизированными, но должны быть mixed, что сильно снижает ценность объекта как DTO и удобство извлечения из него данных.
Почему? Потому что когда мы хотим, например, проверить что в строке реквеста прилетел int (целое число) а не что то иное (float - 123.50) (или наоборот нам нужен float), то мы применяем проверку ПОЛЯ объекта на int или на float:
Но чтобы это работало, тип поля должен быть без указания типа (mixed), иначе, если поле будет
то Validation будет всегда определять его содержимое как int - всегда будет приведение типа к int при присваивании этому полю строки запроса.
Какие есть варианты сохранить типизированный феншуй?
Делать второй нормальный типизированный DTO на основе Command и отправлять его дальше?
Это весьма костыльно.
Подскажите пожалуйста, Дмитрий: как элегантно пользоваться validation и сохранить удобный типизированный DTO для дальнейшей обработки ?
Да, при ручном присваивании есть такая проблема. Вариантов несколько.
Либо валидировать не саму команду, а массив
$data = $request->getParsedBody()
. И потом уже присваивать провалидированные данные из$data
в команду с типами.Либо в команде оставлять тип
mixed
, а реальный тип указывать аннотацией:Либо команду заполнять данными не вручную, а через более умный компонент serializer, который бы видел тип
int
и выводил красивую ошибку несовпадения типа вместо PHP TypeError.Попробую для подобных вещей использовать Laminas Normalizer.
Например, в DTO тип поля ?string. nullable - чтобы Validator понимал что поле опциональное. а в db таблице это поле - только строка (not nullable)
И в Normalizer попробую настроить для таких полей правило: если в dto поля - null - преобразовать его в пустую строку (для дальнейшей беспроблемной записи в БД).
А вот вдогонку. Проблема вторая:
В своём проекте, в исключениях (Exception) мне мало одного поля message, поэтому я использую два разных сообщения для одной ошибки - для пользователя и для логов. Типа того:
и при ловле моего Exeption:
И все было прекрасно пока я не познакомился с Symfony Validation, в котором есть так много вкусных проверок, но предусмотрено только одно сообщение об ошибке(Message).
Дмитрий, как посоветуете использовать Symfony Validation с сохранением двух разных сообщений об одной ошибке?
Благодарю!
Можно некрасивые технические ошибки оборачивать красивыми пользовательскими:
При этом оригинальное исключение мы передаём в
$previous
в конструктор внешнего.Так пользователю выведется внешнее сообщение "что-то не так", а в лог запишется полный стектрейс вместе с вложенным оригинальным исключением для гиков.
Дмитрий, откройте тайну: почему Вы решили задавать constrains в аннотациях, а не через PHP (ниже пример)? Ведь в аннотация проще ошибиться при множестве правил, а в PHP hinting подсказывает..
Кстати, чтобы включить эту возможность достаточно ли добавить в конфигурации ?
Кстати, темп и краткость/подробность подачи материала для меня лично теперь оптимальны, спасибо! А если покажеться что тема не раскрыта - буду вопрошать :)
Это визуально удобнее, когда правила находятся рядом с полем, а не где-то отдельно внизу.
Уже сейчас в PHP 8 можно вместо аннотаций использовать атрибуты:
Тогда всё будет нативно подсказываться без плагинов для аннотаций.
Вчера как раз записал скринкаст про перевод нашего проекта на атрибуты. Скоро опубликую.
Благодарю за вдохновляющие ответы, Дмитрий! Буду использовать.
Вот это шикарно! Главное, чтобы чайникам, типа меня, было чётко объяснено преимущество атрибутов перед аннотациями.
Вангую что след каст про переход на PHP 8.1 - там кучу всего задепрекейтили...
Атрибуты - это теперь конструкции языка, где можно вставлять обычный программный код.
А аннотации - это просто обычные текстовые комментарии, которые парсились регулярками.
осталось понять зачем код вставлять в атрибуты, когда он отлично работает и вне атрибутов :)
Не отлично. В комментарии код
entity=Token::class
не работает.ну, это и не код :) PHP код был бы таким: $entity=Token::class
В общем, я понял что если Вы в видео не покажете на примерах + с пояснением предназначение атрибутов, то каши в головах может только добавиться
Это код с именованными аргументами:
Здравствуйте, Дмитрий!
Сделал себе обработчик ошибок в middleware, по аналогии из аукциона. Оказалось, что такой код не отлавливает / не логирует PHP ошибки. Только исключения.
Например, в Вашем коде не будет залогирована ошибка несуществующего ключа массива:
Это грустно, т.к. такие ошибки критичны.
Посоветуете ли сделать лог ошибок как здесь? Advanced Notices and Warnings Handling
Чтобы ловились не только
Exception
, но иError
, можно указыватьThrowable
:У нас такие ошибки перехватывает Sentry.
А чтобы ловить Notice или Warning регистрируют глобальный обработчик через
set_error_handler
и в нём либо сразу логируют, либо преобразовывают в исключение.При обработке ошибок столкнулся с неприятной вещью:
Отправляю из vanilla JavaScript (browser) fetch() запрос . В бэкенде предусматриваю возврат json ответа в случае ошибки, чтобы ошибку красиво отобразить на фронтенде.
Однако, в случае ошибки возвращается не json, а страшный html который сформирован xdebug. То есть, xdebug нагло "впихивает" в ответ своё html содержимое вместо json ответа.
Может будут идеи как избежать такой навязчивости xdebug? Пусть xdebug отображает ошибки только там, где ему положено - на html страницах.
В таком режиме Xdebug не использую, так что не подскажу.
Или войти через: