2

I'm trying to validate a field array, and I'd like to point out which field is wrong in the validation errors.

I have a form to upload multiple images. For each image, there must be a caption and the alt attribute for HTML. If I try to upload 3 images and miss the fields for two of them, I'll get an error message like the following:

The field 'image file' is required.
The field 'image caption' is required.
The field 'image alt' is required.
The field 'image caption' is required.
The field 'image alt' is required.
The field 'image file' is required.

The problem is that the :attribute is repeated and if the user wants to update multiple images he/she will have to check all of them to find which field is missing!

What I want is this:

The field 'image file (item 1)' is required.
The field 'image caption (item 1)' is required.
The field 'image alt (item 1)' is required.
The field 'image caption (item 3)' is required.
The field 'image alt (item 3)' is required.
The field 'image file (item 1)' is required.

So the user can know where to fix the problem.

First, I tried this:

$attributes = [
  'img.*.file'    => 'Image file (item :* )',
  'img.*.alt'     => 'Image alt (item :* )',
  'img.*.caption' => 'Image caption (item :* )',
];
//
$this->validate($request, $rules, [], $attributes);

I supposed that the :* would be replaced by the index of the field (1, 2, 3, 4, etc) as the same way :attribute is replaced by the attribute. However, the :* is not replaced by the index of the fields; instead, it is displayed as plain text.

It worths to note that I designed the code in such way that the HTML name attribute is indexed sequentially for all fields (img[1][alt], [img][2][alt], etc, img[1][caption], [img][2][caption], etc), so each field has the right index. Having that in mind, I suppose there is a way to get the index and use to create custom attributes in the error messages.

I searched for this problem and found the same question here Validation on fields using index position, but it uses angular, not laravel.

How can I get the index and put it in the attribute?If that is not possible, is there any other way to accomplish the desirable result without having to set up the error messages?

I would like to change the attributes and keep the default error messages that laravel provides

4 Answers 4

3

You can use the position placeholder or the index placeholder. The index placeholder will start from 0 and the position will start from 1.

$attributes = [
  'img.*.file'    => 'Image file (item : :position)',
  'img.*.alt'     => 'Image alt (item : :position)',
  'img.*.caption' => 'Image caption (item : :position)',
];

This will produce errors like: The Image file (item : 1) field is required.

You can tweak the error message as you want. You can also use the :position placeholder in custom messages instead of attributes.

$messages = [
  'img.*.file.required' => 'The field image file (item : :position) is required.',
    // add the other errors...
];

$this->validate($request, $rules, $messages);
Sign up to request clarification or add additional context in comments.

2 Comments

any idea how to get this for nested arrays? like roles.*.roleables.*? I mean it only gives position for the deepest one :(
@Back2Lobby, You will probably have multiple rules to handle that: a rule for roles.*, another for roles.*.roleables, another for roles.*.roleables.*.
1

Try this example

$input = Request::all();

$rules = array(
    'name' => 'required',
    'location' => 'required',
    'capacity' => 'required',
    'description' => 'required',
    'image' => 'required|array'
);

$validator = Validator::make($input, $rules);

if ($validator->fails()) {

    $messages = $validator->messages();

    return Redirect::to('venue-add')
        ->withErrors($messages);

}

$imageRules = array(
    'image' => 'image|max:2000'
);

foreach($input['image'] as $image)
{
    $image = array('image' => $image);

    $imageValidator = Validator::make($image, $imageRules);

    if ($imageValidator->fails()) {

        $messages = $imageValidator->messages();

        return Redirect::to('venue-add')
            ->withErrors($messages);

    }
}

1 Comment

I got your point! Although your code does not solve the problem, it gave me a good idea to address the problem! Thank you!
0

Thanks to the user sss S, I could implement their ideas to solve the problem.

Here is the code for the store method. It replaces (item) with (item $i) in the error message, where $i is the index of the field. Therefore the user can know exactly where is the error.

public function store(Request $request)
    {
        $rules      = $this->rules();
        $attributes = $this->attributes();

        $validator = Validator::make($request->all(), $rules, [], $attributes);
        $errors    = [];
        
        if ($validator->fails()) {
            $errors = $validator->errors()->all();
        }


        $imgRules = [
            'file'    => 'required|image|mimes:jpeg,jpg,webp,png|max:1999',
            'alt'     => 'required|string|max:100|min:5',
            'caption' => 'required|string|max:100|min:5',
        ];

        $imgAttributes = [
            'alt'     => 'HTML alt attribute (item)',
            'caption' => 'Caption(item)',
            'file'    => 'image file (item)',
        ];

        foreach($request->img as $i => $img) {
            $imgValidator = Validator::make($img, $imgRules, [], $imgAttributes);
            $imgErrors = [];

            if($imgValidator->fails()) {
                $imgErrors = $imgValidator->errors()->all();
            }

            foreach($imgErrors as $k => $v) {
                $imgErrors[$k] = str_replace('(item)', "(item $i)", $v);
            }

            $errors = array_merge($errors, $imgErrors);
        }

        if(count($errors) > 0) {
            return response()->json(['success' => false, 'errors' => $errors]);
        }

        // here is the code store the new resource
        //  ...

        // then return success message
    }

4 Comments

honestly, it is a bit crazy this has to be done this way and laravel doesn't have something like :num
I agree with you. This is to much hard code. @Alex, do you know where I could suggest a feature to new versions of Laravel? So maybe new releases of it will have built-in tools to validate dynamic fields
i would guess the official github. if you do I'll add a comment to support it!
@Alex I did ask a feature request to Laravel. Here is [the issue on Github] (github.com/laravel/framework/discussions/32977). If you add a comment to support it I will be thankful
0

You can use the placeholder :second-index or :second-position for nested arrays in Laravel 11.

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.