Laravel: Using validator's sometimes method when input is an array - php

I have a form that posts a structure field as an array. The structure array contains definitions of database table columns.
$validator = Validator::make($request->all(), [
'structure' => 'required|array|min:1',
'structure.*.name' => 'required|regex:/^[a-z]+[a-z0-9_]+$/',
'structure.*.type' => 'required|in:integer,decimal,string,text,date,datetime',
'structure.*.length' => 'nullable|numeric|required_if:structure.*.type,decimal',
'structure.*.default' => '',
'structure.*.index' => 'required_if:is_auto_increment,false|boolean',
'structure.*.is_nullable' => 'required_if:is_auto_increment,false|boolean',
'structure.*.is_primary' => 'required_if:is_auto_increment,false|boolean',
'structure.*.is_auto_increment' => 'required_if:structure.type,integer|boolean',
'structure.*.is_unique' => 'required_if:is_auto_increment,false|boolean',
'structure.*.decimal' => 'nullable|numeric|required_if:structure.*.type,decimal|lt:structure.*.length',
]);
Without going into explanation of all the rules, one thing should be made sure that the length field is always null when the type is not string or decimal as you cannot assign a length to columns other than these types. So, I am trying to use the sometimes method on the $validator instance.
$validator->sometimes('structure.*.length', 'in:null', function ($input) {
// how to access the structure type here?
});
My question is inside the closure, how do I make sure that the length is null only for the array element that has the type set to other than string or decimal.
I have tried the dd function and it seems the whole input array is passed to the closure.
$validator->sometimes('structure.*.length', 'in:null', function ($input) {
dd($input);
});
Here is the output of the dd method.
I can use a foreach construct but wouldn't that be inefficient? Checking all the elements for a single element?
How do I check the type only for the array element under consideration?
Is there a Laravel way to do this?

How about thinking opposite? if the Type is String or Decimal, the Length field will become Required.
$validator->sometimes('structure.*.length', 'required', function ($input) {
return $input->type == 'string' or $input->type == 'decimal';
});

This is a great question. I took a look at the API for sometimes(). It seems, what you want to do, is currently not possible with it.
A possible alternative could be to use an After Validation Hook. For example:
$validator->after(function ($validator) {
$attributes = $validator->getData()['structure'];
foreach($attributes as $key => $value) {
if(! in_array($value['type'], ['string', 'decimal']) && ! is_null($value['length'])) {
$validator->errors()->add("structure.{$key}.length", 'Should be null');
}
}
});

Related

Merge Laravel unique validations with array generic validations

I found a code snippet that solved the problem I had with uniqueness validation, when dealing with arrays:
$rules = [ // this ones are ok for all
'staff.*.name' => 'required|max:128',
'staff.*.description' => 'max:512',
'staff.*.anothervalue' => 'string',
];
// here loop through the staff array to add the ignore
foreach($request->staff as $key => $staff) {
if ( array_key_exists('id', $staff) && $staff['id'] ) { // if have an id, means an update, so add the id to ignore
$rules = array_merge($rules, ['staff.'.$key.'.email' => 'required|email|unique:users,id,'.$staff['id']]);
} else { // just check if the email it's not unique
$rules = array_merge($rules, ['staff.'.$key.'.email' => 'required|email|unique:users']);
}
}
Now I need to go a step further and avoid duplication of email values inside the same array (not in database), so I found the "distinct" rule in Laravel documentation. Also, there is the need for making the email values required only if some another value is present in the same array entry. I see that there is a "required_with" rule that comes handy in those situations. However, I can't find the way to combine the rules generated with the foreach loop with those that use the "wildcard" array notation.
Your suggestions will be appreciated.
~~You can mix and match.~~
Edit: Turns out you cannot.
So you would need to overwrite the rule each time, or set all the rules as per staff.*.email when keyed individually; or make some custom validation logic that throws a BadRequestHttpException/ValidationException or similar.
e.g.:
foreach ...
$rules['staff.'.$key.'.email' => 'required_with:staff.*.id|email|distinct|unique:users']);

Laravel 8.X validation on attributes that contain a certain string

I'm trying to make an application that saves grocery lists and retrieves them from a database. In the request, the values get passed along in JSON format:
"item:47" => "{"id":47,"name":"Beer","brand":"Jupiler","weight":null,"note":"Six pack;_bottles","order_id":15}"
"item:88" => "{"id":88,"name":"Tomatoes","brand":null,"weight":null,"note":null,"order_id":15}"
"item:110" => "{"id":110,"name":"Gura_Bread","brand":null,"weight":0.3,"note":null,"order_id":15}"
"item:-1" => "{"id":-1,"name":"Beef_Jerky","brand":"Canadon","weight":0.5,"notes":"Spicy_Ones"}"
New items are marked with a descending negative id, while existing items retain their id from the DB
When this arrives in the back-end of the laravel application, I would like to validate the JSON string with Laravel's validate(). The only problem is that the amount of items that can be passed varies in amount. Sometimes it can be one item, while other times it could be 10 items instead.
Is there a way to add this JSON rule that could only trigger when it notices that there's a certain string in one or multiple attributes of an incoming request? In this case, it should trigger when it sees that the string item:.
For context, here are the parameters of an example request.
"picking_method" => "Cheapest"
"item:47" => "{"id":47,"name":"Beer","brand":"Jupiler","weight":null,"note":"Six pack;_bottles","order_id":15}"
"item:88" => "{"id":88,"name":"Tomatoes","brand":null,"weight":null,"note":null,"order_id":15}"
"item:110" => "{"id":110,"name":"Gura_Bread","brand":null,"weight":0.3,"note":null,"order_id":15}"
"item:-1" => "{"id":-1,"name":"Beef_Jerky","brand":"Canadon","weight":0.5,"notes":"Spicy_Ones"}"
"store_street" => "Haag Pines"
"store_number" => "1855"
"store_postal_code" => "82792-01"
"store_city" => "Port Muhammadhaven"
"store_country" => "Guernsey"
"delivery_street" => "Rosenbaum Island"
"delivery_number" => "4974"
"delivery_postal_code" => "61093"
"delivery_city" => "East Carlee"
"delivery_country" => "Slovenia"
"delivery_notes" => null
"medical_notes" => null
After experimenting some more, I came up with this solution.
In order for this method to work, you'll need to have a substring that is the same across all the attributes that you want to check.
Before performing any validation at all, I decided to collect all the attributes that I want to check into an array with a foreach loop. This is where the substring part is important because it will be used to decide which attributes will be collected:
$item_attributes = [];
foreach ($request->post() as $key => $value) {
if (str_contains($key, 'item:')) {
array_push($item_attributes, $key);
}
}
After that, I looped over the $item_attributes array and used it to make a rules array, where every value in the $item_attributes is used as a key. As value, I added the json rule.
$rules = [];
foreach ($item_attributes as $attribute) {
$rules[$attribute] = "json";
}
After that, I validate the data and returned it, so it can be used in the main function of my code:
return $request->validate($rules);
When combined, this will result into the following method:
function validateItems(Request $request)
{
$item_attributes = [];
foreach ($request->post() as $key => $value) {
if (str_contains($key, 'item:')) {
array_push($item_attributes, $key);
}
}
$rules = [];
foreach ($item_attributes as $attribute) {
$rules[$attribute] = "json";
}
return $request->validate($rules);
}

Laravel 5.1. Indexed array's validation

I have input form, where user choose the quantity of item's details for his preorder. Ex: Julia's preorder : drink's - water 1 bottle, milk 1 glass, bulletproof coffee 1 cup.
#foreach ($item->detail()->orderBy('sequence')->get() as $detail)
<td><input name="estimate_quantity[]"></td>
#endforeach
I want to validate my quantity, only integers greater than zero or equal to it.
So I made rule
public function rules()
{
$rules = [
'estimate_quantity' => 'required|array',
];
$estimate_quantity = $this->request->get('estimate_quantity');
foreach ($estimate_quantity as $index => $value){
$rules["estimate_quantity.$index"] = 'integer|min:0';
}
return $rules;
}
It doesn't work. If I entered alpha character, it will be an error
ErrorException in helpers.php line 468:
htmlentities() expects parameter 1 to be string, array given
1.What is the right way to do this validation? Making it in controller doesn't look's nice.
2.If i will make my custom rule in separate file, where better to store it? Create app/Validations folder?
3.What magic happening after rules execution and before controller method's execution?
I'm pretty new in Laravel and programming as well, sorry for this easy questions.
i think you can give the array element instead of . notation use array index notation below should work
public function rules()
{
$rules = [
'estimate_quantity' => 'required|array',
];
$estimate_quantity = $this->input('estimate_quantity');
foreach ($estimate_quantity as $index => $value){
$rules["estimate_quantity[".$index."]"] = 'integer|min:0';
}
return $rules;
}

validation value having two possible types

How to validate request value when it should be string or array both valid?
'val' => 'bail|required|string'
'val' => 'bail|required|array'
What would be the the validation expression?
I don't think there is a way to achieve it using the validation rules out of the box. You will need to use Custom Validation Rules to achieve this:
In AppServiceProvider's boot method, add in
public function boot()
{
Validator::extend('string_or_array', function ($attribute, $value, $parameters, $validator) {
return is_string($value) || is_array($value);
});
}
Then you can start using it:
'val' => 'bail|required|string_or_array'
Optionally you can set the custom validation error messages, or using a custom validation class to achieve it. Check out the doc link above for more information.
The solution provided by #lionel-chan is most elegant way but you can also do it by checking type of the variable in order to add the appropriate rule as:
$string_or_array_rule = is_array($inputs['val']) ? 'array' : 'string'
"val" => "bail|required|{$string_or_array_rule}"
only changed #Krezo's answer to ternary operator
'val' => ['bail','required',function($attribute, $value, $fail) {
is_array($value) || is_string($value)?: $fail("Must be array or string");
}],
Just use closure
"val" => [function($attribute, $value, $fail) {
if (!(is_array($value) || is_string($value))) $fail("$attribute must be array or string");
}]

Laravel change input value

In laravel, we can get the input value via Input::get('inputname'). I try to change the value by doing this Input::get('inputname') = "new value";. But then, I get the error message saying Can't use function return value in write context.
Is it possible for us change the input value so that when later calling on Input::get('inputname') will get the new amended value?
Thanks.
You can use Input::merge() to replace single items.
Input::merge(['inputname' => 'new value']);
Or use Input::replace() to replace the entire input array.
Input::replace(['inputname' => 'new value']);
Here's a link to the documentation
If you're looking to do this in Laravel 5, you can use the merge() method from the Request class:
class SomeController extends Controller
{
public function someAction( Request $request ) {
// Split a bunch of email addresses
// submitted from a textarea form input
// into an array, and replace the input email
// with this array, instead of the original string.
if ( !empty( $request->input( 'emails' ) ) ) {
$emails = $request->input( 'emails' );
$emails = preg_replace( '/\s+/m', ',', $emails );
$emails = explode( ',', $emails );
// THIS IS KEY!
// Replacing the old input string with
// with an array of emails.
$request->merge( array( 'emails' => $emails ) );
}
// Some default validation rules.
$rules = array();
// Create validator object.
$validator = Validator::make( $request->all(), $rules );
// Validation rules for each email in the array.
$validator->each( 'emails', ['required', 'email', 'min: 6', 'max: 254'] );
if ( $validator->fails() ) {
return back()->withErrors($validator)->withInput();
} else {
// Input validated successfully, proceed further.
}
}
}
If you mean you want to overwrite input data, you can try doing:
Input::merge(array('somedata' => 'SomeNewData'));
Try this,it will help you.
$request->merge(array('someIndex' => "yourValueHere"));
I also found this problem, I can solve it with the following code:
public function(Request $request)
{
$request['inputname'] = 'newValue';
}
Regards
I'm using Laravel 8.
The following is working for me:
$request->attributes->set('name', 'Value');
I used Raham's answer to solve my problem. However, it was nesting the updated data within an array, when I needed it at the same level as other data. I used:
$request->merge('someIndex' => "yourValueHere");
A note other Laravel newbies, I used the merge method to account for an empty checkbox value in a Laravel 7 update form. A deselected checkbox on an update form doesn't return 0, it doesn't set any value in the update request. As a result that value is unchanged in the database. You have to check for a set value and merge a new value if nothing exists. Hope that helps someone.
Just a quick update. If the user doesn't check a box and I need to enter a value in the DB I do something like this in my controller:
if(empty($request->input('checkbox_value'))) {
$request->merge(['checkbox_value' => 0]);
}

Categories