Laravel validate array element is required if array is present - php

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;
}

Related

Laravel 9 Validation: array is optional but its keys are required: required_array_keys and nullable don't work together

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' => []
]);

Validating input parameters against route bound model

I have a small question concerning validation.
there is an api route POST /api/document/{document}/link it accepts an array of document IDs ({"ids": [1, 2, 3]}) to be linked to the Document bound to the route. I validate this array as follows
public function rules()
{
return [
'ids' => 'required|array',
'ids.*' => 'numeric|exists:documents,id'
];
}
The thing is the Document model has a partner attribute and it's not possible to link together documents from different partners. What I want is to check if the documents passed (by their IDs) belong to the same partner as the bound Document. I would like to validate this within the FormRequest. Is it possible?
You can use these for your rules:
'ids' => [
'required',
'array'
],
'ids.*' => [
'required',
'exists:documents,id'
],
'ids.*.partner_id' => [
Rule::in([$document->partner_id])
]
this wil validate your id matches with the numbers in the array, since we only put the id from the route given $document in there it should match or return failed.
So, here is what I ended up with:
public function rules()
{
/** #var Document $document */
$document = $this->route('document');
return [
'ids' => ['required', 'array'],
'ids.*' => ['required', 'numeric', Rule::exists('documents','id')->where('partner_id', $document->partner_id)],
];
}
As it turned out the case is described in Laravel docs here https://laravel.com/docs/5.8/validation#rule-exists. I just needed to customize the query executed to ensure that both the passed id and partner_id exist.

Laravel: Recursive Validation Per Rule

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.

Laravel filled validation rule for array element

I need to check input array of strings and raise warning if at least one of array elements is empty.
The following rule is used:
return Validator::make($data, [
'branches' => 'array',
'branches.*' => 'filled|max:255'
]);
However it seems filled rule doesn't work (while min:1 works fine).
Should it work with array elements or not?
UPDATE:
branches array is not mandatory, but if exists it should contain non empty elements.
UPDATE:
Finally found mistake in my validation rule.
It should look like
return Validator::make($data, [
'branches' => 'array',
'branches.*.*' => 'filled|max:255'
]);
since input array is array of arrays. Now filled rule works as expected with my input data.
Use required instead
return Validator::make($data, [
'branches' => 'required|array',
'branches.*' => 'required|max:255'
]);
From the documentation: https://laravel.com/docs/5.5/validation#available-validation-rules
required
The field under validation must be present in the input data and not
empty. A field is considered "empty" if one of the following
conditions are true:
The value is null.
The value is an empty string.
The value is an empty array or empty Countable object.
The value is an uploaded file with no path.
If you want to validate the array only if there is field data present use filled. You can combine this with present.
return Validator::make($data, [
'branches' => 'present|array',
'branches.*' => 'filled|max:255'
]);
filled
The field under validation must not be empty when it is present.
present
The field under validation must be present in the input data but can be empty.
Considering your comment you should try nullable
return Validator::make($data, [
'branches' => 'nullable|array',
'branches.*' => 'nullable|max:255'
]);
OR
You can use presentthis will ensure that array should be passed either with values or just an empty array
return Validator::make($data, [
'branches' => 'present|array',
'branches.*' => 'nullable|max:255'
]);

Laravel 4 validation constant pass

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'
];

Categories