I'm working on an admin panel where an administrator can setup validation custom validation rules for membership applications. I understand need to setup a $attribute => $rules pair in the database.
However, there is a requested feature I am not quite sure how to implement. The administrator wants each $key => $rule pair to recursively and optionally have children $key => $rule pairs that would be executed if the parent failed. So in the end, each rule could have 0 to many children rules that would all need to pass to make the parent rule pass.
Example:
// Original validation (Assume age = 16, time_at_job = 18 and monthly_income = 3000)
[
'age' => 'min:21', // Fail, but pass because of subset is all pass
'time_at_job' => 'min:6' // Pass
'monthly_income' => 'min:2000' // Pass
]
// If the original age fails and this passes, then age passes and continue to the original
time_at_job.
[
'age' => 'min:18, // Fail, but pass because of subset is all pass
'time_at_job' => 'min:12' // Pass
'monthly_income' => 'min:2500' // Pass
]
// If the subset age passed and this passes, then the subset age passes, but the subset
time_at_job and monthly income will need to pass before the original age can pass.
[
'age' => 'min:16, // Pass
'time_at_job' => 'min:18' // Pass
'monthly_income' => 'min:3000' // Pass
]
Any help on where to start with this would be greatly appreciated.
I would imagine you could use a method call as the array value, like
public function rules(){
return [
'age' => $this->ageChildCheck(),
]
}
Then after the rules in the validator:
public function ageChildCheck(){
$data = $this->validationData();
if($data['time_at_job'] > passNumber && $data['monthly_income'] > passNumber2){
return 'min:0';
}else{
return 'min:18';
}
}
that should allow you to variably set what the validation rules will be in the end. Set it to 0 if it will auto pass if the conditions are met, and to 18 when they fail.
Related
Summary
Context
Sources
2.1. Unit test
2.2. FormRequest's rules method
Behaviors
3.1. Actual behavior
3.2. Expected behavior
Question
Context
In a Unit test, I want to send data to a FormRequest in a REST call. I am testing the behavior of the validation rules I've written in the rules method of the FormRequest.
Sources
Unit test
public function test_detach_user_job_status()
{
$response = $this->put(route('users.update', ['user' => $this->applier['id']], [
'job' => [
]
]));
$response->assertStatus(200);
}
FormRequest's rules method
public function rules()
{
return [
'name' => 'nullable|string',
'job' => 'nullable|array:id,attach_or_detach,message|required_array_keys:id,attach_or_detach',
'job.id' => 'integer|gt:0',
'job.attach_or_detach' => 'boolean',
'job.message' => 'required_if:job.attach_or_detach,true|string',
];
}
Behaviors
Actual behavior
The test succeeds.
Expected behavior
The test fails. Indeed, the array job is provided but no keys id or attach_or_detach or (eventually) message are provided, whereas the validation rules do specify: required_array_keys:id,attach_or_detach.
Also, if no job array is specified at all, then the validator must not reject the request because this array is not provided, nor its keys: it's perfectly normal since the array must be optional (it is nullable to provide this feature).
Question
Why doesn't Laravel make my test fail since my nullable (= optional) array is provided, and that its keys are required?
You didn't put the correct input. you should put the post body to put() method instead of route() method
change this:
$response = $this->put(route('users.update', ['user' => $this->applier['id']], [
'job' => [
]
]));
to:
$response = $this->put(route('users.update', ['user' => $this->applier['id']]),
[
'job' => []
]);
I have a time tracking application where every time that a new Time Entry is about to be added, I must first verify that all the previous time entries have been closed (meaning that an ending date has been set) and throw and error message using the validate() method.
I don't know how feasable this is or how to do it, reading the documentation it seems that most custome rules require that an attribute be given, but in this case it's more about validating the logical requirements rather than the form of the post request.
When I receive a post request I fetch all previous time entries which come before the post request starting time and have not yet been given an ending time.
Ideally, if I get any time entries returned I would throw an error saying 'You need to close the previous time entry before opening a new one'.
For more clarity, here is what I want to do in code :
$timeEntry= new TimeEntry;
$openTimeEntries = $timeEntry->Where('start_time', '<', $request->startTime)->Where('end_time', 0)->get();
$count = $openTimeEntries->count();
$request->validate([
'comment' => 'string',
'candidateId' => 'required',
'startTime' => 'required|date',
'endTime' => 'date|nullable|after:startTime',
'CustomeTimeEntryRule' => $openTimeEntries->count() > 0, // If false I want this rule to add the message to the validate error array
]);
You are on the right track.
However, If you really customize validation you should create a request for here you can read more about it.
Simply call php artisan make:request TimeEntryStoreRequest
public function rules()
{
return [
'CustomeTimeEntryRule' => $openTimeEntries->count() > 0,
];
}
/**
* #return array|string[]
*/
public function messages(): array
{
return [
'CustomeTimeEntryRule.*' => 'Custom message',
];
}
However, if it is not a form input from a user I think you should check it inside your controller not in the form.
Also you can simplify your code like this:
use App\Models\TimeEntry;
$openTimeEntriesCount = TimeEntry::select('id')->where('start_time', '<', $request->startTime)->where('end_time', 0)->count();
A simple way to do this is to merge the custom attribute to the request :
$timeEntry= new TimeEntry;
$openTimeEntries = $timeEntry->Where('start_time', '<', $request->startTime)->Where('end_time', 0)->get();
$count = $openTimeEntries->count();
$request->merge([
'CustomeTimeEntryRule' => $count,
]);
Then we can validate the attribute using the in rule, which will return a custom validation message which we can specify as a second argument, when the count is not equal to 0:
$request->validate([
'comment' => 'string',
'candidateId' => 'required',
'startTime' => 'required|date',
'endTime' => 'date|nullable|after:startTime',
'CustomeTimeEntryRule' => 'in:0',
], [
'CustomeTimeEntryRule.in' => 'You need to close the previous time entry before opening a new one'
]);
Laravel 5.7. I have a form request validation for a model Foo. The model has an optional field bar, which must be an array. If it is present, it must contain two keys, bing and bang. But if the array is absent, obviously these two keys should not be validated.
This is what I have so far:
return [
'bar' => 'bail|array|size:2',
'bar.bing' => 'required|numeric',
'bar.bang' => 'required|numeric',
];
This works when I send a request with the bar array present. But when I send a request without the bar array, I still get the validation errors
The bar.bing field is required
The bar.bang field is required
How can I make them only required when bar is present?
Try with this rules
return [
'bar' => 'nullable|bail|array|size:2',
'bar.bing' => 'required_with:bar|numeric',
'bar.bang' => 'required_with:bar|numeric',
]
Docs for required_with
Here's what I tend to do in this sort of situations
public function rules(): array
{
$rules = [
// ...
];
if ($this->bar) {
$rules['bar'] = 'array|size:2';
$rules['bar.bing'] = 'required|numeric';
$rules['bar.bang'] = 'required|numeric';
}
return $rules;
}
I have this scenario where I have a form as follows:
public $selling_price;
public $numbers;
public $inventory_factor;
public function rules() {
return [
['selling_price'], 'integer'],
[['inventory_factor'], 'safe'],
['numbers', 'each', 'rule' => ['integer']],
}
I have this last validation rule to make sure that I get an array of integers. This works fine when the input is a string for example. IT does not work though if an array [null] is sent. This for example does not throw errors
{
"selling_price": 2200,
"numbers": [null]
}
Using vardumper, gives the numbers array to be
[
0 => null
]
Is there way in Yii2 through which I can either remove(filter) the null values from the array before starting, or validating those as well?
Having looked at the special topic for the core validators, I see that under the each validator it shows:
rule: an array specifying a validation rule. The first element in the array specifies the class name or the alias of the validator. The rest of the name-value pairs in the array are used to configure the validator object.
Also, for the yii\validators\EachValidator, which extends yii\validators\Validator it has a property $skipOnEmpty, which defaults to true:
$skipOnEmpty public property
- Whether this validation rule should be skipped if the attribute value is null or an empty string.
public boolean $skipOnEmpty = true
So, accordingly, you need to tweak your rule as follows.
['numbers', 'each', 'rule' => ['integer', 'skipOnEmpty' => false]],
Now your validator for numbers will not turn a blind eye to the values in the array that are empty - if it finds any empty or non-integer values, the validation will fail.
['numbers', 'integer', 'min' => 0]
This will Validate that the value is an integer greater than 0 if it is not empty. Normal validators have $skipOnEmpty set to true.
Reference : https://www.yiiframework.com/doc/guide/2.0/en/input-validation
in this Data Filtering topic you can refer for these
I'm using Laravel 4.2.8 and trying to validate the next form:
The first select field is required. And only one is required from the next three fields.
Phone with formatting is the last. And another two are for digits (some IDs).
I validate in controller, the code is next:
public function getApplication()
{
$input = Input::except('_token');
Debugbar::info($input);
$input['phone'] = preg_replace('/[^0-9]/', '', $input['phone']); // remove format from phone
$input = array_map('intval', $input); // convert all numeric data to int
Debugbar::info($input);
$rules = [ // Validation rules
['operation-location' => 'required|numeric'],
['app-id' => 'numeric|min:1|required_without_all:card-id,phone'],
['card-id' => 'numeric|digits:16|required_without_all:app-id,phone'],
['phone' => 'numeric|digits:12|required_without_all:app-id,card-id']
];
$validator = Validator::make($input, $rules);
if ($validator->passes()) {
Debugbar::info('Validation OK');
return Redirect::route('appl.journal', ['by' => 'application']);
}
else { // Validation FAIL
Debugbar::info('Validation error');
// Redirect to form with error
return Redirect::route('appl.journal', ['by' => 'application'])
->withErrors($validator)
->withInput();
}
}
As you may see I convert numeric IDs to integers myself and leave only number for phone number.
The problem is when I submit form as it is, it passes validation, despite one field is required and starter phone format is too short.
I've tried changing required_without_all to just required on all fields (!), but it still passes fine with blank empty form submitted.
And I expect at least one field to be properly filled.
Debug of my inputs.
Initial:
array(4) [
'operation-location' => string (1) "0"
'app-id' => string (0) ""
'card-id' => string (0) ""
'phone' => string (6) "+3 8(0"
]
After conversion to int:
array(4) [
'operation-location' => integer 0
'app-id' => integer 0
'card-id' => integer 0
'phone' => integer 380
]
Posted similar smaller problem to Laravel issues.
I know this sounds weird but I think this is just an issue with your rules array.
You current rules array is an array of arrays. The Validator looks for an array with keys and values. I believe your current rules are being parsed as keys, but with no value. And then the Validator was basically seeing no rules, and it automatically passed. Try this.
$rules = [
'operation-location' => 'required|numeric',
'app-id' => 'numeric|min:1|required_without_all:card-id,phone',
'card-id' => 'numeric|digits:16|required_without_all:app-id,phone',
'phone' => 'numeric|digits:12|required_without_all:app-id,card-id'
];