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

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

Related

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

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

Cannot foreach over an array of objects in a Laravel POST

I have an array of objects which have been posted from a Vue Axios function, which I wish to loop over and save into a database. They are answers to a question.
I have passed in $data which is the array of answer objects (each has a content, correct and mark property), and the $id of the question they belong to. When I return $data, it shows me the array of objects with all the correct properties. When I return $data[0], I can access the first object. But when I try and foreach as below, it complains that $content doesn't exist. Running count() on $data also errors. What is wrong here?
Route::post('answers/{id}', function (Request $data, $id) {
foreach ($data as $value) {
$post[] = [
'user_id' => 1,
'question_id' => $id,
'content' => $value->content,
'correct' => $value->correct,
'mark' => $value->mark
]);
}
Answer::save($post);
});
You are trying to iterate over the hole $request object, which is an instance of the Request class. To access the received values first get them:
// To get all the data
$data = $request->all();
// or..
// To get just a specific value
$data = $request->get('key');
// or..
// only a list of allowed elements
$data = $request->only('here', 'goes', 'your', 'keys');
So, in case your frontend are sending an array of items under the key items. Just get them like mentioned above:
$items = $request->get('items');
Then you can use the foreach():
$items = $request->get('items');
foreach($items as $item)
{
// your operations
}
You can read more about Retrieving Input, in the documentation.

php - why post request is taking too much time?

Using php-mysql, I am fetching data, pushing it into an array. An array consists of 486 records. It's an associative array with 7 column for each record.
When there is a GET request, it works fine. Getting data, binding it to table, chart and dropdown. Everything works fine.
I need to populate dropdown based on selection of another dropdown. And in that case I am making a POST request. And searching in the same array of 486 records.
$temp = Array();
$teamSelectData = Array();
foreach ($allBookingsData as $key => $value) {
if($value['PDG'] == $passedPDG){
array_push($temp, $value["Team_Name"]);
}
}
$tempTeam = array_iunique($temp);
foreach ($tempTeam as $key => $value) {
array_push($teamSelectData, Array(
'name' => $value,
'value' => $key,
'title' => $value
));
}
$returnArray['TeamSelectData'] = $teamSelectData;
// get unique items from array
function array_iunique($array) {
$upper = array_map('strtolower', $array);
return array_intersect_key($array, array_unique($upper));
}
I couldn't able to figure out why is it taking too much time to execute. The comparison if($value['PDG'] == $passedPDG) is the issue or the function array_iunique. And the same way I am populating dropdown for PDG. Based on a selection of PDG, I need to fill dropdown of Team.
How to make this function efficient ?

CakePHP: How to use Find method + AJAX request with possibly empty search parameters

I'm working with CakePHP v2.3.x and on an edit page I need to dynamically update the page with search results...
I'm making an AJAX call from one of my Views/Tests/admin_edit.php view page to a specific action in my QuestionsController.php.
Here's the action (so far) that handles the request:
public function admin_search() {
if ($this->request->is('post')) {
$searchdata = $this->request->data;
$r = $this->Question->find('all', array('conditions' => array('Question.id' => $searchdata['id'])));
echo json_encode($r);
exit;
}
}
It currently only returns questions whose IDs match the one entered by the user, but the finished version will search several different fields. I know how to do this by adding additional key/value pairs to the conditions array. However, I don't know how to make those fields optional. What if the user enters the question name, but NOT the id, or visa versa? Is there a configuration so that CakePHP will ignore any empty field conditions?
Similarly, is there a way to set the operator so that, for example, I could match substrings or integer ranges? Update: I found this in the docs.
I would just remove any empty entries yourself first.
So let's say you have a $searchdata array with three optional fields, one of which is blank. First build your conditions array:
$searchdata = array("id" => 1, "name" => "", "type" => "foo");
$conditions = array('Question.id' => $searchdata['id'], 'Question.name' => $searchdata['name'], "Question.type" => $searchdata['type']);
(Or if you want to get fancy)
foreach($searchdata AS $key => $value) $conditions['Question.' . $key] = $value;
Now clean up $conditions, get rid of empty values:
$conditions = array_filter($conditions);
Tada:
$r = $this->Question->find('all', array('conditions' => $conditions));
See http://3v4l.org/JN6PA

Array Mapping function, returns false every time

Ok, trying to make a function that I can pass a variable to, that will search a static currently hardcoded multi dimensional array for its keys, and return the array matched to the key found (if found).
This is what I have thus far.
public function teamTypeMapping($teamType)
{
//we send the keyword football, baseball, other, then we return the array associated with it.
//eg: we send football to this function, it returns an array with nfl, college-football
$mappings = array(
"football" => array('nfl', 'college-football'),
"baseball" => array('mlb', 'college-baseball'),
"basketball" => array('nba', 'college-basketball'),
"hockey" => array('nhl', 'college-hockey'),
);
foreach($mappings as $mapped => $item)
{
if(in_array($teamType, $item)){return $mapped;}
}
return false;
}
And I'd like to make a call to it, example:
teamTypeMapping("football");
Amd have it return the array associated with the key "football", I have tried this a couple ways, and each time I come up false, maybe I am missing something so Im up for taking some advice at this point.
The reason it's not working is that you are looping through the $mappings array, and trying to see if $teamType is in the $item.
There are two problems with your approach:
You are looking in the $item (this is the array('nfl', 'college-football')) for 'football'. This is incorrect.
You are using in_array() which checks if a 'value' is in the array, not the 'key' that you have used. You might want to take a look at the array_key_exists() function - I think this is what you meant to use.
My personal preference is to use isset() instead of array_key_exists(). Slightly different syntax, but both do the same job.
See below for a revised solution:
public function teamTypeMapping($teamType)
{
//we send the keyword football, baseball, other, then we return the array associated with it.
//eg: we send football to this function, it returns an array with nfl, college-football
$mappings = array(
"football" => array('nfl', 'college-football'),
"baseball" => array('mlb', 'college-baseball'),
"basketball" => array('nba', 'college-basketball'),
"hockey" => array('nhl', 'college-hockey'),
);
if (isset($mappings[$teamType]))
{
return $mappings[$teamType];
}
return false;
}
I checked your function
public function teamTypeMapping($teamType)
{
//we send the keyword football, baseball, other, then we return the array associated with it.
//eg: we send football to this function, it returns an array with nfl, college-football
$mappings = array(
"football" => array('nfl', 'college-football'),
"baseball" => array('mlb', 'college-baseball'),
"basketball" => array('nba', 'college-basketball'),
"hockey" => array('nhl', 'college-hockey'),
);
foreach($mappings as $mapped => $item)
{
if(in_array($teamType, $item)){return $mapped;}
}
return false;
}
And when you like to make a call to it, example:
teamTypeMapping("football");
then it return false.
Solution is If your want the array then you want
foreach($mappings as $mapped => $item)
{
if($mapped == $teamType){return $mapped;}
}

Categories