Luciano
Luciano

Reputation: 2196

Validating array in Laravel using custom rule with additional parameter

I'm working with Laravel 5.7 and I need to validate a phone length by using 2 inputs (prefix+number). The total digits has to be 10 always.

I'm using this custom rule for other projects which works fine:

<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;

class PhoneLength implements Rule
{
    public $prefix;

/**
 * Create a new rule instance.
 *
 * @return void
 */
public function __construct($prefix = null)
{
    //
    $this->prefix = $prefix;
}

/**
 * Determine if the validation rule passes.
 *
 * @param  string  $attribute
 * @param  mixed  $value
 * @return bool
 */
public function passes($attribute, $value)
{
    //
    return strlen($this->prefix)+strlen($value) == 10 ? true : false;
}

/**
 * Get the validation error message.
 *
 * @return string
 */
public function message()
{
    return 'El Teléfono debe contener 10 dígitos (prefijo + número)';
}
}

In my controller I do something like

$validatedData = $request->validate([
  'prefix' => 'integer|required',
  'number' => ['integer','required', new PhoneLength($request->prefix)],
]);

Now I need to make use of arrays, so my new validation looks like

$validatedData = $request->validate([
  'phones.*.prefix' => 'required',
  'phones.*.number' => ['required', new PhoneLength('phones.*.prefix')],
]);

The above code doesn't work at all, the parameter is not being sent as expected. How can I send an array value? Of course I need to get the values from the same array element, so if phones[0].number is under validation, the prefix phones[0].prefix is needed.

I've found this question, but I refuse to believe that is not possible to do in a "native" way: Laravel array validation with custom rule

Thanks in advance

Upvotes: 9

Views: 11017

Answers (3)

AlexanderT
AlexanderT

Reputation: 187

According to this https://github.com/laravel/framework/pull/18654 you can use, it will add your custom rule as dependent and replace asterix with needed indexes

Validator::extendDependent('contains', function ($attribute, $value, $parameters, $validator) {
   // The $parameters passed from the validator below is ['*.provider'], when we imply that this
   // custom rule is dependent the validator tends to replace the asterisks with the current
   // indices as per the original attribute we're validating, so *.provider will be replaced
   // with 0.provider, now we can use array_get() to get the value of the other field.

    // So this custom rule validates that the attribute value contains the value of the other given
    // attribute.
    return str_contains($value, 
            array_get($validator->getData(), $parameters[0])
    );
});


Validator::make(
    [['email' => '[email protected]', 'provider' => 'mail.com']],
    ['*.email' => 'contains:*.provider']
)->validate();

Upvotes: 0

shino47
shino47

Reputation: 213

The Abdeldayem Sherif's answer is good but there is a problem when the attribute has more level of nesting, for example: clients.*.phones.*.prefix. In this case, exploding and using the 1 index will cause an unexpected error. A better solution is using str_replace.

class PhoneLength implements Rule
{
    public function passes($attribute, $value)
    {
        $prefixAttr = str_replace('.number', '.prefix', $attribute);
        $prefix = request()->input($prefixAttr);
    }
}

Upvotes: 1

user10896277
user10896277

Reputation:

You could get $prefix from the request itself:

class PhoneLength implements Rule
{
    public function passes($attribute, $value)
    {
        $index = explode('.', $attribute)[1];
        $prefix = request()->input("phones.{$index}.prefix");
    }
}

or pass the $request in the PhoneLength rule constructor, then use it.

Upvotes: 13

Related Questions