Какой подход использовать для валидации данных?
Валидация, масштабируемость и юзабилити, это как бы совсем разное.

Для юзабилити - валидация на фронте. Введя что-то невалидное пользователь должен видеть проблему сразу, а не когда закончит заполнение формы, и отправит всё на бэк.

На бэке при этом должна присутствовать всё та же валидация, чтобы не было возможности сохранить невалидным данные прямыми запросами.

Для гибкости - контроллер валидирует только соответствие структуры запроса (ругается что не хватает значений, или ожидали данные в одном формате, а пришли в другом). Сами данные в процессе обработки оборачивать в самовалидируемые ValueObjectы, которые при создании будут уже проверять данные на соответствие более специфичным требованиям.

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

И т.д. и т.п.
Я бы использовал Симфони валидатор.
Как уже написали выше - валидировать ValueObjectы.
Либо если ты разрабатываешь архитектуру для своего приложения, то можно имплементировать валидацию реквестов в контроллере, тем же симфони валидатором.