У меня есть класс 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 о сбое проверки не появилось.
Спасибо за внимание к моей проблеме, Микки
Кроме того, если вы хотите потерпеть неудачу в prepareForValidation(), вам нужно а) не использовать исключение и б) сказать json_decode() генерировать исключения — по умолчанию он этого не делает.
И действительно ли вы передаете эти строки JSON в строке запроса URL? Это кажется не идеальным.
привет @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) } }
^^ var_dump взят из $this->sorters_decoded






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