Validating non-required associative array with required fields - php

I want to validate that if my request contains a field of associative array type, that array needs to contain specific fields (but only if it's present)
Ie, array, if present, needs to contain foo:
{ data: {}}
and
{ data: { array: { foo: 1 } }
is fine, but
{ data: { array: {} }
and
{ data: { array: { bar: 1 } }
is not.
I've written this validator:
['data.array' => 'sometimes', 'data.array.foo' => 'required']
But it doesn't accept the first scenario.
>>> \Illuminate\Support\Facades\Validator::make(
['data' => []],
['data.array' => 'sometimes', 'data.array.foo' => 'required']
)->errors()->all();
=> [
"The data.array.foo field is required.",
]
I'd like to avoid using 'data.array.foo' => 'required_with:data.array' (it also doesn't work for scenario when array is empty)
>>> \Illuminate\Support\Facades\Validator::make(
['data' => ['array' => []]],
['data.array' => 'sometimes', 'data.array.foo' => 'required_with:data.array']
)->errors()->all();
=> []
Are there any non-custom validators that will help me accomplish this?

You can also use Rule::requiredIf after Laravel 5.6 and up, try this:
$validator = Validator::make( $request->input(), [
// ...
'data.array.foo'=> Rule::requiredIf( function () use ($request){
return (bool) $request->input('data.array');
})
]);

Related

Laravel & Faker gives not a random element of array

I've a problem with Laravel and Faker. I want to pick a random element out of an array with faker's randomElement function. I have this simple code:
$siloID = DB::table('silos')->pluck('id');
echo(print("silos: ".$siloID));
echo(print("\nrandom element: ".$faker->randomElement($siloID)));
which is giving me back this on the console:
silos: [3,9,14,18,21,93,42,55,88,99,31,38,65,1,23,68,98,13,39,40,45,49,59,6,11,24,25,60,64,90,94,97,100,4,15,7,26,29,85,35,43,75,22,48,58,80,86,87,62,82,89,30,95,12,28,33,73,17,27,57,72,74,83,91,69,78,37,76,81,20,53,79,32,46,56,77,92,34,41,44,67,16,19,52,61,96,5,10
,47,51,54,84,2,36,66,71,50,70,8,63]1
random element: 851
I don't understand this because 851 is not in the array? What did I do wrong?
Combining echo and print is creating the issue (its adding a 1 at the end due to the parens). The $faker method randomElement is working correctly.
To fix just remove the extra php method.
echo("silos: ".$siloID);
echo("\nrandom element: ".$faker->randomElement($siloID));
As I suspect this is just for testing output and your final code won't need the echo / print statments combined, the $faker method should work fine for you.
You can do something similar to example below. It will return you random array element. here, I'm storing random array value in the JSON format to the database
$factory->define(Rule::class, function (Faker $faker) {
$rule = [
'age' => [
'borrower_age_min' => 'min:21',
'borrower_age_max' => 'max:75'
],
'affordability' => [
'annual_income' => 'integer|min:40000',
'loan_amount' => 'integer|max:3*',
],
'finance' => [
'loan_length' => 'integer|max:12',
'loan_amount' => 'integer|max:500000',
]
];
return [
'rule' => json_encode([
$faker->randomElement(
[
$rule['age']['borrower_age_min'],
$rule['age']['borrower_age_max'],
$rule['affordability']['annual_income'],
$rule['affordability']['loan_amount'],
$rule['finance']['loan_length'],
$rule['finance']['loan_amount']
]
)
])
];
});

PHP / Laravel - Foreach store into database (\Grammar::parameterize() error)

I have an array, which looks like this:
array:3 [▼
"field" => array:2 [▼
0 => "fromName"
1 => "from"
]
"operator" => array:2 [▼
0 => "="
1 => "="
]
"value" => array:2 [▼
0 => "Oliver"
1 => "oliver#mywebsite.com"
]
]
I am trying to save the above array, into my database table called email_rules:
Below is my code.
StreamEmailRulesController.php:
public function store(Stream $stream)
{
//Validate the request.
//Validate the request.
$attributes = request()->validate([
'field' => 'required|array|min:1',
'field.*' => [
'required', 'string',
Rule::in(['fromName', 'from']),
],
'operator' => 'required|array|min:1',
'operator.*' => [
'required', 'string',
Rule::in(['=', '!=', 'matches']),
],
'value' => 'required|array|min:1',
'value.*' => 'required|string|min:3|max:255',
]);
//Add the document to the database.
$stream->addRules($attributes);
//Return back.
return redirect()->back();
}
Now the $stream->addRules() function is responsible for saving the data to the database.
Stream.php:
/**
* A stream can have many rules.
*/
public function rules()
{
return $this->hasMany(EmailRule::class);
}
/**
* Add Email Rules(s) to the stream
*
* #return Illuminate\Database\Eloquent\Model
*/
public function addRules(array $attributes)
{
return $this->rules()->create($attributes);
}
Now, above does not work. I get below error:
Argument 1 passed to Illuminate\Database\Grammar::parameterize() must be of the type array, int given,
What am I doing wrong?
If you dump $attributes you may be getting an int (bool) as a pass or fail or even json, depending on what's going in, from the validation. This might just be a matter of changing syntax from
$attributes = request()->validate([...
to
$attributes= $this->validate(request(), [...
I believe your issue is that you're trying to save an array as a singular value. IE those attributes need to be iterated over to create a new set of rules for each one, instead. Normally, I'd expect to see the array ready to create individual objects. In this case, though it looks like it is structured to create individual fields (field, operator, value), so looping through those may not do what you wish either -- it provides multiple fields to the create construct, rather than a full set of object params for a new rule(). I think Laravel is hinting that you may wish to change your request/return structure to match the model format.
I think it could be the array structure. Can you modify the array to?:
[
[
"field" => "fromName",
"operator" => "=",
"value" => "Oliver"
],
[
"field" => "from",
"operator" => "=",
"value" => "oliver#mywebsite.com"
],
]
EDIT:
In the Controller add a loop like this:
...
foreach ($attributes as $key => $value) {
foreach ($value as $k => $v) {
$data [$k][$key] = $v;
}
}
//Add the document to the database.
$stream->addRules($data);
The problem was that Laravels create or createMany expect an array with key => pair values, where the key corresponds to the database columns.
This article from Adam Wathan helped me out a lot.
This is what I ended up doing:
$requestData = collect(request()->only('field', 'operator', 'value'));
$rules = $requestData->transpose()->map(function ($ruleData) {
return new EmailRule([
'field' => $ruleData[0],
'operator' => $ruleData[1],
'value' => $ruleData[2],
]);
})->toArray();
//Add the rules to the database.
$stream->addRules($rules);

Laravel 5.5 Conditionally adding validation rule

I have data coming in through an AJAX post like this:
data:
0: {type: 'percent', amount: 10,…}
1: {type: 'percent', amount: 200,…}
As you can see, the last item in the array is a problem. If the type is percent, and the amount is more than 100, the validation should fail.
I'm using the following function to validate the request:
public function validateRequest( $request ) {
$rules = [
'data.*.type' => 'required|alpha',
'data.*.amount' => 'required|min:1|int',
]
$messages = [...];
Validator::make($request->all(), $rules, $messages)->validate();
}
I've been looking on the Validation page, and I think I need to conditionally add the max:100 rule to that specific array index but only if that specific array index' type is percent. I'm just not sure how to get that done.
Thank you in advance!
I usually do such things as simple like this:
$rules = [
'data.*.type' => 'required|alpha',
'data.*.amount' => ['required', 'min:1', 'int'],
];
foreach ($request->input('data') as $key => $value)
{
if (array_get($value, 'type') == 'percent') {
$rules["data.{$key}.amount"][] = 'max:100';
}
}
Notice the array syntax for rules instead of pipe to make it easier to add additional rules.

Converting Laravel validation message dot syntax into array

I'am using Laravel on server side. Let's imagine our controller receive two fields url [string] and data [array with index head]. We can validate data and customize errors messages with
$this->validate($request, [
'url' => 'required',
'data.head' => 'required',
], [
'url.required' => 'The :attribute field is required',
'data.head.required' => 'The :attribute field is required',
]);
If validation fails, Laravel send back response with json data
{
"url": ["The url field is required"],
"data.head": ["The data.head field is required"]
}
How we can convert response data to send json, as below?
{
"url": ["The url field is required"],
"data": {
"head": ["The data.head field is required"]
}
}
In javascript
Loop on errors
error: function (errors) {
$.each(errors['responseJSON']['errors'], function (index, error) {
var object = {};
element = dotToArray(index);
object[index] = error[0];
validator.showErrors(object);
});
}
convert in dot notation into array notation. i.e abc.1.xyz into abc[1][xyz]
function dotToArray(str) {
var output = '';
var chucks = str.split('.');
if(chucks.length > 1){
for(i = 0; i < chucks.length; i++){
if(i == 0){
output = chucks[i];
}else{
output += '['+chucks[i]+']';
}
}
}else{
output = chucks[0];
}
return output
}
Laravel has an helper called array_set that transform a dot based notation to array.
I don't know how you send the errors via ajax, but you should be able to do something like that:
$errors = [];
foreach ($validator->errors()->all() as $key => $value) {
array_set($errors, $key, $value);
}
Edit:
But apparently, you should be able to not use the dot notation by Specifying Custom Messages In Language Files like this example:
'custom' => [
'email' => [
'required' => 'We need to know your e-mail address!',
],
],
I don't know if it's still a valid question but to create a custom validation using dot notation in laravel, you can specify the array like this in your validation.php
'custom' => [
'parent' => [
'children' => [
'required' => 'custom message here'
]
]
This will be the parent.children property.
see ya.
key = key.replace(/\./g, '[') + Array(key.split('.').length).join(']');

Laravel validating required at least one element in array

I am trying to post the following data to an endpoint built up on Laravel.
{
"category": "2",
"title": "my text goes here",
"difficulty": 1,
"correct": {
"id": "NULL",
"text": "Correct"
},
"wrong": [
{
"id": "NULL",
"text": ""
},
{
"id": "NULL",
"text": ""
},
{
"id": "NULL",
"text": ""
}
]
}
and I have the following validation rules.
return [
'correct' => 'required|array',
'correct.text' => 'required',
'wrong' => 'required|array|between:1,3'
];
What I am trying to accomplish is the wrong should be and array and it should contain at least one element and should not exceed 3. Now these rules are satisfying, but there is one more case I need to take care and that is the validation of the text in wrong . With the current rules, if I post the above data, it will accept as there is no rule in place for the text in the wrong section. Which rule I need to add to validate that the wrong section at least contains one entry with a not empty text.
tl;dr
If you have very specific needs for a validator rule, you can always create your own.
Create a Custom Validator
The scheme will be: properties_filled:propertyName:minimumOccurence. This rule will check if the field under validation:
Is an array.
Its elements have at least minimumOccurence amounts of non empty (!== '') values among element properties called propertyName.
In your app/Providers/AppServiceProvider.php file's boot method, you can add the custom rule implementation:
public function boot()
{
Validator::extend('properties_filled', function ($attribute, $value, $parameters, $validator) {
$validatedProperty = $parameters[0];
$minimumOccurrence = $parameters[1];
if (is_array($value)) {
$validElementCount = 0;
$valueCount = count($value);
for ($i = 0; $i < $valueCount; ++$i) {
if ($value[$i][$validatedProperty] !== '') {
++$validElementCount;
}
}
} else {
return false;
}
return $validElementCount >= $minimumOccurrence;
});
}
Then you can use it in your validation like this:
return [
'correct' => 'required|array',
'correct.text' => 'required',
'wrong' => 'required|between:1,3|properties_filled:text,1'
];
Testing
Note: I assumed that you parse your JSON data with json_decode's $assoc parameter set to true. If you use an object then change the $value[$i][$validatedProperty] !== '' in the condition to: $value[$i]->{$validatedProperty} !== ''.
Here is my example test:
$data = json_decode('{"category":"2","title":"mytextgoeshere","difficulty":1,"correct":{"id":"NULL","text":"Correct"},"wrong":[{"id":"NULL","text":""},{"id":"NULL","text":""},{"id":"NULL","text":""}]}', true);
$validator = Validator::make($data, [
'correct' => 'required|array',
'correct.text' => 'required',
'wrong' => 'required|between:1,3|properties_filled:text,1'
]);
$validator->fails();
Take advantage of the validation rule in
EDIT: I assume that wrong will have a specific value, therefore pass that value in this way
return [
'correct' => 'required|array',
'correct.text' => 'required',
'wrong' => 'required|array|between:1,3',
'wrong.text' => 'sometimes|min:1|in:somevalue,someothervalue',
];
The sometimes validation makes sure that field is checked only if exists. To check that there will be at least
I'm not sure, but does min suffice to your request? Otherwise you have to write a custom validation rule as someone else suggested
I got the same issue while trying to validate an array on the API side. I made a solution. try this
$validator = Validator::make($request->all(), [
'target_user_ids' => 'required',
'target_user_ids.*' => 'present|exists:users,uuid|distinct',
]);
if ($validator->fails()) {
return response()->json([
'status' => false,
'error' => $validator->errors()->first(),
], 400);
}
If you want to validate input fields in array, you can define your rules like this:
return [
'correct' => 'required|array',
'correct.text' => 'required',
'wrong' => 'required|array|between:1,3',
'wrong.*.text' => 'required|string|min:1',
];

Categories