How to transform a Laravel collection by keys? - php

I have a Laravel collection where I have data for users (their posts, comments and reputation). I would like to transform this collection where each item becomes one of the three properties (posts, comments and reputation).
So the result would be like - for field: 'posts' and user_id: 46 we have 1542 posts. And for field: 'comments' the same user has 2. etc.
I tried to create multiple loops over the collection to get to this result but it is not working as I don't know how may loops I need. Is there a better and more clean way to do this?
Here is my original collection
"data": [
{
"user_id": 46,
"username": "johnive",
"posts": 1542,
"comments": 2,
"reputation": 48.5,
},
{
"user_id": 30,
"username": "zacky13",
"posts": 54,
"comments": 16,
"reputation": 14.3,
},
{
"user_id": 107,
"username": "lil_elf4",
"posts": 564,
"comments": 60,
"reputation": 67.5,
},
],
Here is the result I would like to achieve
"data": [
{
"user_id": 46,
"username": "johnive",
"field": "posts",
"value": 1542
},
{
"user_id": 46,
"username": "johnive",
"field": "comments",
"value": 2
},
{
"user_id": 46,
"username": "johnive",
"field": "reputation",
"value": 48.5
},
{
"user_id": 30,
"username": "zacky13",
"field": "posts",
"value": 54
},
{
"user_id": 30,
"username": "zacky13",
"field": "comments",
"value": 16
},
...
],
I tried using multiple loops seems like a dirty way to do it and I don't know how many loops I would need.
foreach ($collection as $item)
{
$new_item = new stdClass;
$new_item->user_id = $item->user_id;
$new_item->username = $item->username;
$new_item->field = 'posts';
$new_item->value = $item->posts;
}
foreach ($collection as $item)
{
$new_item = new stdClass;
$new_item->user_id = $item->user_id;
$new_item->username = $item->username;
$new_item->field = 'comments';
$new_item->value = $item->comments;
}
foreach ($collection as $item)
{
$new_item = new stdClass;
$new_item->user_id = $item->user_id;
$new_item->username = $item->username;
$new_item->field = 'reputation';
$new_item->value = $item->reputation;
}

You could do it e.g. like this:
$result = collect();
foreach ($collection as $item)
{
foreach(['posts', 'comments', 'reputations'] as $type) {
$new_item = new stdClass;
$new_item->user_id = $item->user_id;
$new_item->username = $item->username;
$new_item->field = $type;
$new_item->value = $item->$type;
$result->push($new_item);
}
}

Related

send wrong object in laravel paginate collection

I use the below code for pagination an array so for the first page everything is fine and the response is shown correctly but for the next pages, I have a problem, in the paginated array add a key per each object that I didn't want that so how can I fix that?
public function currentOrders(Request $request)
{
$user = $request->user();
$orders = Order::with('address','foods')->where(['order_status'=>0,'user_id' => $user->id])->get();
$data=[];
if (sizeof($orders)>0) {
foreach ($orders as $order){
$tmp['date'] = Jalalian::forge($order->created_at)->format('%B %d، %Y');
$tmp['time'] = Jalalian::forge($order->created_at)->format('H:i');
$tmp['total'] = $order->sum_price;
$tmp['discount_pay'] = $order->price_off;
$address = Address::withTrashed()->whereId($order->address_id)->first();
$tmp['address'] = optional($address)->address;
$tmp['foods'] = [];
foreach ($order->foods as $food){
$price = 0;
$foodDetail = Article::withTrashed()->find($food->food_id);
$products = OrderItem::with('product')->where(['order_id'=>$food->order_id,'food_id'=>$food->food_id])->get();
$sec_tmp['products'] = [];
foreach ($products as $product){
$third_tmp['product_name'] = $product->product->title;
$third_tmp['count'] = $product->count;
$product_price = ($product->product->price->price_off ? $product->product->price->price_off : $product->product->price->customer_price) * $product->count;
$third_tmp['price'] = $product_price;
$price += $product_price;
array_push($sec_tmp['products'] ,$third_tmp );
}
$sec_tmp['food_name'] = $foodDetail->title;
$sec_tmp['food_count'] = $food->count;
$sec_tmp['food_price'] = $price * $food->count;
array_push($tmp['foods'],$sec_tmp);
}
array_push($data,$tmp);
}
}
$data_collection = collect($data);
$data_paginated = $this->paginate($data_collection);
return response()->json(['orders'=>$data_paginated]);
}
public function paginate($items, $perPage = 2, $page = null, $options = [])
{
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
$items = $items instanceof Collection ? $items : Collection::make($items);
return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
}
my current result is
{
"orders": {
"current_page": 1,
"data": [
{
"date": "november 19، 2020",
"time": "01:38",
"total": 976400,
"discount_pay": 926400,
"address": "main st",
"foods": [
{
"products": [
{
"product_name": "oil",
"count": 3,
"price": 15000
},
{
"product_name": "egg",
"count": 5,
"price": 362000
},
{
"product_name": "fish",
"count": 1,
"price": 111200
}
],
"food_name": "sosage",
"food_count": 2,
"food_price": 976400
}
]
},
{
"date": "november 19، 2020",
"time": "01:43",
"total": 976400,
"discount_pay": 926400,
"address": "main street",
}
],
"first_page_url": "/?page=1",
"from": 1,
"last_page": 2,
"last_page_url": "/?page=2",
"next_page_url": "/?page=2",
"path": "/",
"per_page": 2,
"prev_page_url": null,
"to": 2,
"total": 4
}
}
but what I have in next pages is :
{
"orders": {
"current_page": 2,
"data": {
"1": {
"date": "november 19، 2020",
"time": "01:43",
"total": 976400,
"discount_pay": 926400,
"address": "main st",
"foods": [
{
"products": [
{
"product_name": "egg",
"count": 3,
"price": 15000
},
{
"product_name": "oil",
"count": 5,
"price": 362000
},
{
"product_name": "fish",
"count": 1,
"price": 111200
}
],
"food_name": "susage",
"food_count": 2,
"food_price": 976400
}
]
}
},
"first_page_url": "/?page=1",
"from": 2,
"last_page": 4,
"last_page_url": "/?page=4",
"next_page_url": "/?page=3",
"path": "/",
"per_page": 1,
"prev_page_url": "/?page=1",
"to": 2,
"total": 4
}
}
In the data object, there is a key like "1": { ... } and I want to be the same as the first page what should I do?
$data was an array but you get an object with key 1. it looks like the page number itself. But You're supposed to have a collection of $temp in $data;
You can check if $data_paginated['data'] is an array before returning return response()->json(['orders'=>$data_paginated]);
I think the error is on $this->paginate($data_collection) where $data_collection is a collection of items from list of $temp.
or could be on collect($data).
I see no error elsewhere.

Find latest children in nested categories php

I tried to parse a JSON using PHP from url. I need a list of item that have no children - means children field should be empty array and parent_id shouldn't be 0 - means not be parents.
JSON:
{
"body": {
"data": [
{
"id": 1,
"name": "car",
"parent_id": "0",
"children": [
{
"id": 2,
"name": "bmw",
"parent_id": "1",
"children": [
{
"id": 4,
"name": "bmw i8",
"parent_id": "2",
"children": []
}
]
},
{
"id": 3,
"name": "mustang",
"parent_id": "1",
"children": []
}
]
},
{
"id": 5,
"name": "clothes",
"parent_id": "0",
"children": []
},
{
"id": 6,
"name": "mobile",
"parent_id": "0",
"children": [
{
"id": 7,
"name": "apple",
"parent_id": "6",
"children": [
{
"id": 9,
"name": "Iphone 12 pro",
"parent_id": "7",
"children": []
},
{
"id": 10,
"name": "Iphone 11",
"parent_id": "7",
"children": []
}
]
},
{
"id": 8,
"name": "Xiaomi",
"parent_id": "6",
"children": []
}
]
}
]
}
}
Output Expected:
[4,3,9,10,8]
This is my php code that i tried and doesn't work.
$CategoryUrl = file_get_contents(self::CATEGORY_URL,true);
$array = json_decode($CategoryUrl,true);
$list = array();
foreach( $array['body']['data'] as $value ){
if (($value['parent_id'] != 0) && empty($value['children'])) {
foreach( $value['children'] as $val ){
$list[] = $val;
}
}
}
print_r($list);
It is indeed a little tricky to keep track of parent ID values and children call using array_walk_recursive as it jumps directly to its children. However, this can be still accomplished with your own recursive version like below. Keep checking with parent_id and children count. If both constraints satisfy, add them to $result, else keep calling children recursively.
<?php
$str = '{"body":{"data":[{"id":1,"name":"car","parent_id":"0","children":[{"id":2,"name":"bmw","parent_id":"1","children":[{"id":4,"name":"bmw i8","parent_id":"2","children":[]}]},{"id":3,"name":"mustang","parent_id":"1","children":[]}]},{"id":5,"name":"clothes","parent_id":"0","children":[]},{"id":6,"name":"mobile","parent_id":"0","children":[{"id":7,"name":"apple","parent_id":"6","children":[{"id":9,"name":"Iphone 12 pro","parent_id":"7","children":[]},{"id":10,"name":"Iphone 11","parent_id":"7","children":[]}]},{"id":8,"name":"Xiaomi","parent_id":"6","children":[]}]}]}}';
$data = json_decode($str,true);
$result = [];
function walkRecursive($data,&$result){
foreach($data as $entry){
if($entry['parent_id'] != 0 && count($entry['children']) == 0){
$result[] = $entry['id'];
}else{
walkRecursive($entry['children'],$result);
}
}
}
walkRecursive($data['body']['data'],$result);
print_r($result);
We can solve this problem by function call recursive algorithm,
hope it clear for you
$CategoryUrl = file_get_contents(self::CATEGORY_URL,true);
$array = json_decode($CategoryUrl, true);
$items = $array['body']['data'];
$list = [];
findParentIds(items, $list);
// doSomething
print_r($list);
/**
* passe by ref (list)
* #param array $children
* #param $list
*/
function findParentIds(array $children, & $list)
{
foreach ($children as $child) {
// use case 1: has no children and parent_id is 0 , just continue
if (empty($child['children']) && $child['parent_id'] == "0") {
continue;
}
// use case 2: has no children and parent_id is 0 , it's parent item
if (empty($child['children']) && $child['parent_id'] != "0") {
array_push($list, $child['id']);
}
// has children and parent_id is not 0 , recall function to treat use case 1 and 2 ..
findParentIds($child['children'], $list);
}
}

Laravel : How do I return parent > child relationship from same table

I have a 3 tables product, category, attributes. In attributes table there is parent_id used for sub-attributes in a same table.Using loop I get a data.
My code :
$productDetails = Product::with('categories')->get();
foreach ($productDetails as $key => $value) {
foreach ($value->categories as $key => $value) {
$attributes = Attribute::where('product_id',$value->product_id)->where('category_id',$value->id)->where('parent_id',Null)->get();
$value->Attributes = $attributes;
foreach ($attributes as $key => $value) {
$subAttributes = Attribute::where('parent_id', $value->id)->get();
$value->subAttributes = $subAttributes;
}
}
}
Output :
{
"productDetails": [
{
"id": 1,
"title": "Small Size Diamond",
"icon": null,
"status": "Active",
"categories": [
{
"id": 1,
"product_id": 1,
"title": "Sieve Size",
"status": "Active",
"sort_order": 1,
"Attributes": [
{
"id": 1,
"product_id": 1,
"category_id": 1,
"parent_id": null,
"title": "- 2.0",
"status": "Active",
"sort_order": 1,
"subAttributes": [
[
{
"id": 9,
"product_id": 1,
"category_id": 1,
"parent_id": 1, // Attributes table ID
"title": "+ 0000 - 000",
"status": "Active",
"sort_order": 1
},
{
"id": 10,
"product_id": 1,
"category_id": 1,
"parent_id": 1, // Attributes table ID
"title": "+ 000 - 00",
"status": "Active",
"sort_order": 2
}
]
]
}
]
}
]
}
]}
The problem is in first product I gt completed response but in other prodcts in loop I do not get subAttributes data. How can i do this?
Simple, please do not use same variable name in all foreach loop,
Your code should like following
$productDetails = Product::with('categories')->get();
foreach ($productDetails as $pro_key => $pro_value) {
foreach ($pro_value->categories as $cat_key => $cat_value) {
$attributes = Attribute::where('product_id',$cat_value->product_id)->where('category_id',$cat_value->id)->where('parent_id',Null)->get();
$cat_value->Attributes = $attributes;
foreach ($attributes as $att_key => $att_value) {
$subAttributes = Attribute::where('parent_id', $att_value->id)->get();
$att_value->subAttributes = $subAttributes;
}
}
}

PHP - Most efficient way to filter two arrays

I have a question on what is the most performant way to filter two arrays of objects. I have two arrays of products from different systems and i want to work out which products have been removed from one array and then return the products that have been removed.
See the current function i have below which i know is super slow.
public function checkRemove($externalProducts, $localProducts){
//Push all the SKU codes from feed to an array();
$arr = [];
foreach ($externalProducts->products as $product) {
if($product->StockNumber != null){
array_push($arr, $product->StockNumber);
}
}
//Loop through the local products
$productsRemove = [];
foreach ($localProducts->products as $key => $localProduct) {
if(in_array($localProduct->sku, $arr)){
}else{
array_push($productsRemove, $localProduct);
}
}
return $productsRemove;
}
$externalProducts = {
"Filter": {
"Title": "All Products"
},
"Products": [{
"Type": "Jacket",
"Price": 75,
"ExpiryDate": "2018-06-30",
"StockNumber": "180220/003",
"Created": "2018-02-20 12:24:06",
"Modified": "2018-05-30 02:00:23"
},
{
"Type": "Jeans",
"Price": 150,
"ExpiryDate": "2018-06-30",
"StockNumber": "180221/004",
"Created": "2017-08-10 15:11:44",
"Modified": "2018-05-30 02:00:22"
},
{
"Type": "Jacket",
"Price": 240,
"ExpiryDate": "2018-06-30",
"StockNumber": "150804/012",
"Created": "2015-08-04 17:03:42",
"Modified": "2018-05-30 02:00:22"
}
]
}
$internalProducts = "localProducts": [{
"title": "Fur Coat",
"id": 16526,
"created_at": "2018-05-17T10:15:45Z",
"updated_at": "2018-05-17T10:15:45Z",
"sku": "180514/001",
"price": "75.00",
"regular_price": "75.00",
"categories": [
"Jackets",
],
},
{
"title": "Ripped Jeans",
"id": 16527,
"created_at": "2018-05-17T10:15:45Z",
"updated_at": "2018-05-17T10:15:45Z",
"sku": "180221/004",
"price": "150.00",
"regular_price": "150.00",
"categories": [
"Jeans",
],
},
{
"title": "Leather Jacket",
"id": 16528,
"created_at": "2018-05-17T10:15:45Z",
"updated_at": "2018-05-17T10:15:45Z",
"sku": "150804/012",
"price": "240.00",
"regular_price": "240.00",
"categories": [
"Jackets",
],
}
]
Take a look at array_filter
You can provide a callback function which will be run for each element in the array. If the callback function returns true, the current value from the array is returned in the result array.
You still have to iterate over one array at least. It is $localProducts. So, for $localProducts there're no improvements. But you can improve $externalProducts - add a special method (if you can) that will return StockNumbers only. More effective will be if StockNumbers will be of structure as:
[
'stocknumber1' => true,
'stocknumber2' => true,
'stocknumber3' => true,
'stocknumber4' => true,
'stocknumber5' => true,
]
This will improve your search, as checking isset($StockNumbers['stocknumber4']) is faster than in_array or array_search.
If you can't change structure of $externalProducts->products, than build array of stock numbers in a loop:
public function checkRemove($externalProducts, $localProducts){
//Push all the SKU codes from feed to an array();
$arr = [];
foreach ($externalProducts->products as $product) {
if ($product->StockNumber != null){
// Again I add sku as key, not as value
$arr[$product->StockNumber] = true;
}
}
//Loop through the local products
$productsRemove = [];
foreach ($localProducts->products as $localProduct) {
// check with `isset` is faster
if (isset($arr[$localProduct->sku])) {
array_push($productsRemove, $localProduct);
}
}
return $productsRemove;
}

Fetch particular data from duplicated record in PHP

I just wanted to know is there a possibility to get specific columns from duplicated row in PHP-MySQL connection and use them to the existing data in JSON?
/...db connection etc.
if ($statement->execute())
{
$response["lecturer"] = array();
while($row = $statement->fetch(PDO::FETCH_ASSOC))
{
$lecturer = array();
$lecturer["id"] = $row["lecturerid"];
$lecturer["image"] = $row["l_image"];
$lecturer["degree"] = $row["degree"];
$lecturer["name"] = $row["l_name"];
$lecturer["surname"] = $row["surname"];
$lecturer["field"] = array();
$lecturer["field"]["fieldid"] = $row["fieldid"];
$lecturer["field"]["name"] = $row["f_name"];
array_push($response["lecturer"], $lecturer);
}
}
/... json_encode etc.
So, there can be a situation, where one lecturer has 2 different fields, and I'm getting something like this:
{
"lecturer": [
{
"id": 1,
"image": "http://...",
"degree": "PhD",
"name": "John",
"surname": "Doe",
"field": {
"fieldid": 13,
"name": "field1"
}
},
{
"id": 1,
"image": "http://...",
"degree": "PhD",
"name": "John",
"surname": "Doe",
"field": {
"fieldid": 14,
"name": "field2"
}
},
Instead of that, I would like to get JSON:
{
"lecturer": [
{
"id": 1,
"image": "http://...",
"degree": "PhD",
"name": "John",
"surname": "Doe",
"field": {
"fieldid": 13,
"name": "field1",
"fieldid": 14,
"name": "field2"
}
},
In the MySQL database there are two tables: lecturer, and field, but also lecturer_field table, where I have relation between lecturerid and fieldid.
As i was pointed out, it's impossible to declare similar named field in json object:
"field": {
"fieldid": 13,
"name": "field1",
"fieldid": 14,
"name": "field2"
}
Valid case is
"field": [{
"fieldid": 13,
"name": "field1",
},
{
"fieldid": 14,
"name": "field2"
}]
You can do that in this way:
if ($statement->execute())
{
$indexedLecturers = array();
while($row = $statement->fetch(PDO::FETCH_ASSOC))
{
$lid = $row["lecturerid"];
if (isset($indexedLecturers[$lid])) {
array_push($indexedLecturers[$lid]["field"], array(
"fieldid" => $row["fieldid"],
"name" => $row["f_name"],
));
} else {
$lecturer = array();
$lecturer["id"] = $lid;
$lecturer["image"] = $row["l_image"];
$lecturer["degree"] = $row["degree"];
$lecturer["name"] = $row["l_name"];
$lecturer["surname"] = $row["surname"];
$lecturer["field"] = array();
array_push($lecturer["field"], array(
"fieldid" => $row["fieldid"],
"name" => $row["f_name"],
));
$indexedLecturers[$lid] = $lecturer;
}
}
$response['lecturer'] = array_values($indexedLecturers);
}

Categories