johncarter's avatar

Change validation message :attribute placeholder for array inputs

I have a nested array of inputs being posted to a controller.

$validatedData = request()->validate([
    'line.*.image' => 'image|max:500',
    'line.*.description' => 'required',
    'line.*.retail_price' => 'min:0|required',
    'line.*.quantity' => 'min:1|required',
]);

When validation fails I receive an error message that looks like this:

The line.0.retail_price field is required.

How do I replace that to read something like:

The retail price on line 1 is required.

0 likes
9 replies
bobbybouwmann's avatar

You can do that like so in your resources/lang/en/validation.php file

'custom' => [
    'line.*. retail_price' => [
        'required' => 'The retail price is required.',
        ],
],

As far as I know there is no way to get the index of the validation at this point.

There are ways to dynamically add validation messages to validation request, but that is something you have to build customly.

johncarter's avatar

Thanks @bobbybouwmann - the index is what I really need because the UI doesn't really lend itself to inline error messages.

Do you have any recommended reading on building it bespoke?

For now I will hack a JS string manipulation until I get the custom validation working.

tykus's avatar

You could use a Closure validation rule, calculating the position inside:

'line.*.retail_price' => function ($attribute, $value, $fail) {
    [$group, $position, $name] = explode('.', $attribute);
    $fieldName = str_replace('_', $name);

    // required rule
    if (!$this->validateRequired($attribute, $value)) {
        $fail('The ' . $fieldName . ' on line ' . $position + 1 . ' is required.');
    }


    // min rule
    $minValue = 0;
    if (!$this->validateMin($attribute, $value, [$minValue])) {
        $fail('The ' . $fieldName . 'on line ' . $position + 1 . ' must be at least ' . $minValue);
    }
},

Not as easy to consume, certainly, but it will produce the required validation error messages.

1 like
johncarter's avatar

@sti3bas and @tykus both these solutions look like great options.

I am quite new to Laravel, so could you just tell me where the code is dropped in.

@tykus I presume the closure goes like this, if so I still get the old message.

$validatedData = request()->validate([
    //...
    'line.*.description' => 'required',
    'line.*.retail_price' => function ($attribute, $value, $fail) {
        [$group, $position, $name] = explode('.', $attribute);
        $fieldName = Str::replace('_', $name);
    
        // required rule
        if (!$this->validateRequired($attribute, $value)) {
            $fail('The ' . $fieldName . ' on line ' . $position + 1 . ' is required.');
        }
    
    
        // min rule
        $minValue = 0;
        if (!$this->validateMin($attribute, $value, [$minValue])) {
            $fail('The ' . $fieldName . 'on line ' . $position + 1 . ' must be at least ' . $minValue);
        }
    },
    // ...
]);

I read https://laravel.com/docs/6.0/validation#using-closures but not sure that docs method works when using $validatedData = request()->validate([//**]);

tykus's avatar

Whenever validation rules begin to look a little bit complicated, I tend to extract a FormRequest. Remember you will need this approach for every rule covering nested inputs, in your case, line.*.image, line.*.description, line.*.retail_price and line.*.quantity. That is going to make the Controller method look very busy.

johncarter's avatar
johncarter
OP
Best Answer
Level 3

Thanks for everyone's help. I managed to get it working by:

php artisan make:request StoreLines

Then adding the following method:

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreLines extends FormRequest
{

    public function rules()
    {
        return [
            // Your rules... e.g. 'line.*.description' => ['required', 'max:255'],
        ]
    }

    public function messages()
    {
        $messages = [];
        foreach ($this->request->get('line') as $line => $requestData) {
            foreach ($requestData as $input => $value) {
                $messages['line.' . $line . '.' . $input . '.required'] = 'The bloody ' . str_ireplace('_', ' ', $input) . ' on line ' . ($line + 1)  . '  is missing';
                $messages['line.' . $line . '.' . $input . '.min'] = 'The ' . str_ireplace('_', ' ', $input) . ' on line ' . ($line + 1)  . '  is must be a minimum of :min.';
            }
        }
        return $messages;
    }
}

One thing that is really irritating is that the error message array gets modified after it is returned and loses its order, so not all errors per line are grouped. But it's not that important.

1 like
Munesh's avatar

How to error message change public function collection(Collection $rows) {

      Validator::make($rows->toArray(), [
         '*.price' => 'numeric',
     ])->validate();

} Errro Message The 1.price must be a number. I need Row No 1.price must be a number.

Please or to participate in this conversation.