I want to convert all of my static data to collection in Laravel.
This is my data:
static $menu_list = [
[
'path' => 'admin/report/transaction',
'active' => 'admin/report',
'name' => 'Report',
'icon' => 'file-text',
'children' => [
'path' => 'admin/report/transaction',
'active' => 'admin/report/transaction',
'name' => 'Transaction',
],
],
];
This function converts my data to array:
public static function menuList()
{
$menu_list = collect(self::$menu_list)->map(function ($voucher) {
return (object) $voucher;
});
}
but function above can only convert main of array, it can't convert children => [...] to collection.
You need a recursive call.
public static function convertToCollection()
{
$menu_list = self::menuList(self::$menu_list);
}
public static function menuList($list)
{
return collect($list)->map(function ($voucher) {
if(is_array($voucher)) {
return self::menuList($voucher)
}
return $voucher;
});
}
You need to use collect() inside map() again:
public static function menuList()
{
$menu_list = collect(self::$menu_list)->map(function ($voucher) {
return (object) array_merge($voucher, [
'children' => collect($voucher['children'])
]);
});
}
Just add a small code peace to your approach.
$menu_list = collect(self::$menu_list)->map(function ($voucher) {
$voucher['children'] = (object) $voucher['children'];
return (object) $voucher;
});
Output
Illuminate\Support\Collection {#574 ▼
#items: array:1 [▼
0 => {#573 ▼
+"path": "admin/report/transaction"
+"active": "admin/report"
+"name": "Report"
+"icon": "file-text"
+"children": {#567 ▼
+"path": "admin/report/transaction"
+"active": "admin/report/transaction"
+"name": "Transaction"
}
}
]
}
Related
I am trying to calculate and store the "Sum values from children to their parent in a variable dimension json/array".
We should start with the children with the lowest level, sum their values and store it in their parent. Move up one level, repeat the calculation, and so on.
Here is an example of given array (the "value" is "weight"):
[
"weight" => 0,
"children" => [
[
"weight" => 10
],
[
"weight" => 0,
"children" => [
[
"weight" => 60,
"children" => [
"weight" => 100
]
]
]
]
]
]
I would like to be able to dynamically calculate it like this :
[
"weight" => 110,
"children" => [
[
"weight" => 10
],
[
"weight" => 100,
"children" => [
[
"weight" => 100,
"children" => [
"weight" => 100
]
]
]
]
]
]
Do you have an idea ?
Thanks !!
If you want to update parent nodes with child nodes, then you must use a OOP concept. Because objects can store the pointer of parent node. Then you can easily update the parent node by using this pointer.
Define a two classes for Nodes and Node groups. Then you can implement your logics in these classes.
<?php
$arr = [
"weight" => 0,
"children" => [
[
"weight" => 10
],
[
"weight" => 0,
"children" => [
[
"weight" => 60,
"children" => [
"weight" => 100
]
]
]
]
]
];
interface CalculateWeight {
function calculateWeight();
}
class Node implements CalculateWeight {
public $weight = 0;
/** #var Group **/
public $parent = null;
public function __construct(?self $parent){
$this->parent = $parent;
}
function calculateWeight() {
if($this->parent){
$this->parent->plusWeight($this->weight);
}
}
public function setWeight(int $weight){
$this->weight = $weight;
}
// Creating nodes from an array
public static function fromArray(array $node, ?Node $parent=null): Node{
$weight = $node["weight"];
if(isset($node["children"])){
$group = new Group($parent);
// Checking the weather it has one child or multiple children
if(isset($node["children"][0])){
foreach($node["children"] as $node){
$group->addChild( Node::fromArray($node, $group));
}
} else {
$group->addChild(Node::fromArray($node["children"], $group));
}
return $group;
} else {
$node = new Node($parent);
$node->setWeight($weight);
return $node;
}
}
public function toArray(){
return ["weight"=> $this->weight];
}
}
class Group extends Node implements CalculateWeight {
public $childs = [];
/** #var Group **/
public $parent = null;
public function __construct(?self $parent){
$this->parent = $parent;
}
public function plusWeight(int $weight){
$this->weight += $weight;
}
public function addChild(Node $child){
$this->childs[] = $child;
}
public function calculateWeight(){
// Calculate children weights
foreach($this->childs as $child){
$child->calculateWeight();
}
// Updating calculated weight to parent node
if($this->parent){
$this->parent->plusWeight($this->weight);
}
}
public function toArray(){
return [
"weight"=> $this->weight,
"children"=> array_map(function($node){return $node->toArray();}, $this->childs)
];
}
}
$parentNode = Node::fromArray($arr,null);
$parentNode->calculateWeight();
var_dump($parentNode->toArray());
I'm trying to test a function which walks through a class, takes the public properties and makes an object with it.
The non public properties are ignored in the output.
So, I mock the class that will be processed and add it some properties.
This is my code:
class GetSettingsTest extends TestCase
{
public function getExpected() {
return (object) [
"one" => (object) [
"oneOne" => "1.1",
"oneTwo" => "1.2",
"oneThree" => (object) [
"oneThreeOne" => "1.3.1",
"oneThreeTwo" => "1.3.2",
]
],
"two" => (object) [
"twoOne" => "2.1",
"twoTwo" => "2.2",
"twoThree" => (object) [
"twoThreeOne" => "1.3.1",
"twoThreeTwo" => "1.3.2",
]
],
"three" => (object) [
"threeOne" => "3.1",
"threeTwo" => "3.2"
]
// four is not here : it is protected or private.
];
}
public function getSettingsMock() {
$stub = $this->getMockBuilder('FakeSettingsClass')
->disableOriginalConstructor()
->getMock();
$stub->one = (array) [
"oneOne" => "1.1",
"oneTwo" => "1.2",
"oneThree" => (array) [
"oneThreeOne" => "1.3.1",
"oneThreeTwo" => "1.3.2",
]
];
$stub->two = (array) [// provide an array, must return an object
"twoOne" => "2.1",
"twoTwo" => "2.2",
"twoThree" => (object) [// provide an object, must return an object
"twoThreeOne" => "1.3.1",
"twoThreeTwo" => "1.3.2",
]
];
$stub->three = (array) [
"threeOne" => "3.1",
"threeTwo" => "3.2"
];
$stub->four = (array) [
// I want this to be protected or private to be not present in the output.
"fourOne" => "4.1",
"fourTwo" => "4.2"
];
return $stub;
}
public function testGetSettings() {
$expected = $this->getExpected();
$getSettings = new GetSettings($this->getSettingsMock());
$value = $getSettings->getSettings();
$this->assertEquals($expected, $value);
}
}
The function works well with a var_dump, it ignores non-public values as expected.
The test works without the non-public part, but I want to test it with the non-public part.
I can't figure how to test the non-public part in PhHPUnit.
Probably by setting a protected value in the getSettingMock function but how can I do that?
Here is a solution based on xmike's comment and with the Phpunit doc here : https://phpunit.readthedocs.io/en/9.0/fixtures.html.
make a fixture class like this :
class GetSettingsFixture
{
public array $one = [
"oneOne" => "1.1",
"oneTwo" => "1.2",
"oneThree" => [
"oneThreeOne" => "1.3.1",
"oneThreeTwo" => "1.3.2",
]
];
public array $two = [
"twoOne" => "2.1",
"twoTwo" => "2.2",
"twoThree" => [
"twoThreeOne" => "1.3.1",
"twoThreeTwo" => "1.3.2",
]
];
public array $three = [
"threeOne" => "3.1",
"threeTwo" => "3.2"
];
public string $four = "a string";
private array $five = [ // this should be ignored in the output.
"fiveOne" => "5.1",
"fiveTwo" => "5.2"
];
protected array $six = [ // this should be ignored in the output.
"sixOne" => "6.1",
"sixTwo" => "6.2"
];
public function testFunction() { // this should be ignored in the output.
return "something";
}
}
And this test pass :
class GetSettingsTest extends TestCase
{
private GetSettingsFixture $given;
public function setUp(): void {
// this function is executed before test.
$this->given = new GetSettingsFixture(); // this call the fixture class.
}
public function tearDown(): void {
// this function is executed after the test.
unset($this->given);
}
public function getExpected() {
return (object) [
"one" => (object) [
"oneOne" => "1.1",
"oneTwo" => "1.2",
"oneThree" => (object) [
"oneThreeOne" => "1.3.1",
"oneThreeTwo" => "1.3.2",
]
],
"two" => (object) [
"twoOne" => "2.1",
"twoTwo" => "2.2",
"twoThree" => (object) [
"twoThreeOne" => "1.3.1",
"twoThreeTwo" => "1.3.2",
]
],
"three" => (object) [
"threeOne" => "3.1",
"threeTwo" => "3.2"
],
"four" => "a string"
// five, six are not here : it is protected or private.
// testFunction is hot here too, it's not a property.
];
}
public function testGetSettings() {
$expected = $this->getExpected();
$getSettings = new GetSettings($this->given);
$value = $getSettings->getSettings();
$this->assertEquals($expected, $value);
}
}
My collection:
($usersWithCommission) Illuminate\Support\Collection {#2625
#items: array:2 [
0 => array:3 [
"userId" => 1
"name" => "Sim Aufderhar"
"net_commission" => null
]
1 => array:3 [
"userId" => 2
"name" => "Carolyn Lang III"
"net_commission" => null
]
]
}
I would like modify the net_commission property, but I can not:
foreach ($soldProperties as $property) {
if (!$property->buyer_user_id && !$property->seller_transferring_user_id) {
$usersWithCommission->where('userId', $property->user_id)->first()['net_commission'] += $property->net_commission_of_sold;
}
}
How I can?
Thanks your answers.
Collections provide a method map which allows you to iterate your collection and add/modify fields.
function modify_net_commision($var) {
return YOUR_LOGIC_HERE;
}
$collection = [
[ "userId" => 1, "name" => "Sim Aufderhar", "net_commission" => null ],
[ "userId" => 2, "name" => "Carolyn Lang III", "net_commission" => null ],
];
$external_var = 'I will be used on modify_net_commision function';
$new_collection = collect($collection)->map(function ($arr) use ($external_var) {
$arr['net_commission'] = modify_net_commision($external_var);
return $arr;
})
If you want to remove some fields from your collection, use reject method.
Doc: https://laravel.com/docs/5.8/collections
Hope it helps you.
Have a great day.
public function test()
{
$collection = [
[ "userId" => 1, "name" => "Sim Aufderhar", "net_commission" => null ],
[ "userId" => 2, "name" => "Carolyn Lang III", "net_commission" => null ],
];
$data= collect($collection)->map(function($collection, $key) {
$collect = (object)$collection;
return [
'userId' => $collect->userId,
'name' => $collect->name,
'net_commission' => $this->modify_commision($key)
];
});
dd($data);
}
public function modify_commision($key) {
$property = [
['userId' => 1 ,'net_commission_of_sold' => 30],
['user_id' => 2,'net_commission_of_sold' => 40]
];
return $property[$key]['net_commission_of_sold'];
}
***Hope it helps you***
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],
]
];
});
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');