The documentation for required_without states:
required_without:foo,bar,...
The field under validation must be present _only when_ any of the other specified fields are not present.
My expectation of this function from the name and the "only when" in the description is for it to function as an XOR: The validation will only pass if the given value is present, and the other value is NOT present. What I'm seeing in my application, however, is the validation passing when another value IS present.
So, to visualise it, here is the behaviour I expect if I had the rules:
$rules = [
'field_a' => 'required_without:field_b',
'field_b' => 'required_without:field_a',
];
| Field A present | Field B present | Validation passes? |
| --- | --- | --- |
| :white_check_mark: Yes | :white_check_mark: Yes | :red_circle: No |
| :white_check_mark: Yes | :red_circle: No | :white_check_mark: Yes |
| :red_circle: No | :white_check_mark: Yes | :white_check_mark: Yes |
| :red_circle: No | :red_circle: No | :red_circle: No |
And here is what I'm observing - how it's currently behaving:
| Field A present | Field B present | Validation passes? |
| --- | --- | --- |
| :white_check_mark: Yes | :white_check_mark: Yes | :white_check_mark: Yes |
| :white_check_mark: Yes | :red_circle: No | :white_check_mark: Yes |
| :red_circle: No | :white_check_mark: Yes | :white_check_mark: Yes |
| :red_circle: No | :red_circle: No | :red_circle: No |
Is this a problem? Or am I simply misunderstanding the validation rule (I apologise for wasting your time if I am!)?
required_without is just a "weak" version of required,
If smth passes required then it should pass the required_without too.
So I guess the current behavior is the intendent one.
The current behavior is expected. If the field that's validated by required_without is present, it will pass anyways. Basically it's like required but the rule is only applied if the other value is not set.
Ok I understand. Thank you for explaining.
I thought it was weird how this rule works, it doesn't seem to make sense to be honest. But I solved this problem, for future google explorers, like this
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Validator::extend('required_xor', function($attribute, $value, $params, $validator){
$values = $validator->getData();
$intersect = array_intersect_key(array_flip($params), $values);
return empty($intersect);
}, 'You can only provide :field when the :xor_fields are not provided');
Validator::replacer('required_xor', function($message, $attribute, $rule, $params){
$message = str_replace(':field', $attribute, $message);
$message = str_replace(':xor_fields', implode(',', $params), $message);
return $message;
});
}
public function boot()
{
Validator::extend('required_xor', function($attribute, $value, $params, $validator){
$values = $validator->getData();
$intersect = array_intersect_key(array_flip($params), $values);return empty($intersect); }, 'You can only provide :field when the :xor_fields are not provided'); Validator::replacer('required_xor', function($message, $attribute, $rule, $params){ $message = str_replace(':field', $attribute, $message); $message = str_replace(':xor_fields', implode(',', $params), $message); return $message; });
1st message I got was that no such methods as extend and replacer exist on the validator object. A quick look at the class Illuminate\Validation\Validator revealed the likeliest method name similar to yours is addReplacer and addExtension. After calling them, I got methods not static. After trying to inject it, I got the error "Unresolvable dependency resolving [Parameter #1 [
Ah, I didn't think to put this initially, but in my code I use the facade, I've updated the code in my comment to reflect that. Sorry about that. Try again?
Ah, I didn't think to put this initially, but in my code I use the facade, I've updated the code in my comment to reflect that. Sorry about that. Try again?
Many thanks for your prompt response. A little comment here and there doesn't hurt though.
For those wondering how it works: https://laravel.com/docs/5.8/validation#using-extensions
Validator::extend('required_xor', function($attribute, $value, $params, $validator){
$values = $validator->getData(); // [field_name=>'foo',field2 => 'bar']
$intersect = array_intersect_key(array_flip($params), $values); // ([field_name=> 0], $values)
return empty($intersect); // no clashes
}, 'You can only provide :field when the :xor_fields are not provided'); // Add placeholders we'll replace later
Ah, I didn't think to put this initially, but in my code I use the facade, I've updated the code in my comment to reflect that. Sorry about that. Try again?
I've just realized that while the validator works for situations where both fields are set, it (incorrectly) when neither is set. Fixed with an extra condition:
Validator::extend('required_xor', function($attribute, $value, $params, $validator) {
$values = $validator->getData(); // [field_name=>'foo',field2 => 'bar']
$containsNeither = empty($values[$attribute]) && !in_array($params[0], $values); // assumes you're excluding only one field
$containsBoth = array_intersect_key(array_flip($params), $values); // ([field_name=> 0], $values)
return empty($containsBoth) && !$containsNeither; // no clashes
}, 'You can only provide :field when the :xor_fields are not provided'); // Add placeholders we'll replace later
I can't believe something so simple will turn out this convoluted to implement. The solution in my reply above doesn't correctly account for when the checked field is filled. This current one attempts to fix that. I doubt I recommend using it in an actual because there is no need for a such a complex solution to a very trivial validation. It is welcome to edition and review btw
Validator::extend('required_xor', function($attribute, $value, $params, $validator){
$values = $validator->getData(); // [field_name=>'foo',field2 => 'bar']
$selfEmpty = empty($values[$attribute]);
$containsNeither = $selfEmpty && !in_array($params[0], array_keys($values)); // assumes you're excluding only one field
$containsBoth = array_intersect_key(
array_flip($params), array_filter($values) // remove fields set to null
); // ([field_name=> 0], $values)
return (empty($containsBoth)|| $selfEmpty) && !$containsNeither; // no clashes and counterpart is filled
}, 'You can only provide :field when the :xor_fields are not provided'); // Add placeholders we'll replace later
Wow, great work, I'm not working on this project right now cause I'm travelling, but I'll be back and I'll check it out when I can, great job!
I'm currently using Lumen and created a custom request based on Laravel's FormRequest.
First I created a new Rule named NotPresent
class NotPresent implements Rule
{
private $with;
​
/**
* NotPresent constructor.
*/
public function __construct(?string $with=null)
{
$this->with = $with;
}
​
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return false;
}
​
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
if (empty($this->with)) {
return 'The :attribute must not be present';
}
return 'The :attribute must not be present with '.$this->with.' attribute';
}
}
then I created a sometimes validation overriding in my validation Request the getValidatorInstance method from CustomRequest (aka FormRequest on Laravel)
protected function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
$validator->sometimes('context_name', [new NotPresent('context url')], function ($input) {
$context = $input->get('context_url');
return !empty($context);
});
$validator->sometimes('context_url', [new NotPresent('context name')], function ($input) {
$context = $input->get('context_name');
return !empty($context);
});
return $validator;
}
Most helpful comment
I thought it was weird how this rule works, it doesn't seem to make sense to be honest. But I solved this problem, for future google explorers, like this