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

akyrey's avatar
Level 7

Nested array validation

Hello, on Laravel 12.3.0 I'm trying to validate an array of ids like this

{
	"tags": ["tag_1", "tag_2"]
}

using this rule

'tags.*' => Rule::forEach(fn (?string $value) => [
        Rule::exists(Tag::class, 'name'),
    ]
)

but I'm getting the error BadMethodCallException: Method Illuminate\Validation\Validator::validateExists:tags,name does not exist. and if I dump the $value it's always null. What am I doing wrong?

Thank you

0 likes
4 replies
LaryAI's avatar
Level 58

The issue you're encountering is due to the incorrect use of the Rule::exists method within the forEach closure. The Rule::exists method is not designed to be used in this way. Instead, you should use the exists validation rule directly in your validation array.

Here's how you can properly validate an array of IDs to ensure each tag exists in the tags table:

use Illuminate\Validation\Rule;
use App\Models\Tag;

// Assuming you're in a controller method or a form request
$request->validate([
    'tags' => 'required|array',
    'tags.*' => [
        'required',
        Rule::exists(Tag::class, 'name'),
    ],
]);

Explanation:

  1. 'tags' => 'required|array': This ensures that the tags field is present and is an array.

  2. 'tags.*' => [...]: This applies the validation rules to each element within the tags array.

  3. Rule::exists(Tag::class, 'name'): This checks that each tag name exists in the name column of the tags table. The exists rule is used here to ensure that each tag in the array corresponds to an existing record in the database.

By using the exists rule directly in the validation array, you avoid the BadMethodCallException and ensure that each tag is validated correctly.

1 like
martinbean's avatar

@akyrey I’d not over-complicate things and just use the string-based validation rules for such simple validation:

return [
    'tags' => ['array'],
    'tags.*' => ['bail', 'string', 'distinct', 'exists:tags,name'],
];
1 like
akyrey's avatar
Level 7

@martinbean thank you for your response! Sorry, I forgot to mention I have a slighty more complicated situation like this

'tags.*' => Rule::forEach(fn (?string $value) => [
    Rule::exists(Tag::class, 'name')->where(function (Builder $query) use ($value) {
        $query->where('name', 'ilike', $value)
            ->where('user_id', auth()->user()?->id);
    }),
]

that's why I needed the $value

EDIT

After writing this response I think I'm doing something wrong anyway, because I want to check the existence using the ilike comparison and I think the Rule::exists(Tag::class, 'name') would check with the = operator

martinbean's avatar
Level 80

@akyrey Yeah, the database rules are very simplistic and only do simple where clauses.

You could use a closure-based validation rule where you could add more complicated clauses:

'tags.*' => [
    'bail',
    'string',
    'distinct',
    function (string $attribute, string $value, Closure $fail) {
        Tag::query()
            ->where('name', 'ilike', $value)
            ->where('user_id', '=', $this->user()->getKey()
            ->existsOr(fn () => $fail('validation.exists'));
    },
],
1 like

Please or to participate in this conversation.