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

How can we access the value inside the callback of Rule::when(), when the field is an array. #38865

Closed
itxshakil opened this issue Sep 19, 2021 · 3 comments

Comments

@itxshakil
Copy link

  • Laravel Version: 8..55.0
  • PHP Version: 7.4.9
  • Database Driver & Version:

Description:

How can we access the value inside the callback of Rule::when(), when the field is an array.

  • The problem is we don't know the iteration(key), so we can't access it from request.

I guess we may pass the current value as the second parameter to the callback.

$validator = Validator::make($request->all(), [
'openings.*.from' => ['required_with:openings.*.id', 'date_format:H:i'],
'openings.*.to' => ['required_with:openings.*.id', Rule::when(function($a){
// Need the current value for conditional check
},['date_format:H:i']), 'after:openings.*.from'],
]);

If we wanted to add validation(date_format:H:i) on the basis of the current value of openings's to.

But I can not access the value of current openings to.

Originally posted by @itxshakil in #38361 (comment)

Steps To Reproduce:

  • Add an array field for validation (For eg. : request()->validate([ 'abc.*' => ['required'] ]);
  • Try to add conditional validation on the basis of the value of field.
@derekmd
Copy link
Contributor

derekmd commented Sep 21, 2021

The Rule::when() closure argument is only given the $validator->getData() / $request->all() payload so the nested array key context isn't available. The code that invokes the closure also doesn't have that context available since the Rule::when() filter happens before the request's nested array payload is traversed during validation.

public static function filterConditionalRules($rules, array $data = [])
{
return collect($rules)->mapWithKeys(function ($attributeRules, $attribute) use ($data) {
if (! is_array($attributeRules) &&
! $attributeRules instanceof ConditionalRules) {
return [$attribute => $attributeRules];
}
if ($attributeRules instanceof ConditionalRules) {
return [$attribute => $attributeRules->passes($data)
? array_filter($attributeRules->rules())
: array_filter($attributeRules->defaultRules()), ];
}
return [$attribute => collect($attributeRules)->map(function ($rule) use ($data) {
if (! $rule instanceof ConditionalRules) {
return [$rule];
}
return $rule->passes($data) ? $rule->rules() : $rule->defaultRules();
})->filter()->flatten(1)->values()->all()];
})->all();
}


// Need the current value for conditional check

This is the most important part of your question that has been excluded. What is the conditional you need to check? Some other validation features may cover the requirement.

  1. Use one of the required_with* rules such as required_with_all:openings.*.id,openings.*.from

  2. Create a ValidatorAwareRule rule object that will be given the nested array key being validated and can also access the full data payload: https://laravel.com/docs/8.x/validation#using-rule-objects

    $validator = Validator::make($request->all(), [
        'openings.*.from' => [
            'required_with:openings.*.id',
            'date_format:H:i'
        ],
        'openings.*.to' => [
            'required_with:openings.*.id',
            new DateRangeEndRule,
        ],
    ]);
    use Illuminate\Contracts\Validation\Rule;
    use Illuminate\Contracts\Validation\ValidatorAwareRule;
    
    class DateRangeEndRule implements Rule, ValidatorAwareRule
    {
        public function setValidator($validator)
        {
            $this->validator = $validator;
        }
    
        public function passes($attribute, $to)
        {
            // $attribute is 'openings.0.to', 'openings.1.to', etc.
    
            $key = preg_replace('/\.to$/', '.from', $attribute);
            $from = $this->validator->getData()[$key] ?? null;
    
            if (from === null) {
                 return true;
            }
    
            // compare $from and $to, then conditionally run validation
            // for 'date_format:H:i' & 'after:openings.*.from'
        }
    
        public function message()
        {
            // ...
        }
    }

@itxshakil
Copy link
Author

Thanks, it solved the issue.
I was not aware that the code that invokes the closure also doesn't have that context available since the Rule::when() filter happens before the request's nested array payload is traversed during validation.

@driesvints
Copy link
Member

Thanks @derekmd

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

No branches or pull requests

3 participants