Input validation exclusive or - php

I have a model called Answer.
This model has two possible relations. A Question or a Product.
But, Answer should only ever have one relation. Either Question or Product.
There is a form to create answers.
This form has three inputs. two of them are <select> inputs. The other is a text input, called name.
I want my validation to only allow one to be filled out.
My current validation:
$validator = Validator::make(
Input::all(),
array('name' => array('required'))
);
$validator->sometimes('product', array('required', 'numeric'), function ($input) {
return !is_numeric($input->question);
});
$validator->sometimes('question', array('required', 'numeric'), function ($input) {
return !is_numeric($input->product);
});
Requires at least one of the selected to be filled out, but will also allow two.
So, my question is: How can I change my validation to only allow one of the selects to be filled out.
But one of them must always be filled out.
Select 1:
<select name="question" class="form-control">
<option>None</option>
<option value="1" selected="selected">Question 1</option>
<option value="2">Question 2</option>
</select>
Select 2:
<select name="product" class="form-control">
<option>None</option>
<option value="2" selected="selected">Product 1</option>
<option value="3">Product 2</option>
</select>

#Razor's custom XOR validation rule is pretty nice, but there's another route if you don't want to create a custom rule. You can add values to Input representing your constraints, using Input::merge, then use those for validation:
Input::merge(array(
'hasBoth' => Input::has('product') && Input::has('question'),
'hasNeither' => !Input::has('product') && !Input::has('question')
));
$validator = Validator::make(
Input::all(), array(
'name' => 'required',
'hasNeither' => 'size:0',
'hasBoth' => 'size:0',
), array(
'hasNeither.size' => 'A question or a product is required.',
'hasBoth.size' => 'You cannot choose both a question and a product.'
)
);
You should still change the empty value in your form to <option value=''>None</option>.
Unlike using an XOR comparison, this method allows you to return separate error messages for no values vs. two values without any further error checking.

First off. You must specify an empty value in "None" option:
<option value=''>None</option>
You are looking for XOR operator, you need to create a custom validation rule in this case:
Validator::extendImplicit('xor', function($attribute, $value, $parameters)
{
if( $value XOR app('request')->get($parameters[0]))
return true;
return false;
});
Validator::replacer('xor', function($message, $attribute, $rule, $parameters)
{
// add indefinite articles
$IAattribute = (stristr('aeiou', $attribute[0]) ? 'an ' : 'a ') . $attribute;
$parameters[0] = (stristr('aeiou', $parameters[0][0]) ? 'an ' : 'a ') . $parameters[0];
if(app('request')->get($attribute))
return 'You cannot choose both '.$IAattribute.' and '.$parameters[0].'.';
return $IAattribute.' or '.$parameters[0].' is required.';
});
$validator = Validator::make(
app('request')->all(),
array('name' => array('required')
,'product' => array('xor:question')
));

The required_without validation rule might be what you want:
The field under validation must be present only when any of the other
specified fields are not present.
Set the question field to required_without:product and the product field to required_without:question.

Answers are a little old now. Today laravel brings some prohibition-rules which will resolve your problems without any hacks.
For your validation that means:
return [
'question' => 'nullable|required_without:product',
'product' => ['nullable',
Rule::prohibitedIf(fn() => $this->question != '')]
Both selects need an item with empty-value, with a text/label you like. Like this:
<select>
<option value="">Foo</option>
</select>
Like this one of the both always need to be set. But its not possible that both are set.
See here: https://laravel.com/docs/9.x/validation#rule-prohibited

Related

Laravel 9 required_if not validating

I'm using Laravel 9 and Livewire 2.0
I have an integer field called 'new_weight' that should validate required if the boolean checkbox 'stripped' is selected. Although 'stripped' is not selected, it's still validating as required. The validation $rules are being hit because if I remove the rule, it doesn't validate at all.
I also recently noticed that if I die and dump '$this->stripped' when the checkbox is selected, it dumps 'true' to the console and not '1', but if I leave unchecked, it dumps '0'--not sure if this matters.
edit.php
...
protected $rules = [
'car_color' => 'required|string',
'new_weight' => 'required_if:stripped,accepted|integer|min:1|max:999',
];
protected $messages = [
'car_color' => 'Please enter car color.',
'new_weight' => 'New Weight Value must be great than 1 but less than 999'
];
...
edit.blade.php
...
<div class="flex items-center h-5">
<input wire:model.defer="stripped" id="stripped" name="stripped"
wire:click="updateStripped"
type="checkbox">
</div>
<div>
<input wire:model.defer="new_weight" id="new_weight" name="new_weight"
type="text"
placeholder="New Vehicle Weight in lbs:">
</div>
...
Since stripped itself is not a rule, I would consider using a custom callback to evaluate the stripped input.
'new_weight' => [
Rule::requiredIf(function () use ($request) {
return $request->input('stripped');
}),
'integer|min:1|max:999'
]
I have this problem too, so I use Rule::when like this and get me same result:
[Rule::when(this->stripped == 'accepted', 'required|integer|min:1|max:999')]
when condition ( this->stripped == 'accepted' ) is true, the rules run
of course if stripped is a check box, you must find out what that's return, so first use dd for that, then replace that with 'accepted' ( I think check box return true and false )
The new_weight field had a default null value in the database. Once I re-migrated with a default empty value, my problem was solved.

Laravel 5.6: Skip validation step if field exists and isn't changed. If field IS changed it is Required - but not unique

I have situation where an admin edits an employee form: The first name, last name, and SSN are required on ADDING an employee. No problems there. Where I have an issue is when EDITING the form. I have no problem validating the SSN as it is a unique field.
'ssn_edit' => 'required|unique:employees,ssn,' . $id
But what I DO have an issue with is the non-unique fields. I don't know how to set the validation to skip by ID when the field is NOT unique. Here is the entire rules section of the FormRequest:
public function rules()
{
$id = $this->input('employee_id');
return [
'first_name' => 'required',
'last_name' => 'required',
'ssn_edit' => 'required|unique:employees,ssn,' . $id
];
}
Obviously - this throws the validation error on first_name and last_name regardless if the field is populated or not.
Any help some of you Laravel gurus can throw my way would be GREATLY appreciated!
There is so many tricks you can do to solve the problem. But I only got two ways..
The first is: Prevent to send your ssn_edit value when you want to edit the employee
example:
<input type="text" value="{{ isset($employee) ? $employee->ssn_edit : old('ssn_edit') }}" #isset($employee) disabled #endisset name="ssn_edit">
public function rules()
{
$id = $this->input('employee_id');
return [
'first_name' => 'required',
'last_name' => 'required',
'ssn_edit' => 'required|sometimes|unique:employees,ssn,' . $id
];
}
The second is: Check your method before validate the employee.. is it POST or PUT, if it's PUT don't add the unique rule in your validation.
Conclusion: The validation will work every time you call the validation, no matter if you edit or add new employee. #CMIIW
You mustn't have error with validating non-unique fields, probably you have wrong edit form, set value attribute to input like
<input type="text" value="{{ $employee->first_name }}" name="first_name">

Laravel input array why do I get null in $request->quantity

After searching similar questions I still didn't get the error
The thing is that I have a form that usually works, but the 10% of the users have this error
array_sum() expects parameter 1 to be array, null given
Form
<select name="quantity[]" class="form-control option-quantity" data-price="100">
<option data-price="100" value="0">0</option>
<option data-price="101" value="1" selected="selected">1</option>
<option data-price="102" value="2">2</option>
</select>
Controller
$validator = Validator::make($request->all(), [
'quantity.*' => 'required|integer',
]);
if ($validator->fails()) {
abort(500);
}
if(count($request->quantity) == 0 || array_sum($request->quantity) < 1)){
//message to select at least a quatity
}
They always put an amount because I did't restrictions, js to avoid nulls or 0 and from the server side not to allow 0
So, as it happens just sometimes, I'm wondering if some explorers do something weird changing the quantity[] to quantity or null or something, I'm driving a bit crazy here and I can't reproduce the error myself and it wouldn't be a good idea to wait the error to happen to a user (:
BTW of course I can add if($request->quantity) to avoid the error, but that not solves the question of why I get a null just sometimes, rest is fine.
As per suggestion after doing dd($request->quantity); I get:
array:1 [
0 => "1"
]
But again, for me its working good, not for the 10%.
You can simply avoid this error by checking quantity.It
if($request->quantity){
if(count($request->quantity) == 0 || array_sum($request->quantity) < 1)){
//message to select at least a quatity
}
}
It is throwing this error because $request->quantity is null;
Hope this helps
Since you have not tagged the version of Laravel, I'll make an assumption that you are using latest version.
why I get a null
Check your validation rule..
When a user submits the data with empty strings, Laravel makes it nullable. Checkout the middleware ConvertEmptyStringsToNull which is located inside Illuminate\Foundation\Http\Middleware folder.
So, if you want it to be always present at the time of submitting the user data, make it required like so.
return [
'quantity' => 'required',
// .. other validation rules..
];
Try removing "[]" from the name on the select, so you will have this:
<select name="quantity" class="form-control option-quantity" data-price="100">
<option data-price="100" value="0">0</option>
<option data-price="101" value="1" selected="selected">1</option>
<option data-price="102" value="2">2</option>
</select>

Laravel Validation Rules If Value Exists in Another Field Array

I am working in Laravel 5.4 and I have a slightly specific validation rules need but I think this should be easily doable without having to extend the class. Just not sure how to make this work..
What I would like to do is to make the 'music_instrument' form field mandatory if program array contains 'Music'.
I found this thread How to set require if value is chosen in another multiple choice field in validation of laravel? but it is not a solution (because it never got resolved in the first place) and the reason it doesn't work is because the submitted array indexes aren't constant (not selected check boxes aren't considered in indexing the submission result...)
My case looks like this:
<form action="" method="post">
<fieldset>
<input name="program[]" value="Anthropology" type="checkbox">Anthropology
<input name="program[]" value="Biology" type="checkbox">Biology
<input name="program[]" value="Chemistry" type="checkbox">Chemistry
<input name="program[]" value="Music" type="checkbox">Music
<input name="program[]" value="Philosophy" type="checkbox">Philosophy
<input name="program[]" value="Zombies" type="checkbox">Zombies
<input name="music_instrument" type="text" value"">
<button type="submit">Submit</button>
</fieldset>
</form>
If I select some of the options from the list of check boxes I can potentially have this result in my $request values
[program] => Array
(
[0] => Anthropology
[1] => Biology
[2] => Music
[3] => Philosophy
)
[music_instrument] => 'Guitar'
Looking at validation rules here: https://laravel.com/docs/5.4/validation#available-validation-rules I think something like his should work but i am literally getting nothing:
$validator = Validator::make($request->all(),[
'program' => 'required',
'music_instrument' => 'required_if:program,in:Music'
]);
I was hoping this would work too but no luck:
'music_instrument' => 'required_if:program,in_array:Music',
Thoughts? Suggestions?
Thank you!
Haven't tried that, but in general array fields you usually write like this: program.*, so maybe something like this will work:
$validator = Validator::make($request->all(),[
'program' => 'required',
'music_instrument' => 'required_if:program.*,in:Music'
]);
If it won't work, obviously you can do it also in the other way for example like this:
$rules = ['program' => 'required'];
if (in_array('Music', $request->input('program', []))) {
$rules['music_instrument'] = 'required';
}
$validator = Validator::make($request->all(), $rules);
I know this post is older but if someone came across this issue again.
$validator = Validator::make($request->all(),[
'program' => 'required',
'music_instrument' => 'required_if:program,Music,other values'
]);
You could create a new custom rule called required_if_array_contains like this...
In app/Providers/CustomValidatorProvider.php add a new private function:
/**
* A version of required_if that works for groups of checkboxes and multi-selects
*/
private function required_if_array_contains(): void
{
$this->app['validator']->extend('required_if_array_contains',
function ($attribute, $value, $parameters, Validator $validator){
// The first item in the array of parameters is the field that we take the value from
$valueField = array_shift($parameters);
$valueFieldValues = Input::get($valueField);
if (is_null($valueFieldValues)) {
return true;
}
foreach ($parameters as $parameter) {
if (in_array($parameter, $valueFieldValues) && strlen(trim($value)) == 0) {
// As soon as we find one of the parameters has been selected, we reject if field is empty
$validator->addReplacer('required_if_array_contains', function($message) use ($parameter) {
return str_replace(':value', $parameter, $message);
});
return false;
}
}
// If we've managed to get this far, none of the parameters were selected so it must be valid
return true;
});
}
And don't forget to check there is a use statement at the top of CustomValidatorProvider.php for our use of Validator as an argument in our new method:
...
use Illuminate\Validation\Validator;
Then in the boot() method of CustomValidatorProvider.php call your new private method:
public function boot()
{
...
$this->required_if_array_contains();
}
Then teach Laravel to write the validation message in a human-friendly way by adding a new item to the array in resources/lang/en/validation.php:
return [
...
'required_if_array_contains' => ':attribute must be provided when ":value" is selected.',
]
Now you can write validation rules like this:
public function rules()
{
return [
"animals": "required",
"animals-other": "required_if_array_contains:animals,other-mamal,other-reptile",
];
}
In the above example, animals is a group of checkboxes and animals-other is a text input that is only required if the other-mamal or other-reptile value has been checked.
This would also work for a select input with multiple selection enabled or any input that results in an array of values in one of the inputs in the request.
The approach I took for a similar problem was to make a private function inside my Controller class and use a ternary expression to add the required field if it came back true.
I have roughly 20 fields that have a checkbox to enable the input fields in this case, so it may be overkill in comparison, but as your needs grow, it could prove helpful.
/**
* Check if the parameterized value is in the submitted list of programs
*
* #param Request $request
* #param string $value
*/
private function _checkProgram(Request $request, string $value)
{
if ($request->has('program')) {
return in_array($value, $request->input('program'));
}
return false;
}
Using this function, you can apply the same logic if you have other fields for your other programs as well.
Then in the store function:
public function store(Request $request)
{
$this->validate(request(), [
// ... your other validation here
'music_instrument' => ''.($this->_checkProgram($request, 'music') ? 'required' : '').'',
// or if you have some other validation like max value, just remember to add the |-delimiter:
'music_instrument' => 'max:64'.($this->_checkProgram($request, 'music') ? '|required' : '').'',
]);
// rest of your store function
}
Here my piece of code to solve that kind of trouble usind Laravel 6 Validation Rules
I tried to use the code above
public function rules()
{
return [
"some_array_field.*" => ["required", "integer", "in:1,2,4,5"],
"another_field" => ["nullable", "required_if:operacao.*,in:1"],
];
}
I need that when some_array_field has 1 in your value, another_field must be validated, otherwhise, can be null.
With the code above, doesn't work, even with required_if:operacao.*,1
If I change the rule for another_field to required_if:operacao.0,1 WORKS but only if the value to find is in index 0, when the order changes, validation fails.
So, I decided to use a custom closure function
Here's the final code for the example that works fine form me.
public function rules()
{
return [
"some_array_field.*" => ["required", "integer", "in:1,2,4,5"],
"another_field" => [
"nullable",
Rule::requiredIf (
function () {
return in_array(1, (array)$this->request->get("some_array_field"));
}
),
]
];
}
I hope that solve your trouble too!

How can I use QuickForm to add disabled select options?

I have code using QuickForm that creates a select widget with the following:
$form->addElement( 'select', 'state_id', 'State:', statesArray() );
statesArray() queries the database to get the states available and returns an associative array with the ids linked to the state names. I'm using a similar technique throughout the solution.
What I'd like to do is prepend this array with two options that are disabled, so that by default the select menu says something like "Please select a state" followed by a dash, both of which are disabled. If I weren't using QuickForm, the select would have the following as the first two options:
<option value="" disabled="disabled">Select a State</option>
<option value="" disabled="disabled">-</option>
Both options are disabled, and if the user leaves the option on the first value, the select widget submits an empty value which is made invalid by the form checking code.
Is there a way to do this with QuickForm?
Thanks,
Chuck
OK, after digging much deeper into the QuickForm documentation, I figured this out. The solution is to not populate the select widget with an array, but to build the select element manually add this to the form.
Originally, I had this:
function dbArray( $tableName, $fieldName ) {
$query = <<< EOT
SELECT `id`, `$fieldName`
FROM `$tableName`
ORDER BY `$fieldName`
EOT;
$link = connectToDatabase();
$result = mysql_query( $query, $link );
while ( $rec = mysql_fetch_assoc( $result ) );
{
$array[$rec['id']] = $rec[$fieldName];
}
return $array;
}
function statesArray() {
return dbArray( 'states', 'name' );
}
$form = new HTML_QuickForm( 'account', 'POST' );
$form->addElement( 'select', 'state_id', 'State:', statesArray() );
I did a version where array( 'none' => 'Please select a State' ) was prepended to the dbArray call before returning the array to the calling code, but this didn't make the option disabled. Adding a rule to confirm that the choice is numeric was the workaround ($form->addRule( 'state_id', 'You must select a state.', 'numeric' )). But I still didn't like that it was selectable. Here's the solution I found.
function statesSelect() {
$select = HTML_QuickForm::createElement( 'select' );
$select->addOption( 'Select a State', '', array( 'disabled' => 'disabled' ) );
$select->addOption( '-', '', array( 'disabled' => 'disabled' ) );
$statesArray = dbArray( 'states', 'name' );
foreach ( $statesArray as $id => $name ) {
$select->addOption( $name, $id );
}
return $select;
}
$form = new HTML_QuickForm( 'account', 'POST' );
$form->addElement( statesSelect() );
$form->addRule( 'state_id', 'You must select a state.', 'required' );
I hope this helps someone else. :)
Techinically, the best solution would be to use optgroup, but browsers usually won't default to this value, but instead to the first option in the select group.
Why not have your Select State have a value of "None" (similar to your given old-school solution) and have leave it enabled, then have the widget return the form as invalid if they leave it blank? Is this outside of the scope of QuickForm?
I think most users don't notice if something is enabled or disabled, and the only benefit of disabling is to make it un-selectable.
Is your goal geared toward validating the input? If so, why not just add some javascript between the form and QuickForm to check if the user has selected a disabled (or value="Null") element before submitting it?
Obviously I need to read the QuickForm documentation to get the full picture, but based on what I can get from your example, you could just add Please Choose State => None to the state_array and then have whatever form validator you plan to use not accept "None" as a valid input.

Categories