Laravel FormRequest Validation for Rest API получает вызовы с 2 параметрами запроса json, которые не работают должным образом

У меня есть класс Laravel FormRequest, который должен проверять две строки запроса JSON (вложенные объекты и массивы) на маршруте GET в моем JSON Rest API. Я беру строки запроса json, декодирую их в php-объекты и сохраняю их во время подготовки к проверке по запросу для проверки.

По какой-то причине кажется, что это не работает должным образом. Проверка json «sorters» работает, как и проверка «sorters_decoded» для «обязательного» и «массива». Проверка элемента массива и все последующее, кажется, не работает, так как я достигаю функции контроллера, даже если отправлены недопустимые данные. Коллекция пакетов входных данных запроса изменена (недопустимые данные имеют значение null), но ответ проверки 422 не генерируется. Возможно, вы видите что-то не так с этим кодом?

class RecipeFindRequest extends FormRequest
{

    protected function prepareForValidation(): void
    {
        try {
            $sorters = null;
            if ($this->query->has('sorters')) {
                $sorters = json_decode($this->query->get('sorters'));
                $this->merge([
                    'sorters_decoded' => $sorters,
                ]);
            }

            $filters = null;
            if ($this->query->has('filters')) {
                $filters = json_decode($this->query->get('filters'));
                $this->merge([
                    'filters_decoded' => $filters,
                ]);
            }

            $this->merge([
                'language' => $this->headers->get('language'),
            ]);
        } catch(\Throwable $e) {
            //die silently, fail response will get raised on validation time
        }
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules(): array
    {
        return [
            'sorters' => ['json'],
            'sorters_decoded' => ['required', 'array'],
            'sorters_decoded.*.name' => ['required', 'string', Rule::in(['likes', 'ratings', 'calories', 'carbs', 'protein', 'fat', 'created'])],
            'sorters_decoded.*.direction' => ['required', 'string', Rule::in(['asc', 'desc'])],
            'sorters_decoded.*.order' => ['required', 'integer'],
            'filters' => ['json'],
            'filters_decoded.duration_high' => ['integer', 'min:0'],
            'filters_decoded.duration_low' => ['integer', 'min:0'],
            'filters_decoded.title' => ['string'],
            'filters_decoded.difficulty' => ['array'],
            'filters_decoded.difficulty.*' => ['string', 'exists:difficulties,id'],
            'filters_decoded.ingredients' => ['array'],
            'filters_decoded.ingredients.*.id' => ['string', 'exists:ingredients,id'],
            'filters_decoded.ingredients.*.relation' => ['string', Rule::in(['include', 'exclude'])],
            'filters_decoded.liked_by_me' => ['boolean'],
            'filters_decoded.cookbooks' => ['array'],
            'filters_decoded.cookbooks.*' => ['string', 'exists:cookbooks,id'],
            'filters_decoded.nutritions' => ['array'],
            'filters_decoded.nutritions.*.category_id' => ['string', 'exists:nutrition_categories,id'],
            'filters_decoded.nutritions.*.nutrition_high' => ['numeric', 'min:0'],
            'filters_decoded.nutritions.*.nutrition_low' => ['numeric', 'min:0'],
            'language' => ['string', 'size:2', 'exists:i18n_languages,short'],
            'page' => ['integer', 'min:1'],
            'per_page' => ['integer', 'min:1'],
        ];
    }
}

Оба параметра отправляются как строки запроса json, и шаг декодирования работает, поэтому это не связано с декодированием URL.

Я попытался изменить проверку элемента массива sorters.*.name со строки на целое число, и когда я отправил некоторые данные, например [{'name':'a'}], проверка "integer" изменила данные в $response-> query->sorters_decoded to [{'name':null}]. Но ответа 422 о сбое проверки не появилось.

Спасибо за внимание к моей проблеме, Микки

Вы сделали dump($this->sorters_decoded), чтобы увидеть, соответствует ли структура тому, что вы ожидаете? (Не включайте изображения текста в свой вопрос, он недоступен или не может быть использован повторно.)

miken32 08.06.2023 22:07

Кроме того, если вы хотите потерпеть неудачу в prepareForValidation(), вам нужно а) не использовать исключение и б) сказать json_decode() генерировать исключения — по умолчанию он этого не делает.

miken32 08.06.2023 22:12

И действительно ли вы передаете эти строки JSON в строке запроса URL? Это кажется не идеальным.

miken32 08.06.2023 22:13

привет @miken32. Спасибо за ваш комментарий. Извините за текст на картинке, соответствующий параметр запроса сортировщика - "[{"name":"likes","direction":"asc","order":1}]". Поскольку это вызов get, я не хотел передавать json в теле запроса. Я мог бы изменить его на POST, если ничего не поможет. Я проверил $this->sorters_decoded с помощью xdebug, и мне кажется, что это правильный формат. var_dump($this->sorters) показывает: array(1) { [0]=> object(stdClass)#1450 (3) { ["name"]=> string(5) "likes" ["direction"]=> string(3) "asc" ["order"]=> int(1) } }

MikkeyDevelop 09.06.2023 09:01

^^ var_dump взят из $this->sorters_decoded

MikkeyDevelop 09.06.2023 09:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
5
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Здесь есть ряд проблем:

  • В prepareForValidation() у вас есть блок try/catch, который ничего не делает с пойманным исключением. Вопреки тому, что предлагает ваш комментарий, он не будет возвращен как ошибка проверки, потому что вы ее поймаете. Даже если он останется незамеченным, я не уверен, что он будет отображаться как ошибка 422 на стороне клиента.
  • json_decode() по умолчанию не выдает ошибок, так что оператору try/catch все равно не с чем работать!
  • json_decode() также по умолчанию декодирует объекты; ваши последующие правила проверки не будут работать без массивов.
  • У вас есть правила проверки данных, которые вы отправляете в строке запроса. Проверка формы запроса работает только с телом сообщения, а не со строкой запроса. Все, что вы хотите проверить, должно быть объединено вручную.

Вот что я бы попробовал:

<?php

namespace App\Http\Requests;

use Illuminate\Validation\ValidationException;
use Throwable;

class RecipeFindRequest extends FormRequest
{

    protected function prepareForValidation(): void
    {
        try {
            if ($this->query->has('sorters')) {
                $sorters = json_decode($this->query->get('sorters'), associative: true, flags: \JSON_THROW_ON_ERROR);
            }

            if ($this->query->has('filters')) {
                $filters = json_decode($this->query->get('filters'), associative: true, flags: \JSON_THROW_ON_ERROR);
            }

            $this->merge([
                // user cannot easily change the headers of a request
                // so you should set a sensible default for this value
                'language' => $this->headers->get('language', 'en'),
                'page' => $this->query->get('page'),
                'per_page' => $this->query->get('per_page'),
                'sorters_decoded' => $sorters ?? null,
                'filters_decoded' => $filters ?? null,

            ]);
        } catch(Throwable $e) {
            if (isset($sorters)) {
                // if this is set, the error was with the second decode
                $messages = ['filters' => 'An invalid filter value was passed'];
            } else {
                $messages = ['sorters' => 'An invalid sort value was passed'];
            }
            throw ValidationException::withMessages($messages);
        }
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules(): array
    {
        return [
            'sorters_decoded' => ['required', 'array'],
            'sorters_decoded.*.name' => ['required', 'string', Rule::in(['likes', 'ratings', 'calories', 'carbs', 'protein', 'fat', 'created'])],
            'sorters_decoded.*.direction' => ['required', 'string', Rule::in(['asc', 'desc'])],
            'sorters_decoded.*.order' => ['required', 'integer'],
            'filters_decoded.duration_high' => ['integer', 'min:0'],
            'filters_decoded.duration_low' => ['integer', 'min:0'],
            'filters_decoded.title' => ['string'],
            'filters_decoded.difficulty' => ['array'],
            'filters_decoded.difficulty.*' => ['string', 'exists:difficulties,id'],
            'filters_decoded.ingredients' => ['array'],
            'filters_decoded.ingredients.*.id' => ['string', 'exists:ingredients,id'],
            'filters_decoded.ingredients.*.relation' => ['string', Rule::in(['include', 'exclude'])],
            'filters_decoded.liked_by_me' => ['boolean'],
            'filters_decoded.cookbooks' => ['array'],
            'filters_decoded.cookbooks.*' => ['string', 'exists:cookbooks,id'],
            'filters_decoded.nutritions' => ['array'],
            'filters_decoded.nutritions.*.category_id' => ['string', 'exists:nutrition_categories,id'],
            'filters_decoded.nutritions.*.nutrition_high' => ['numeric', 'min:0'],
            'filters_decoded.nutritions.*.nutrition_low' => ['numeric', 'min:0'],
            'language' => ['string', 'size:2', 'exists:i18n_languages,short'],
            'page' => ['integer', 'min:1'],
            'per_page' => ['integer', 'min:1'],
        ];
    }
}

Размер этих URL-адресов должен быть огромным; это то, для чего лучше всего использовать почтовые запросы. (Но это не жесткое и быстрое правило, просто мое мнение.)

Спасибо, это то, что я искал. Я новичок в PHP, последние пару лет работал с C#.

MikkeyDevelop 09.06.2023 23:26

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