Laravel FormRequest Validation for Rest API 获取带有 2 个查询 json 参数的调用无法正常工作

Laravel FormRequest Validation for Rest API get calls with 2 query json params not working properly

提问人:MikkeyDevelop 提问时间:6/9/2023 最后编辑:matiaslauritiMikkeyDevelop 更新时间:6/9/2023 访问量:92

问:

我得到了一个 Laravel FormRequest 类,它应该在我的 JSON Rest API 上的 GET 路由上验证两个 JSON 查询字符串(嵌套对象和数组)。我拿起 json 查询字符串,将它们解码为 php 对象,并在验证准备时间期间存储它们,以请求进行验证。

出于某种原因,这似乎无法正常工作。“sorters”json 验证正在工作,“required”和“array”的“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 de/encoding 无关。

enter image description here

我试图将sorters.*.name数组元素验证从字符串更改为整数,当我发送一些数据(如[{'name':'a'})时,“integer”验证将$response->query->sorters_decoded中的数据更改为[{'name':null}]。但是没有出现 422 验证失败响应。

谢谢你考虑我的问题, 米基

PHP JSON Laravel API 验证

评论

1赞 miken32 6/9/2023
你有没有做过一个结构是否符合你的期望?(不要在您的问题中包含文字图片,因为它无法访问或重复使用。dump($this->sorters_decoded)
1赞 miken32 6/9/2023
此外,如果你想失败,你需要 a) 不使用异常和 b) 告诉抛出异常 - 默认情况下它不会这样做。prepareForValidation()json_decode()
1赞 miken32 6/9/2023
而且,你真的在 URL 查询字符串中传递这些 JSON 字符串吗?这似乎并不理想。
0赞 MikkeyDevelop 6/9/2023
嗨,@miken32。感谢您的评论。对不起,图片上的文字,相关的排序器查询参数是“[{”name“:”likes“,”direction“:”asc“,”order“:1}]”。由于这是一个 get 调用,我不想在请求正文中传输 json。如果没有其他帮助,我可以将其更改为 POST。我用 xdebug 检查了 $this->sorters_decoded,它似乎是正确的格式。var_dump($this->分拣机)显示:array(1) { [0]=> object(stdClass)#1450 (3) { ["name"]=> string(5) "likes" ["direction"]=> string(3) "asc" ["order"]=> int(1) } }
0赞 MikkeyDevelop 6/9/2023
^^var_dump来自$this->sorters_decoded

答:

0赞 miken32 6/9/2023 #1

这里存在许多问题:

  • 在 中,您有一个 try/catch 块,它对捕获的异常不执行任何操作。与您的评论所建议的相反,它不会作为验证错误返回,因为您正在捕获它。即使它没有被捕获,我不确定它是否会在客户端显示为 422 错误。prepareForValidation()
  • 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 的大小必须很大;这是 POST 请求最适合使用的那种事情。(但这不是一个硬性规定,只是我的意见。

评论

0赞 MikkeyDevelop 6/10/2023
谢谢,这就是我一直在找的。我是PHP的新手,最近几年一直在使用C#。