Laravel group by change keys - php

Hi I have reservations and their start times. Now I need to raport it.
$monthlyAmounts = Reservation::all()
->groupBy(function ($proj) {
return cdate($proj->start_time)->format('Y-m');
})
->map(function ($month) {
return $this->sumTime($month);
});
sumTime is my function to calculate diff between start time and end time.
cdate is helper -> carbon::parse($date)..
Result of this is:
array:2 [▼
"2020-01" => 60
"2020-02" => 420
]
Need
But for API it would be better to get like this:
[
"2020" => [
'01' => 60,
'02' => 30
],
"2021" => [
'01' => 30,
]
]
My try
Unfortunately I can't make it like that. I tried:
$monthlyAmounts = Reservation::all()
->groupBy(function ($proj) {
return cdate($proj->start_time)->format('Y-m');
})
->map(function ($month) {
return $this->sumTime($month);
})
->mapToGroups(function ($item,$key) {
$arrkey = explode('-',$key);
return [$arrkey[0]=>[$arrkey[1]=>$item]];
});
But It makes this:
array:1 [▼
2020 => array:2 [▼
0 => array:1 [▼
"01" => 60
]
1 => array:1 [▼
"02" => 420
]
]
]
So I can't do $res['2020']['01']. How to do It?

Try this:
$monthlyAmounts = Reservation::all()
->groupBy(function ($proj) {
return cdate($proj->start_time)->format('Y');
})
->map(function ($items) {
return $items->groupBy(function($item) {
return cdate($item->start_time)->format('m');
})
->map(function($month) {
return $this->sumTime($month);
});
});

Another method using foreach loop
...
$monthlyAmounts = Reservation::all();
$new = [];
foreach($monthlyAmounts as $key => $val)
{
$dis = explode('-', $key);
$new[$dis[0]][$dis[1] = $val;
}
print_r($new);

Related

Transform data with Laravel helpers

I have an array with the following format:
[
{
"stat_month": "01-2019",
"in_sum": 45443,
"out_sum": 42838,
"balance": 2605
},
{...}
]
But I want to transform this array, hopefully in one operation, to this:
[
"labels" => ["01-2019", "02-2019", "03-2019"],
"in_sum" => [45443, 60947, 56734],
"out_sum" => [42838, 42151, 75486],
"balance" => [2605, 18796, -18752]
]
Any ideas how to solve this in one operation with collection helper functions?
Look at mapToGroups in Laravel Collections:
https://laravel.com/docs/5.8/collections#method-maptogroups
or this solution:
$obj1 = new \stdClass;
$obj1->stat_month = "01-2019";
$obj1->in_sum = 45443;
$obj1->out_sum = 42838;
$obj1->balance = 2605;
$obj2 = new \stdClass;
$obj2->stat_month = "02-2019";
$obj2->in_sum = 55443;
$obj2->out_sum = 52838;
$obj2->balance = 3605;
$collection = collect([
$obj1,$obj2
]);
$aResult = [
'labels' => [],
'in_sum' => [],
'out_sum' => [],
'balance' => []
];
$collection->each(function ($item, $key) use (&$aResult) {
$aResult['labels'][] = $item->stat_month;
$aResult['in_sum'][] = $item->in_sum;
$aResult['out_sum'][] = $item->out_sum;
$aResult['balance'][] = $item->balance;
});
Result:
array:4 [▼
"labels" => array:2 [▼
0 => "01-2019"
1 => "02-2019"
]
"in_sum" => array:2 [▼
0 => 45443
1 => 55443
]
"out_sum" => array:2 [▼
0 => 42838
1 => 52838
]
"balance" => array:2 [▼
0 => 2605
1 => 3605
]
]
You can do this is php like this:
<?php
$array = '[
{
"stat_month": "01-2019",
"in_sum": 45443,
"out_sum": 42838,
"balance": 2605
},
{
"stat_month": "01-2019",
"in_sum": 45443,
"out_sum": 42838,
"balance": 2605
},
{
"stat_month": "01-2019",
"in_sum": 45443,
"out_sum": 42838,
"balance": 2605
}
]';
$array = json_decode($array, true);
$arrayResult = [
'stat_month' => array_column($array, 'stat_month'),
'in_sum' => array_column($array, 'in_sum'),
'out_sum' => array_column($array, 'out_sum'),
'balance' => array_column($array, 'balance')
];
echo "<pre>";
print_r($arrayResult);
?>
You can use simplified array_map() and receive desired output:
$i = 0;
$tmp = ['labels'=>[],'in_sum'=>[],'out_sum'=>[],'balance'=>[]];
array_map(function($obj) use (&$tmp, $i){
foreach($tmp as &$val){
$val[] = array_values(((array)$obj))[$i];
$i++;
}
},$ar);
Demo
Or just simple foreach loop:
$tmp = ['labels'=>[],'in_sum'=>[],'out_sum'=>[],'balance'=>[]];
foreach($ar as $obj) {
$i = 0;
foreach($tmp as &$val){
$val[] = array_values(((array)$obj))[$i];
$i++;
}
}
Demo2
You can replace and re-write this code with Laravel map() function. Try to use dynamic loops instead of predefined object's properties.

Laravel get group of arrays

I have code below and result is like:
array:2 [▼
0 => array:1 [▼
"CPU" => "AMD a5"
]
1 => array:1 [▼
"CPU" => "AMD a9"
]
]
I want to group them in same array like
CPU => [
"0" => "AMD a5",
"1" => "AMD a9"
]
Code
$category = Category::where('slug', $slug)->where('status', '=', 'active')->first();
$products = $category->products;
foreach ($products as $product) {
foreach($product->attributes as $attribut){
$attributes[] = [$attribut->group->title => $attribut->title];
}
}
What should I change?
If you want a bit of a more scalable solution where you might have multiple attribute types, the following may help:
$attributes = [
[
'CPU' => 'AMD A5'
],
[
'CPU' => 'AMD A9'
],
[
'GFX' => 'AMD'
]
];
return collect($attributes)
->groupBy(function($item) {
return key($item);
})
->map(function($item) {
return array_flatten($item);
})
->toArray();
The output of the above will be:
array:2 [▼
"CPU" => array:2 [▼
0 => "AMD A5"
1 => "AMD A9"
]
"GFX" => array:1 [▼
0 => "AMD"
]
]
Here's an example you can play with.
if your $attribut->group->title is "CPU" and your $attribut->title is "AMD a5"
then you can use this
foreach ($products as $product) {
foreach($product->attributes as $attribut){
$attributes[$attribut->group->title][] = $attribut->title;
}
}
from #mozammil answer if you still wanna do using foreach
$attributes =
[
[
'CPU' => 'AMD A5'
],
[
'CPU' => 'AMD A9'
],
[
'GFX' => 'AMD'
]
];
$data = [];
foreach ($attributes as $titles){
foreach ($titles as $title=> $row){
$data[$title][] = $row;
}
}
You can try collection methods here is an example.
$keyed = $collection->map(function ($item,$key) {
return [[$key] => $item['CPU']];
});
For more information
https://laravel.com/docs/5.7/collections#method-map
Use array_column to get one column of an array.
$category = Category::where('slug', $slug)->where('status', '=', 'active')->first();
$products = $category->products;
$cpu = array_column($products, "CPU");
This will return an array as your expected result.
https://3v4l.org/UYPhK
This requires PHP 7 since in older versions array_column can't handle objects

Laravel map continue

How I can in laravel map function continue loop?
I have code:
return collect($response->rows ?? [])->map(function (array $userRow) {
if ($userRow[0] == 'Returning Visitor') {
return [
$userRow[1] => [
'type' => $userRow[0],
'sessions' => (int) $userRow[2],
]
];
} else {
return false;
}
});
And output:
Collection {#986 ▼
#items: array:4 [▼
0 => false
1 => false
2 => array:1 [▶]
3 => array:1 [▶]
]
}
I don't need params with false, I need continue it or delete. How I can resolve this?
You can add a reject function after the map to remove all values that are false.
return collect($response->rows ?? [])
->map(function (array $userRow) {
if ($userRow[0] == 'Returning Visitor') {
return [
$userRow[1] => [
'type' => $userRow[0],
'sessions' => (int) $userRow[2],
]
];
} else {
return false;
}
})
->reject(function ($value) {
return $value === false;
});
You can use filter() or reject() (inverse of filter) to filter your collection, then map as you need. Something like this:
return collect($response->rows ?? [])->filter(function (array $userRow) {
return $userRow[0] == 'Returning Visitor';
})->map(function (array $userRow) {
return [
$userRow[1] => [
'type' => $userRow[0],
'sessions' => (int) $userRow[2],
]
];
});

How to parse a laravel collection

I have a laravel collection on output, I want to parse it ->toArray()
Collection {#335
#items: array:2 [
"0f39b1e0-a507-11e7-9d6e-33e84951047e" => array:2 [
"total_amount" => 25000
"debt_type" => array:2 [
0 => "car_loan"
1 => "car_loan"
]
]
"0f218520-a507-11e7-b0ba-8554a4ad039b" => array:2 [
"total_amount" => 15000
"debt_type" => array:1 [
0 => "house_loan"
]
]
]
}
is there any way to parse it so I get the following output:
array:1[
0=>[
'debt_id'=>'0f39b1e0-a507-11e7-9d6e-33e84951047e',
'debt_type'=>'car_loan',
'total_amount'=>25000
],
1=>[
'debt_id'=>'0f218520-a507-11e7-b0ba-8554a4ad039b',
'debt_type'=>'house_loan',
'total_amount'=>15000
]
]
what I have tried it works but not sure if its a good way to go around it:
$appDebts = $appDebts->groupBy('debt_type_id')->map(function ($item) {
return [
'total_amount' => $item->sum('amount'),
'debt_type' => $item->map(function ($item) {
return $item->debt_type->slug;
})->toArray(),
];
})->toArray();
if you dd $appDebts you get the collection that I have added on top of the post
$carLoan = [];
$studentLoan = [];
$houseLoan = [];
$cardLoan = [];
foreach ($appDebts as $debt) {
if ($debt['debt_type'][0] === 'car_loan') {
$carLoan['TotalAmount'] = $debt['total_amount'];
$carLoan['LoanType'] = $debt['debt_type'][0];
}
if ($debt['debt_type'][0] === 'house_loan') {
$houseLoan['TotalAmount'] = $debt['total_amount'];
$houseLoan['LoanType'] = $debt['debt_type'][0];
}
if ($debt['debt_type'][0] === 'student_loan') {
$studentLoan['TotalAmount'] = $debt['total_amount'];
$studentLoan['LoanType'] = $debt['debt_type'][0];
}
if ($debt['debt_type'][0] === 'credit_card_loan') {
$cardLoan['TotalAmount'] = $debt['total_amount'];
$cardLoan['LoanType'] = $debt['debt_type'][0];
}
}
Based on the array you shared:
$parsed = $collection->map(function ($item, $id) {
return [
'debt_id' => $id,
'debt_type' => collect($item['debt_type'])->first(),
'total_amount' => $item['total_amount']
];
})->values()->toArray();
With values you remove the key => value, you get the array without keys
Try with this mapping after the first one that you did :
$appDebts = $appDebts->groupBy('debt_type_id')->map(function ($item) {
return [
'total_amount' => $item->sum('amount'),
'debt_type' => $item->map(function ($item) {
return $item->debt_type->slug;
})->toArray(),
];
}); // <-- remove ->toArray() from here
$appDebts = $appDebts->map(function ($item, $key) {
return [
'debt_type_id' => $key
'debt_type' => $item["debt_type"][0], // assuming you want the first type !!
'total_amount' => $item["total_amount"],
];
})->toArray();
PS : This convert the given collection to tha wanted array for more performance tweaking consider editing the SQL query or the logic of getting appDebts
The only thing I can add to #Llopele's answer is to use keyBy() for easier data access:
$parsed = $collection->map(function ($item, $id) {
return [
'debt_id' => $id,
'debt_type' => collect($item['debt_type'])->first(),
'total_amount' => $item['total_amount']
];
})->values()->keyBy('debt_type')->toArray();
So now you can access data like this Arr::get($parsed, 'house_loan');

Search array of objects for multidimensional array values in PHP

I have following task to do, if there is any chance I would appreciate some help to make it in as efficient way as possible. I need to compare values from array of objects (which comes from Laravel Query Builder join query) with array values.
Objects consist of database stored values:
0 => array:2 [
0 => {#912
+"addition_id": 1
+"valid_from": "2015-09-13 00:00:00"
+"valid_to": "2015-09-19 00:00:00"
+"price": "0.00"
+"mode": 0
+"alias": "Breakfast"
}
1 => {#911
+"addition_id": 2
+"valid_from": "2015-09-13 00:00:00"
+"valid_to": "2015-09-19 00:00:00"
+"price": "10.00"
+"mode": 1
+"alias": "Dinner"
}
while array includes new data, being processed by my method.
0 => array:3 [
0 => array:6 [
"id" => 1
"alias" => "Breakfast"
"price" => "0.00"
"mode" => 0
"created_at" => "2015-09-12 21:25:03"
"updated_at" => "2015-09-12 21:25:03"
]
1 => array:6 [
"id" => 2
"alias" => "Dinner"
"price" => "10.00"
"mode" => 1
"created_at" => "2015-09-12 21:25:18"
"updated_at" => "2015-09-12 21:25:18"
]
2 => array:6 [
"id" => 3
"alias" => "Sauna Access"
"price" => "50.00"
"mode" => 0
"created_at" => "2015-09-12 21:25:35"
"updated_at" => "2015-09-12 21:25:35"
]
]
Now, what I need to do is to find out what position of the array was not in the object (compare id with addition_id) and return it.
Is there any way to do it without two nested foreach loops? I think it can be done somehow smart with array_filter, but I'm not really sure how to write efficient callback (beginner here).
The only way I could get around this was:
private function compareAdditions(array $old,array $new)
{
$difference = $new;
foreach($new as $key => $value) {
foreach($old as $oldEntry) {
if($oldEntry->addition_id == $value['id']) {
unset($difference[$key]);
}
}
}
return $difference;
}
But I would really like to make it without two foreach loops. Help will be very appreciated :)
This might be overkill but it uses a function i write in every project, precisely for these kind of situations :
function additionals($original, $additions) {
$nonExisiting = [];
//convert all the objects in arrays
$additions = json_decode(json_encode($additions), true);
//create an index
$index = hashtable2list($original, 'id');
for(reset($additions); current($additions); next($additions)) {
$pos = array_search(current($additions)['addition_id'], $index);
if($pos !== false) {
//We could replace the originals with the additions in the same loop and save resources
//$original[$pos] = current($additions);
} else {
$nonExisiting[] = current($additions);
}
}
return $nonExisiting;
}
function hashtable2list( $hashtable, $key ){
$array = [];
foreach($hashtable as $entry) {
if( is_array($entry) && isset($entry[$key])) {
$array[] = $entry[$key];
} elseif( is_object($entry) && isset($entry->$key) ) {
$array[] = $entry->$key;
} else {
$array[] = null;
}
}
return $array;
}

Categories