Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] Add ValidatorAwareRule interface #37442

Merged
merged 2 commits into from May 21, 2021
Merged

[8.x] Add ValidatorAwareRule interface #37442

merged 2 commits into from May 21, 2021

Conversation

bastien-phi
Copy link
Contributor

In #36960 Nuno added a DataAwareRule interface which allows a Rule to have access to the data under validation. This is so great !

Unfortunately, it does not allows the Rule to gain access to :

  • 1 - does the current field already have validation errors ?
  • 2 - what are the other rules applied to the current field ?
  • 3 - does other fields has validation errors ?
  • 4 - what are the rules applied to other fields ?

Why do we need this ? I will give it some examples but possibilities are limitless

1 - If we want to check some model existence with some custom condition
Actually

$rules = [
   'user_id' => [
        'required',
        'integer',
        Rule::exists(User::class)
        ->using(fn ($query) => (new User)->newEloquentBuilder($query)->whereIsAuthor())
    ]
]

or

$rules = ['user_id' => ['required', 'integer', 'bail', AuthorExists::class]];

class AuthorExists implements Rule 
{
    public function passes($attribute, $value)
    {
        return User::where('id', $value)->whereIsAuthor()->exists();
    }
}

Here the bail is required to ensure that AuthorExists::passes is not executed with unsafe data.
After

$rules = ['user_id' => ['required', 'integer', AuthorExists::class]];

class AuthorExists implements Rule, ValidatorAwareRule
{
    private $validator;

    public function setValidator($validator)
    {
        $this->validator = $validator;

        return $this;
    }


    public function passes($attribute, $value)
    {
        if ($this->validator->errors()->has($attribute)) {
            return true; 
        }

        return User::where('id', $value)->whereIsAuthor()->exists();
    }
}

The bail here in included directly in the rule, which matches what is done with exists rule (see https://github.com/laravel/framework/blob/8.x/src/Illuminate/Validation/Validator.php#L657 and https://github.com/laravel/framework/blob/8.x/src/Illuminate/Validation/Validator.php#L733)

2 - We may want the rule applied in different ways depending on the previous defined rules.
Which is the way that size, min, ... actually work.
Another use case would be to fetch the date_format in order to have create a DateTime and make assertions about this :

class Monday implements Rule, ValidatorAwareRule
{
    public function passes($attribute, $value)
    {
        if (!$this->validator->hasRule($attribute, 'DateFormat')) {
             throw new Exception("The $attribute must be validated with date_format.");
        }
        if ($this->validator->errors()->has($attribute)) {
            return true;
        }
        // extract  format.
        // this part would be easier if Validator::getRule($attribute, $rules) was public but not in scope of this PR
        [, $params] = collect($v->getRules()[$attribute])
                                    ->map(fn ($rule) => ValidationRuleParser::parse($rule))
                                    ->first(fn ($items) => $items[0] === 'DateFormat'); 
        
         return Date::createFromFormat($params[0], '2021-05-21')->dayOfWeek === Carbon::MONDAY;
    }
}

3 - 4 - Imagine we are building a booking service and we want to validate that the location is available a given day.

With this PR we could simply do something like this :

$rules = [
    'date' => ['required', 'date_format:Y-m-d'],
    'location_id' => ['required', 'integer', Available::class],
];

class Available implements Rule, ValidatorAwareRule 
{
    public function passes($attribute, $value)
    {
        if ($this->validator->errors()->hasAny(['date', $attribute])) {
            return true; 
        }

        return Location::where('id', $value)->whereAvailableOn($this->validator->getData()['date'])->exists();
    }
}

TL;DR : this PR adds only a few lines in the framework but I believe opens up great possibilities and power to custom Rules

@taylorotwell taylorotwell merged commit f80e1e5 into laravel:8.x May 21, 2021
@bastien-phi
Copy link
Contributor Author

Thanks !

@bastien-phi bastien-phi deleted the validator_aware_rule branch May 21, 2021 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants