Cannot foreach over an array of objects in a Laravel POST - php

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.

Related

How do I work with an array object in PHP?

I have a Laravel site I am modifying, but there are some parts of the PHP code I don't quite understand, which are "array objects" or "object arrays". You see, I don't even know what to call them and so can't find a tutorial or basic data on it. Below is the code that I am dealing with:
private function parseMetric($result, $view)
{
$data = collect([]);
$result->each(function($item) use ($data, $view) {
if (isset($item->metric->{$view})) {
$data->push((object)[
'label' => $item->metric->{$view},
'value' => $item->metric->count
]);
}
});
...
From what I can tell, this creates an object out of $result. If I json_encode this and echo it out I get this:
[{"label":"1k-25k","value":14229},
{"label":"1mm+","value":1281},
{"label":"25k-50k","value":398},
{"label":"50k-75k","value":493},
{"label":"75k-100k","value":3848},
{"label":"100k-150k","value":9921},
{"label":"150k-200k","value":4949},
{"label":"200k-250k","value":3883},
{"label":"250k-300k","value":2685},
{"label":"300k-350k","value":2744},
{"label":"350k-500k","value":4526},
{"label":"500k-1mm","value":8690}]
Now this is obviously an array of arrays... or is it? Is it an array of objects? Or is it an object containing arrays? But the most important question is, how do I access and move or change the individual objects/arrays in this object? For example, I want to take the second object/array, which is:
{"label":"1mm+","value":1281}
and move it to the end. How do I do that? How do I find it? I used the following piece of code to find it which is pretty clunky:
$pos = strpos(json_encode($result), '1mm+');
if($pos){
Log::debug('Enrich 73, I found it!!!!!!!!!!!!!!!!!!!!!!!!!!!');
}
And once I find it, how do I move that array/object to the end of the whole object?
And finally, where can I find some kind of tutorial, or documentation, that describes this construct and how to work with it?
There is no need to json_encode the data. Since the data is an instance of Laravel Collection, you can manipulate it like so
$item = $data->firstWhere('label', '1mm+'); // get the item
$data = $data->filter(fn($value, $key) => $value->label !== '1mm+') // remove $item from $data
->push($item); // move $item to the end of data
Acording to Laravel documnentation for Collections, you can try something like this :
To find index of element with name = "1mm+" :
$index = $datas->search(function ($item, $key) {
return $item['name'] == "1mm+";
});
to get an element at a given index :
$element = $datas->get($index);
to Move element at index 3 to the end :
$index = 3
$elementToMove = $data->splice($index, 1);
$datas->push($elementToMove);
Here is a link to the document used : https://laravel.com/docs/8.x/collections

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

Array_map through a array of objects and grab properties

So I have a var_dump($instagram->get_images()); that gives me the following output:
I want to use array_map to map through all the properties and use them inside a foreach loop later on.. but I'm running into some issues:
Here is the attempt that I have:
$mediaUrls = array_map(function($entry) {
return [
'media_url' => $entry['media_url'],
];
}, $instagram->get_images());
I'm getting back the following error:
Could someone assist me on properly array_mapping through the objects and then later be able to use foreach ($MediaUrls as $media) etc...
The error is correct. You're using array map on an object. But the object does have a ->data property that is an array. But the items in the array are objects, so you'll need to refer to their properties rather than using array syntax.
$images = $instagram->get_images();
$mediaUrls = array_map(function($entry) {
return [
'media_url' => $entry->media_url,
];
}, $images->data);
Couple of suggestions. You said, "I want to use array_map to map through all the properties and use them inside a foreach loop later on."
You can reiterate $images->data later on, so I don't really see the value of making another array just for that purpose
foreach ($images->data as $imageData) {
// do something with $imageData->media_url
}
This would be almost exactly the same as iterating the array you're making with array_map.
foreach ($images->data as $imageData) {
// do something with $imageData['media_url']
}
If you want to get an array of just the urls, you can do it more simply with array_column.
$images = $instagram->get_images();
$mediaUrls = array_column($images->data, 'media_url');
(This won't give you the same result. It will be an array of strings rather than an array of arrays.)

Passing an argument to callback function working on an array

I'm using an external class (Zebra_cURL) to execute multiple HTTP GET requests. It worked in the following way:
$items = array(
0=>array('url' => 'url0'),
1=>array('url' => 'url1'),
2=>array('url' => 'url2'),
3=>array('url' => 'url3'),
);
$curl = new Zebra_cURL();
$curl->get(array_column($urls,'url'),'scan_item',$moreimfo);
function scan_item($result,$moreimfo){
$items[$key]['size'] = strlen($result->body);
}
So my callback should fill up my $items array with more info for each url (in my case - size of the page). So there is a missing $key variable.
This class supports extra parameters in the callbacks ($moreimfo in my case). BUT as I understand the data passing to each callback will be always the same.
$result object containing the original url info ($result->info['url']). So I COULD use it to find needed array element. However this looks too inefficient in case the size of an array will be big enough.
I think that I should find how to pass an array member key information for EACH callback execution. Is it possible without modifying the original class?
If you use the url as key in the $items array the solution could be something like
<?php
$items = [
'url0'=>array('url' => 'url0'),
'url1'=>array('url' => 'url1'),
'url2'=>array('url' => 'url2'),
'url3'=>array('url' => 'url3'),
];
$curl = new Zebra_cURL();
$curl->get(
array_keys($items),
function($result) use (&$items) {
$key = $result->info['url'];
$items[$key]['size'] = strlen($result->body);
}
);
using an anymous function that "Imports" the $items array via reference.
While it doesn't solve the original problem of passing a reference to the according array element to the callback, the following should be very fast (as noted in the comments, PHP Arrays are implemented using a hashtable).
$items = array(
0=>array('url' => 'url0'),
1=>array('url' => 'url1'),
2=>array('url' => 'url2'),
3=>array('url' => 'url3'),
);
$lookup=array();
foreach($lookup as $k=>$v) {
$lookup[$v['url']]=$k;
}
$curl = new Zebra_cURL();
$curl->get(array_column($urls,'url'),'scan_item',$moreimfo);
function scan_item($result,$moreimfo){
global $lookup,$items;
$items[$lookup[$result->info['url']]]['size'] = strlen($result->body);
}
Probably you may consider using an OOP-approach, with the callback as a method, then the global-izing of the arrays shouldn't be necessary if you use $this->anyMember

Laravel - Pass arrays with multiple identical titles

So I went over this thread to figure out how to send data from controller to a view here.
However I am trying to pass multiple values with identical titles to the view and I cannot seem to figure out how to do this and still access them later.
I have a for loop that grabs all the films a user has in their database, this information is then stored into a array called $data
$data = array();
foreach ($films as $film)
{
$title = $film->title;
$description = $film->description;
$url = $film->fileUrl;
$data = array_add($data, 'title', $title);
$data = array_add($data, 'description', $description);
$data = array_add($data, 'url', $url);
}
$this->layout->content = View::make('profileFilms')->with($data);
This works but I cannot figure out how to access this information, I tried making each "film" its own array with all three data values and then adding that to the larger array and then passing that array to the view, which again worked but I cannot figure out how to access the data. if i use say {{$title}} it will grab one title, but I have no way of getting both.
How can I pass N number of films each with its title, description and url to a view so I can display them, and how can I display them?
You are adding the values directly to the array once and that is it, because Laravels array_add only overwrites non existing keys.
You should do something like that (or use Eloquent):
<?php
$data = array();
foreach ($films as $film){
$data['films'][] = array(
'title' => $film->title,
'description' => $film->description,
'url' => $film->fileUrl
);
}
$this->layout->content = View::make('profileFilms')->with($data);
Then you can access your films (Blade style):
#foreach($films as $film)
{{$film['title']}}
#endforeach
If you want to use it like $film->title you can use the (object) typecast while assigning the array.
Of course, you could also pass $films directly like
$data = array(
'films' => $films
);
Then you can access your films like you would in the controller. You can alsways use print_r and var_dump to see, what the variable really contains.
$data = array();
foreach ($films as $film)
{
$row = array();
$row = array_add($data, 'title', $film->title);
$row = array_add($data, 'description', $film->description);
$row = array_add($data, 'url', $film->fileUrl);
// will send to view
$data[] = $row;
}
$this->layout->content = View::make('profileFilms')->with($data);

Categories