I have some struggles how to map infinite object in Laravel.
So I have one table Categories that I'm getting in controller like:
$find_parent = Category::where('slug', $slug)->with('childrenRecursive')->first();
childrenRecursive() works fine in that case.
And return of this object would be like (minified):
{
"id": "1cbd459a-ccc0-435b-b9a9-0433e2e9285b",
"parent_id": "f0d29100-d2bc-48c8-89cf-985e0c03b8ac",
"children_recursive": [
{
"id": "30bf23a7-b28c-4s78-b873-1df589eebcb1",
"parent_id": "1cbd459a-ccc0-435b-b9a9-0433e2e9285b",
"children_recursive": [
{
"id": "32312a7-b28c-4s78-b873-1df589eebcb1",
"parent_id": "30bf23a7-b28c-4s78-b873-1df589eebcb1",
}
]
},
{
"id": "32bf23a7-b28c-4s78-b873-1df589eebcb1",
"parent_id": "1cbd459a-ccc0-435b-b9a9-0433e2e9285b",
"children_recursive": []
}
]
}
So from this, I have to get all id's in every object. So I have foreach function in controller that looks like:
$find_parent = Category::where('slug', $slug)->with('childrenRecursive')->first();
$array = [];
foreach($find_parent->childrenRecursive as $l){
array_push($array, $l->id);
if($l->childrenRecursive){
$result = array_merge($array, $this->catTree($l->childrenRecursive));
}
}
return $result;
And catTree would look like:
public function catTree($list){
$tree = [];
foreach($list as $l){
array_push($tree, $l->id);
if($l->childrenRecursive){
array_merge($tree, $this->catTree($l->childrenRecursive));
}
}
return $tree;
}
So this is returning some of the objects, but not everything. What am I doing wrong here?
Related
I have flat array like:
[
{
"id": "1",
"parentId": "0",
"cost": 1000
},
{
"id": "2",
"parentId": "1",
"cost": 2000
},
{
"id": "3",
"parentId": "2",
"cost": 4000
},
...
]
Requirement:
convert flat array to tree array --> (DONE)
sum of each id is the total price of it and its child
now the problem appears:
should summation be done before or after converting from flat array to tree array
This is my code is try convert flat to tree:
public function buildTree(array $flat)
{
$grouped = [];
$fnBuilder = function ($companies) use (&$fnBuilder, $grouped) {
foreach ($companies as $k => $company) {
$id = $company['id'];
if (isset($grouped[$id])) {
$company['children'] = $fnBuilder($grouped[$id]);
}
$companies[$k] = $company;
}
return $companies;
};
return $fnBuilder($grouped[0]);
}
My expect result is like:
[
{
"id": "1",
"sum": 7000,
"children": [
{
"id": "2",
"sum": 6000,
"children": [
{
"id": "3",
"sum": 4000,
},
I wonder if it's possible to handle the summing inside the buildTree?
My idea is to have a tree and then handle the sum of sublevels, but i can't handle assigning the sum to the parent element
I created a class and incorporated your ideas.
class TreeBuilder {
private $flatArr;
private $idToNode;
public function __construct($flatArr) {
// Keep the flat arr in case we need it.
$this->flatArr = $flatArr;
// Create an array to lookup a node to determine if it exists.
$this->idToNode = array_combine(array_column($flatArr, 'id'), $flatArr);
}
public function buildTree() {
// create an empty array to hold root nodes
$roots = [];
// iterate through each node and add it to its parent's children list
foreach ($this->flatArr as &$node) {
$id = $node['id'];
$parentId = $node['parentId'];
if (isset($this->idToNode[$parentId])) {
$this->out("add child to $parentId " . print_r($node, true));
$parentNode = &$this->idToNode[$parentId];
if ( isset($parentNode['children']) ) {
$parentNode['children'] = [&$this->idToNode[$id]];
} else {
$parentNode['children'][] = &$this->idToNode[$id];
}
// $children[] = &$node;
} else {
$this->out("add to root " . print_r($node, true));
$roots[] = &$this->idToNode[$id];
}
}
// calculate the sum of each node and its children recursively
foreach ($roots as &$root) {
$this->calculateSum($root);
}
return $roots;
}
private function calculateSum(&$node) {
// calculate the sum of the current node
$node['sum'] = $node['cost'];
// recursively calculate the sum of the children nodes
$children = &$node['children'];
if (isset($children)) {
foreach ($children as &$child) {
$node['sum'] += $this->calculateSum($child);
}
}
return $node['sum'];
}
private function out($s) {
echo "$s\n";
}
}
You could build the tree without recursion, and then use recursion to update the sum, in post-order depth first order:
function buildTree(array $flat) {
foreach ($flat as ["id" => $id, "cost" => $sum]) {
$keyed[$id] = ["id" => $id, "sum" => $sum];
}
foreach ($flat as ["id" => $id, "parentId" => $parentId]) {
if (isset($keyed[$parentId])) {
$keyed[$parentId]["children"][] = &$keyed[$id];
} else {
$root = &$keyed[$id];
}
}
function updateSum(&$node) {
foreach ($node["children"] ?? [] as &$child) {
$node["sum"] += updateSum($child);
}
return $node["sum"];
}
updateSum($root);
return $root;
}
Example run:
$flat = json_decode('[
{
"id": "1",
"parentId": "0",
"cost": 1000
},
{
"id": "2",
"parentId": "1",
"cost": 2000
},
{
"id": "3",
"parentId": "2",
"cost": 4000
}
]', true);
$root = buildTree($flat);
print_r($root);
I'm trying the following:
I'm getting all clinic_tests related to my patients using the following function:
public function getPatientsClinicTests(Specialist $id)
{
$patientClinicTests = $id->patients()
->with('PatientClinicTests', 'PatientClinicTests.Patient.User')
->get()
->pluck('PatientClinicTests')
->filter(function ($value) { return !empty($value); });
$result = [];
foreach ($patientClinicTests as $array) {
$result = array_merge($result, $array->toArray());
}
return $result;
}
First group of code:
$patientClinicTests = $id->patients()
->with('PatientClinicTests', 'PatientClinicTests.Patient.User')
->get()
->pluck('PatientClinicTests')
->filter(function ($value) { return !empty($value); });
Brings me a collection of arrays as follows:
[
[
{
"id": 16,
"patient_id": 7,
"medical_condition_id": null,
"patient": {
"id": 7,
"user_id": 7,
"pigment_id": 14,
"id_medical_history": "6219116421",
"user": {
"id": 7,
"name": "Austen Wiegand",
}
}
},
.....
],
[
{
"id": 22,
"patient_id": 1,
"medical_condition_id": null,
"patient": {
"id": 7,
"user_id": 1,
"pigment_id": 14,
"id_medical_history": "6219116421",
"user": {
"id": 7,
"name": "Gregor Wiegand",
}
}
},
.......
]
]
As I need to return one array of elements I combine the arrays I got as follows:
$result = [];
foreach ($patientClinicTests as $array) {
$result = array_merge($result, $array->toArray());
}
return $result;
This returns one array as follows:
[
{
"id": 16,
"patient_id": 7,
"medical_condition_id": null,
"patient": {
"id": 7,
"user_id": 7,
"pigment_id": 14,
"id_medical_history": "6219116421",
"user": {
"id": 7,
"name": "Austen Wiegand",
}
}
},
{
"id": 22,
"patient_id": 1,
"medical_condition_id": null,
"patient": {
"id": 7,
"user_id": 1,
"pigment_id": 14,
"id_medical_history": "6219116421",
"user": {
"id": 7,
"name": "Gregor Wiegand",
}
}
},
.......
]
I would like to know if there is a smarter option to return as one array of elements using Eloquent instead a foreach statement.
Thanks a lot for your help!
you could use all() method to convert your collection to an array:
$patientClinicTests = $id->patients()
->with('PatientClinicTests', 'PatientClinicTests.Patient.User')
->get()
->pluck('PatientClinicTests')
->filter(function ($value) { return !empty($value); })->all();
please note that if you want to iterate over your array you should rebuild the array indexes after filtering ... you can do that using 'values' method:
$patientClinicTests = $id->patients()
->with('PatientClinicTests', 'PatientClinicTests.Patient.User')
->get()
->pluck('PatientClinicTests')
->filter(function ($value) { return !empty($value); })->values()->all();
more details in:
https://laravel.com/docs/7.x/collections#introduction
maybe you can use a collect method over the array to allign it in a single array
https://laravel.com/docs/7.x/collections
the link may be helpful
$collection = collect($patientClinicTests);
$collections = $collection->values()->all();
maybe this will work
Flatten method helps me to give a single array without using foreach statement an array_merge() function:
$patientClinicTests = $id->patients()
->with('PatientClinicTests', 'PatientClinicTests.Patient.User')
->get()
->pluck('PatientClinicTests')
->filter(function ($value) { return !empty($value); })->flatten()->all();
I will test using table joining as it has been recommended
In controller get data from db like this, I want to pass whole of $request to another function in this controller to get price it calculating price based of many things from $request:
$user = Auth::user();
$query = Post::query();
$query
->where('province', '=', $user->province)
->where('city', '=', $user->city);
$customers = $query->get();
$customers['calculator'] = $this->calculator($request); // call function
my problem is it return like this:
{
"0": {
"id": 1,
"hash": "RqH29tkfm1dwGrXp4ZCV",
},
"1": {
"id": 3,
"hash": "RqH29tkfm1dwGsXp4ZCV",
},
"calculator": {
"price": 1
}
}
But I need to use that function for each data, and result should be like this:
{
"0": {
"id": 1,
"hash": "RqH29tkfm1dwGrXp4ZCV",
"calculator": {
"price": 1
}
},
"1": {
"id": 3,
"hash": "RqH29tkfm1dwGsXp4ZCV",
"calculator": {
"price": 1
}
}
}
What you want is to set a calculator key for each item in the $customers collection. So you need to loop over it:
foreach ($customers as $customer) {
$customer->calculator = $this->calculator($request);
}
Notice that since the $customer is a Model you should set the calculator as a property. Internally it will be set to the attributes array.
I have a laravel query builder result that looks like this
{
"data": [
{
"id": "",
"awardID": 2,
"title": "Dummy title",
"status": "active",
"raceStart":"",
"raceEnd:":""
}
]
}
What i want to output is something like this
{
"data": [
{
"id": "",
"awardID": 2,
"title": "Dummy title",
"status": "active",
"period": {
"raceStart":"",
"raceEnd:":""
}
}
]
}
This would have been much easier if the period was a table with a 1 to 1 relationship with parent table but this is not the case here.
How can this be achieved?
Check if this will work. I haven't tried though but according to documentation we can add accessor and mutators. But it will change every response you are doing with the model.
Using Eloquent
// Your Model
class Race extends Model
{
{...}
protected $appends = ['period'];
// accessor
public function getPeriodAttribute($value)
{
$this->attributes['period'] = (object)[];
$this->attributes['period']['raceStart'] = $this->attributes['raceStart'];
$this->attributes['period']['raceEnd'] = $this->attributes['raceEnd'];
unset($this->attributes['raceStart']); = $value;
unset($this->attributes['raceEnd']);
return $this->attributes['period'];
}
}
Now when you will access $race->period will give the raceStart and raceEnd value.
Ref: https://laravel.com/docs/5.4/eloquent-mutators#accessors-and-mutators
else another option is after query, do a map
{...}
->map(function($data) {
$data->period = (object)[];
$data->period['raceStart'] = $data->raceStart;
$data->period['raceEnd'] = $data->raceEnd;
unset($data->raceStart);
unset($data->raceEnd);
return $data;
});
Ref: https://laravel.com/docs/5.4/eloquent-collections#introduction
Using QueryBuilder
$races = DB::table('races')->get();
$races = array_map(function ($data) {
$data->period = (object)[
"raceStart" => $data->raceStart,
"raceEnd" => $data->raceEnd
];
unset($data->raceStart);
unset($data->raceEnd);
return $data;
}, $races->data);
I would like to flatten an object. This is what I've got so far:
{
"1": {
"id": 1,
"name": "parent",
"children": {
"4": {
"id": 4,
"name": "child1",
"parent": 1
},
"5": {
"id": 5,
"name": "child2",
"parent": 1
}
}
},
"2":{
"id": 2,
"name": "parent2"
}
}
And this is what I would like to accomplish. So keep the same order but flatten the object:
{
"1": {
"id": 1,
"name": "parent",
},
"4": {
"id": 4,
"name": "child1",
"parent": 1
},
"5": {
"id": 5,
"name": "child2",
"parent": 1
},
"2": {
"id": 2,
"name": "parent2"
}
}
So far I haven't found a solution to this. I've tried a function without much success:
protected function _flattenObject($array)
{
static $flattened = [];
if(is_object($array) && count($array) > 0)
{
foreach ($array as $key => $member) {
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
$this->_flattenObject($member);
}
}
}
return $flattened;
}
The tough part for me is to keep the same order (children below its parent). And the function mentioned above also removes all objects and almost only keeps the keys with its value, so it wasn't a great success at all.
Hopefully somebody over here knows a good solution for this.
By the way, the reason I want such flatten structure is because the system I have to work with, has trouble handling multidimensional arrays and objects. And I still want to display an hierarchy, which is possible with the flatten structure I described, because the objects actually contain a "level" key as well so I can give them some padding based on the "level" while still showing up below their parent.
EDIT:
The JSON didn't seem to be valid, so I modified it a bit.
The main problem seems to be that you are not doing anything with the returned results of your recursive function. Unless using static inside a method does some magic that I don't know of...
So this section:
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
// What happens with the returned value?
$this->_flattenObject($member);
}
Should probably be more like this:
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
// Add the returned array to the array you already have
$flattened += $this->_flattenObject($member);
}
Here is code that works. It adds a field "level" to your objects, to represent how many levels deep in the original hierarchy they were.
<?php
$obj = json_decode('[{
"id": 1,
"name": "parent",
"children": [{
"id": 4,
"name": "child1",
"parent": 1
}, {
"id": 5,
"name": "child2",
"parent": 1
}]
}, {
"id": 2,
"name": "parent2"
}]');
function _flattenRecursive($array, &$flattened, &$level)
{
foreach ($array as $key => $member) {
$insert = $member;
$children = null;
if (is_array($insert->children)) {
$children = $insert->children;
$insert->children = array();
}
$insert->level = $level;
$flattened[] = $insert;
if ($children !== null) {
$level++;
_flattenRecursive($children, $flattened, $level);
$level--;
}
}
}
function flattenObject($array)
{
$flattened = [];
$level = 0;
_flattenRecursive($array, $flattened, $level);
return $flattened;
}
$flat = flattenObject($obj);
var_dump($flat);
?>