Laravel custom validation rule refering to other request params
Asked Answered
B

4

8

I'm trying to create a custom validation rule that accept a parameter, but this parameter is the name of another field in the request, like for the required_with rule.

I easily can handle given params in my rule, but i'm struggling to find out how to retrieve the other field value.

Currently i'm creating my rule class as

class MyClassRule
{
    public function validate($attribute, $value, $parameters, $validator) : bool
    {
        // do some stuff here to return true/false
    }
}

and registering it in my service provider with

Validator::extend('my_rule', 'path\to\MyClassRule@validate');

so i can use it in my request as

public function rules()
{
    return [
        'field' => ['my_rule'],
    ];
}

What i would like to be able to do is

public function rules()
{
    return [
        'other_field' => [...],
        'field'       => ['my_rule:other_rule'],
    ];
}

and use the other_field value in my rule class, but validate()'s $parameters value is just ['other_field']. i.e. an array containing the other field name, not its value.

How can i do this?

Burgee answered 21/10, 2019 at 12:54 Comment(0)
C
9

I'm running this in Laravel 7.x.

In my case, I am trying to make a rule to compare whether two field in my form is equal to one another.

Let's make a new Rule Object as instructed from Laravel's documentation.
https://laravel.com/docs/7.x/validation#custom-validation-rules


Below is the console command to make the Rule class template.
php artisan make:rule StrEqualTo

Below is the generated custom Rule class with the full implementation of the logic.

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class StrEqualTo implements Rule{
    private $referredField = null;

    public function __construct(string $referredField){
        $this->referredField = $referredField;
    }

    public function passes($attribute, $value){
        return request()->input($this->referredField) === $value;
    }

    public function message(){
        return 'The :attribute must match with' . $this->referredField . '.';
    }
}

We first create a private attribute and a constructor with a parameter, the parameter will accept the 'name' attribute of the field you want to refer. We then assign the value from the constructor parameter to our private attribute in our rule class.

private $referredField = null;

public function __construct(string $referredField){
        $this->referredField = $referredField;
}

As stated in Laravel's docs, this function must return true if the validation succeeds, otherwise it must return false. What we do here is to use the request() helper function provided by Laravel and get the value of the field we referred from the form input($this->referredField).

public function passes($attribute, $value){
        return request()->input($this->referredField) === $value;
}

We can edit the error message it will create when the validation failed in this function below.

public function message(){
        return 'The :attribute must match with' . $this->referredField . '.';
}

We then instantiate the custom Rule class to an object to be used as validation rule like the code below. 'confirm-new-pass' => ['required', 'string', 'max:100', new StrEqualTo('new-pass')]

Hope this helps!!!

Chickweed answered 18/5, 2020 at 15:53 Comment(0)
O
4

Because $validator is a full instance of the Validator object being used, we can retrieve data from it using getData():

public function validate($attribute, $value, $parameters, $validator)
{
    // You may want to check to make sure this exists first.
    $otherField = $parameters[0];

    $otherValue = data_get($validator->getData(), $otherField);

    // @todo Validate $otherValue
}

Using data_get() allows you to use dot notation for nested array values as well.

Outwardly answered 21/10, 2019 at 14:32 Comment(1)
Ok, this looks perfect, didn't know anything about the $validator param, none of the examples/tutorials i found used it.Burgee
P
4

Artisan command

php artisan make:rule ValidateOtherField

Class ValidateOtherField

class ValidateOtherField implements Rule
{
        private $error = '';
        public function passes($attribute, $value)
        {
            if(request()->has('field') && request()->get('field') === 'MyValueSuccess'){
                if(is_string($value)){
                    return true;
                } else {
                    $this->error = '- not valid field';
                }
            }
            return false;
        }
        public function message()
        {
            return "Error :attribute {$this->error}";
        }
 }

rules

public function rules()
{
    return [
        'field'       => ['string'], //Validate field
        'other_field' => [new ValidateOtherField],        
    ];
}
Proprioceptor answered 21/10, 2019 at 14:35 Comment(3)
Ok, so i have to implement the Rule interface, but i can't see in the passes() method the $parameters parameter as in my validate() method: this way i can't pass my other_field name as a param to the validation rule. Moreover, with the passes() method am i still able to register my custom rule in my service provider as i already do?Burgee
In Laravel 6.x request()->get('field') from a Rule class doesn't appear to work anymore. Or at least the request()->attributes ParameterBag only hold and empty array, when accessed under PhpUnit.Serviette
I can confirm that it does work in Laravel 8, but it does not work in PhpUnit.Edp
C
2

You can easily implement the Illuminate\Contracts\Validation\DataAwareRule interface in your custom rule. Laravel will inject all the request parameters into your class's $data property.

https://laravel.com/docs/10.x/validation#using-rule-objects (Accessing Additional Data section)

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\Rule;

class YourCustomRule implements Rule, DataAwareRule
{
    /**
     * All the data under validation.
     *
     * @var array
     */
    protected $data = [];

    /**
     * Set the data under validation.
     *
     * @param  array  $data
     * @return $this
     */
    public function setData($data)
    {
        $this->data = $data;

        return $this;
    }

    public function passes($attribute, $value)
    {
        $anotherAttributeValue = $this->data['another_attribute_name'];

        // do your job ...

    }

    public function message()
    {
        return 'your message';
    }
}
Cupped answered 18/3, 2023 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.