Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

fermevc's avatar

Help with request validation (composite uniqueness)

I have two functions in controller, "store()" for inserting single item in database, and "bulkInsert()" for storing multiple items.

I can't figure out why my validation rule for checking uniqueness for multiple values doesn't work when I'm validating data in nested array (array of key,value pairs in request), but works OK when data is not nested (simple key,value pairs in request).

Here is my validation code for single insert action:

$input = Validator::make($request->all(), [
            'group_id' => ['required', 'numeric'],
            'tag' => ['required', 'string', 'max:255'],
            'subgroup' => [
                'required', 'string', 'max:255', Rule::unique('ram_templates', 'subgroup')
                    ->where('group_id', $request->group_id)
                    ->where('tag', $request->tag)
            ],
        ], [
            'group_id.required' => __('Group must be selected'),
            'subgroup.required' => __('Subgroup must be selected'),
            'subgroup.unique' => __('Combination of values for group, subgroup and tag not unique.'),
        ])->validate();

If I try to submit duplicate data, in frontend I receive '422 status' and correct message from validator.

Here is the validation code for multiple insert action:

$input = Validator::make($request->fields, [
            '*.create' => ['boolean'],
            '*.group_id' => ['required_if:*.create,true'],
            '*.subgroup' => [
                'required_if:*.create,true',
                Rule::unique('ram_templates', 'subgroup')
                    ->where('group_id', $request->group_id)
                    ->where('tag', $request->tag)
            ],
            '*.tag' => ['required', 'string'],
        ], [
            '*.group_id.required_if' => __('Group must be selected.'),
            '*.subgroup.required_if' => __('Subgroup must be selected.'),
            '*.subgroup.unique' =>
            __('Combination of values for group, subgroup and tag not unique.'),
        ])->validate();

If duplicate data is submitted, I get '500 status' in frontend, and in Laravel's log I can see the SQL error about duplicate key value insertion attempt. I would like to add that all other rules for nested array validation are working OK.

Do I need to loop through array like mentioned in documentation or something else needs to be done? Thanks in advance!

0 likes
8 replies
rodrigo.pedra's avatar

I never tried having the asterisk as the first parameter, can you try wrapping your data into a key?

Try this:

$input = Validator::make(['data' => $request->fields], [
    'data.*.create' => ['boolean'],
    'data.*.group_id' => ['required_if:*.create,true'],
    'data.*.subgroup' => [
        'required_if:data.*.create,true',
        Rule::unique('ram_templates', 'subgroup')
            ->where('group_id', $request->group_id)
            ->where('tag', $request->tag),
    ],
    'data.*.tag' => ['required', 'string'],
], [
    'data.*.group_id.required_if' => __('Group must be selected.'),
    'data.*.subgroup.required_if' => __('Subgroup must be selected.'),
    'data.*.subgroup.unique' =>
        __('Combination of values for group, subgroup and tag not unique.'),
])->validate();

Note that also, in the docs, all examples have a wrapping key.

If this works, you will need to change your frontend code, so it considers the "wrapper" key we added.

fermevc's avatar

@rodrigo.pedra

Good point! I've noticed the same thing a moment ago, only difference between validator input is that I'm unwrapping $request->all() into $request->fields. Will try and let you know.

1 like
rodrigo.pedra's avatar

@fermevc I was thinking better about this, and I guess you already have a wrapper key after all...

Try this:

$input = Validator::make($request->only('fields'), [
    'fields.*.create' => ['boolean'],
    'fields.*.group_id' => ['required_if:*.create,true'],
    'fields.*.subgroup' => [
        'required_if:fields.*.create,true',
        Rule::unique('ram_templates', 'subgroup')
            ->where('group_id', $request->group_id)
            ->where('tag', $request->tag),
    ],
    'fields.*.tag' => ['required', 'string'],
], [
    'fields.*.group_id.required_if' => __('Group must be selected.'),
    'fields.*.subgroup.required_if' => __('Subgroup must be selected.'),
    'fields.*.subgroup.unique' =>
        __('Combination of values for group, subgroup and tag not unique.'),
])->validate();

If this works, your frontend code for showing validation, might be fine already.

fermevc's avatar

@rodrigo.pedra

Unfortunately, even with wrapping, unique rule is not being checked or it passes, I get duplicate key DBMS error (which I think means that validation has passed).

Here is the updated validator code, please note the comment in 'Rule object' regarding values being compared.

$input = Validator::make(['data' => $request->fields], [
            'data.*.create' => ['boolean'],
            'data.*.group_id' => ['required_if:data.*.create,true'],
            'data.*.subgroup' => [
                'required_if:data.*.create,true',
                Rule::unique('ram_templates', 'subgroup')
                    ->where('group_id', $request->group_id) // <-- THIS IS NOT FEELING RIGHT TO ME !!!!
                    ->where('tag', $request->tag), // <-- THIS IS NOT FEELING RIGHT TO ME !!!!
            ],
            'data.*.tag' => ['required', 'string'],
        ], [
            'data.*.group_id.required_if' => __('Group must be selected.'),
            'data.*.subgroup.required_if' => __('Subgroup must be selected.'),
            'data.*.subgroup.unique' =>
            __('Combination of values for group, subgroup and tag not unique.'),
        ])->validate();

I'm thinking that $request->group_id and $request->tag doesn't exist, it should be something else but it's just a gut feeling... That's why I've mentioned Rule::forEach... but I don't understand how to use it in order to try it...

rodrigo.pedra's avatar
Level 56

@fermevc

Ah! Now I see what you mean.

Try this:

$input = Validator::make($request->only('fields'), [
    'fields.*' => Rule::forEach(function ($field, $attribute) {
        $isCreating = $field['create'] ?? null;
        $isCreating = $isCreating === 'true';

        return [
            'create' => ['boolean'],

            'group_id' => [Rule::requiredIf($isCreating)],
            'tag' => ['required', 'string', 'max:255'],

            'subgroup' => [
                Rule::requiredIf($isCreating),
                Rule::unique('ram_templates', 'subgroup')
                    ->where('group_id', $field['group_id'] ?? null)
                    ->where('tag', $field['tag'] ?? null),
            ],
        ];
    }),
], [
    'fields.*.group_id.required_if' => __('Group must be selected.'),
    'fields.*.subgroup.required_if' => __('Subgroup must be selected.'),
    'fields.*.subgroup.unique' =>
        __('Combination of values for group, subgroup and tag not unique.'),
])->validate();
fermevc's avatar

@rodrigo.pedra

This definitely make sense and it's working as I need it to work. I've never considered the possibility to wrap all other rules in Rule::forEach. Now, I just need to update the frontend. Thanks a lot!

1 like
fermevc's avatar

Just to leave some additional instructions/information:

After updating frontend error handling, I needed to change the code regarding custom error messages in validator. For some reason, 'fields.*.group_id.required_if' needs to be 'fields.*.group_id.required' and 'fields.*.subgroup.required_if' needs to be 'fields.*.subgroup.required'.

Now, all is GOOD, and one more time - thank you Rodrigo!

1 like

Please or to participate in this conversation.