In 5.2.x, I could make the following rules:
./artisan tinker
Psy Shell v0.7.2 (PHP 7.0.13-1+deb.sury.org~trusty+1 — cli) by Justin Hileman
>>> $rules = ['a' => 'boolean', 'b' => 'string|required_if:a,true'];
=> [
"a" => "boolean",
"b" => "string|required_if:a,true",
]
The dependency worked:
>>> Validator::make(['a' => true], $rules)->errors()->all();
=> [
"The b field is required when a is 1.",
]
And null
values where accepted too:
>>> Validator::make(['a' => null], $rules)->errors()->all();
=> []
>>> Validator::make(['b' => null], $rules)->errors()->all();
=> []
Starting with the same ruleset:
./artisan tinker
Psy Shell v0.7.2 (PHP 7.0.13-1+deb.sury.org~trusty+1 — cli) by Justin Hileman
>>> $rules = ['a' => 'boolean', 'b' => 'string|required_if:a,true'];
=> [
"a" => "boolean",
"b" => "string|required_if:a,true",
]
The dependency works:
>>> Validator::make(['a' => true], $rules)->errors()->all();
=> [
"The b field is required when a is 1.",
]
But null
values are not accepted anymore:
>>> Validator::make(['a' => null], $rules)->errors()->all();
=> [
"The a field must be true or false.",
]
>>> Validator::make(['b' => null], $rules)->errors()->all();
=> [
"The b must be a string value.",
]
Nullable Primitives
When validating arrays, booleans, integers, numerics, and strings, null will no longer be considered a valid value unless the rule set contains the new nullable rule:
Validate::make($request->all(), [
'field' => 'nullable|max:5',
]);
So I added nullable
:
>>> $rules = ['a' => 'nullable|boolean', 'b' => 'nullable|string|required_if:a,true'];
=> [
"a" => "nullable|boolean",
"b" => "nullable|string|required_if:a,true",
]
But then, the dependency doesn't work anymore:
>>> Validator::make(['a' => true], $rules)->errors()->all();
=> []
null
values are accepted again, however:
>>> Validator::make(['a' => null], $rules)->errors()->all();
=> []
>>> Validator::make(['b' => null], $rules)->errors()->all();
=> []
What is the solution to achieve the same effect in 5.3 as in 5.2?
This behaviour is caused by the fact that if b
doesn't exist in the request, it is considered to be null
and since b
is specified as nullable
the validator ignores this field altogether and doesn't return any errors.
Check out the isValidatable
method on Illuminate\Validation\Validator
to fully understand it.
That said, I can't think of a set of rules which would provide the same behaviour as in 5.2.
One way this would work, if you specify b
only as required_if:a,true
.
Test cases you provided which are:
['a' => true],
['a' => null],
['b' => null],
would produce following errors:
The b field is required when a is 1.
<passes>
<passes>
If you don't really need the string
validation, this would be the way to go.
Thanks for the reply
Check out the isValidatable method on Illuminate\Validation\Validator to fully understand it.
I actually did, that's why I turned here because I realized that the behaviour with the nullable
is intrinsically not compatible with required_if
in that way.
If you don't really need the string validation, this would be the way to go.
I require this.
Guess I need to come up with another way to fix this. Current options I'm exploring:
$validator->sometimes()
can replace somehow the nullable|required_if
-problemsee if $validator->sometimes() can replace somehow the nullable|required_if-problem
You can fiddle with that, but I think, even if you succeed, the validation rules would be quite unreadable.
I would probably create a custom validation rule. That way you're not dependant on the built-in logic(which might change once again) and you can perfectly describe your use case with this bespoke rule.
I would probably create a custom validation rule. That way you're not dependant on the built-in logic(which might change once again) and you can perfectly describe your use case with this bespoke rule.
Thanks, yes, this sounds like a good idea.
I was thinking about this but actually I'm not sure how to approach this particular case of the dependent fields.
I made a most simplistic case with field b depending on a. In reality, I've to fields (b and c) with their own validation rules and both also having required_if=a,true
.
How can I make a custom rule which spans multiple fields? I wrote custom validation rules but I only always see them dealing with a single field.
hmmm if nullable is an acceptable value then I guess the Validator should be ok about it as a value and never checks the required
rule, if the value is null and you say it's ok to be null then why would it ever fail the required rule.
I guess using $validator->sometimes('required', function(){ return request('a') })
is what you need, no?
I think my previous were not good enough, here's another which better highlights the problem with the change made for 5.3:
>>> $rules = ['a' => 'boolean', 'b' => 'integer|required_if:a,true'];
=> [
"a" => "boolean",
"b" => "integer|required_if:a,true",
]
>>> Validator::make(['a' => false, 'b' => null], $rules)->errors()->all()
=> [
"The b must be an integer.",
]
Why would b
required to be an integer, if it has a required_if
rule.
I guess in 5.2, absent fields and null
values were treated in a more similar way, which changed in 5.3.
Perhaps good on one side, but not sure of the other one.
The only economical solution I found so far is to:
nullable
for fields which have any of the required_*
rulesrequired_*
with a custom one, which accepts null
tooE.g. if my rule is supposed to be string|required_if:a,true
I need to:
my_string
rule, which works like the built-in string
but returns true
for null
my_string|required_if:a,true
Since I've quite some required_*
based rules, I ended up creating such shims for string, integer, boolean, min, max, date_format, in, after ...
At this point it's arguable if I should just have extended the Validator class itself, but
required_*
directives. Although it seems much, 90% of my validation does not include them so it makes sense to leverage the existing Laravel infrastructure as much as possibleThis is now a perspective from a mid-sized project I guess, which currently has >1600 tests.
When I implemented the first parts roughly a year ago, on 5.1, the implicit acceptance of null
for rules like string
felt weird (after all, this is a pure Json API based project), but over time I saw it's sense and I only needed a few custom rules to make it through.
Now after 5.3, only for this update I had to write (shim) more custom rules than we ever had before.
This is not the only thing, but something (gut feeling) tells me although the change from 5.2 to 5.3 looks good on paper (even to me!), in practice especially the nullable
+required_*
combination fells somehow wrong. There were other odd things but this one stood out.
I agree with @mfn. You may want to re-open this issue as I think it needs to be addressed as well.
When using the required_without
validation rule, the documentation states the following:
The field under validation must be present and not empty _only when_ any of the other specified fields are not present.
Note that _only_when_ is emphasized in the above sentence.
Now, when the field is not required because the required_without
condition is false, then it means (according to the documentation) that the field _must not_ be present and _may_ be empty. If this is combined with other validation rules (like numeric
), it may lead to a condition that states that a field _may_ be empty but _must_ be an number. This means that the field _must_ be a number and intrinsically is not empty.
Using nullable
in this case is not an option, as it will defeat the purpose of the required_without
rule.
Using sometimes
is not an option either, as the field may be present with a null value and will therefore trigger the validation rules on that field.
I agree with @mfn. You may want to re-open this issue as I think it needs to be addressed as well.
I agree.
I closed it because there was not much movement going on and I found an economically viable workaround.
I think your example with required_without
seems even better to show the problem of the new Validator design.
@lowerends Can you share a code that should pass but fails or fails but passes? I don't seem to understand the issue here, what are you trying to do?
This passes:
Validator::make(
["username" => null],
['username' => 'nullable|required_without:email']
);
This fails:
Validator::make(
["username" => null],
['username' => 'required_without:email']
);
Which is expected, where's the issue?
@themsaid Let's say you want a user to enter at least a username or an email address.
This correctly fails, as I want one of the fields to be required:
Validator::make(
[
'username' => null,
'email' => null
],
[
'username' => 'required_without:email',
'email' => 'required_without:username|email',
]
);
This fails as well, as the email needs to be an email field:
Validator::make(
[
'username' => 'test',
'email' => null
],
[
'username' => 'required_without:email',
'email' => 'required_without:username|email',
]
);
So in order to solve this now, you need to add nullable
, which correctly passes:
Validator::make(
[
'username' => 'test',
'email' => null
],
[
'username' => 'nullable|required_without:email',
'email' => 'nullable|required_without:username|email',
]
);
But then the following incorrectly passes again, as I want one of the fields to be required:
Validator::make(
[
'username' => null,
'email' => null
],
[
'username' => 'nullable|required_without:email',
'email' => 'nullable|required_without:username|email',
]
);
By introducing the nullable
validation rule, there is no way to require at least the username or the email address field without writing custom validation logic. Does this example clarify the issue?
ahhh, now I see. In the original case:
Validator::make(
[
'username' => 'test',
'email' => null
],
[
'username' => 'required_without:email',
'email' => 'required_without:username|email',
]
);
The problem is that you expect the email
rule not to run if the required_without
check fails, so that if the field is not required then we stop validating further rules, right?
But this is actually not possible, the email rule knows nothing about the previous required_without rule result so it'll run even if the field is not required.
The way to go is to use sometimes
$validator->sometimes('email', 'email', function(){
return ! request('username');
})
This way the email rule won't run unless there's no username given.
So it still requires a custom validation function?
Using sometimes
, the following still fails:
Validator::make(
[
'username' => 'test',
'email' => null
],
[
'username' => 'required_without:email',
'email' => 'sometimes|required_without:username|email',
]
);
sometimes()
is not a custom function, it's built into the Validator for these cases of complex conditions. It's very useful for situations like this.
I'm closing this issue since it's not actually an issue in the core, please check the sometimes()
method and give it a go & feel free to ping me if you need any help.
Here's a workaround from #17032, in case it helps anyone else. In this example we want optional_value
to be required if some_bool
is truthy:
$rules = [
'optional_value' => 'boolean',
];
$rules['optional_value'] .= $request->input('some_bool') ? '|required' : '|nullable';
$this->validate($request, $rules);
For anyone who's still pulling their hair out with this, you can just create a custom validator class, see gist:
We've just updated from Laravel 5.2 to 5.5 and come across this issue, however I believe I was able to get around it using the bail
rule.
Taking the example in first post
"a" => "boolean",
"b" => "string|required_if:a,true",
becomes
"a" => "boolean",
"b" => "bail|required_if:a,true|nullable|string",
My understanding is it'll check if a
is set to true
first, if so then the field is required and a null (or empty as per docs) value won't work for b
. If that passes, it moves on to the next rule which allows a null
value (doesn't actually do any validation), this only applies when a
is not true
as otherwise the rules contradict each other. Finally if it reaches the third rule, it'll check that b
is a string.
Note that it'll check if b
is a string if it's not null
regardless of the value of a
, but this replicates how it worked before.
Hope this helps someone!
Edit: In hindsight, I'm not sure the bail
rule is even needed, it's more the ordering of the rules. This seems to work for the cases that we have that were no longer working after the update.
For anyone running into this issue still. I solved this using exclude_if
Using the example above
"a" => "boolean",
"b" => "exclude_if:a,false|string|required_if:a,true",
My exact usage
If the is_upgrade checkbox is checked, then I want to require the upgrade price.
'is_upgrade' => 'required|boolean',
'upgrade_price' => 'exclude_if:is_upgrade,false|required_if:is_upgrade,true|numeric',
Most helpful comment
The only economical solution I found so far is to:
nullable
for fields which have any of therequired_*
rulesrequired_*
with a custom one, which acceptsnull
tooE.g. if my rule is supposed to be
string|required_if:a,true
I need to:my_string
rule, which works like the built-instring
but returnstrue
fornull
my_string|required_if:a,true
Since I've quite some
required_*
based rules, I ended up creating such shims for string, integer, boolean, min, max, date_format, in, after ...At this point it's arguable if I should just have extended the Validator class itself, but
required_*
directives. Although it seems much, 90% of my validation does not include them so it makes sense to leverage the existing Laravel infrastructure as much as possibleThis is now a perspective from a mid-sized project I guess, which currently has >1600 tests.
When I implemented the first parts roughly a year ago, on 5.1, the implicit acceptance of
null
for rules likestring
felt weird (after all, this is a pure Json API based project), but over time I saw it's sense and I only needed a few custom rules to make it through.Now after 5.3, only for this update I had to write (shim) more custom rules than we ever had before.
This is not the only thing, but something (gut feeling) tells me although the change from 5.2 to 5.3 looks good on paper (even to me!), in practice especially the
nullable
+required_*
combination fells somehow wrong. There were other odd things but this one stood out.