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);
This is a long post, sorry in advance.
I have a couple of questions regarding the diagram below.
How do I create a relationship for these tables? (tried "belongsToMany" and "hasMany")
Should I use Eloquent or Query Builder to get desired result? (image is attached below for desired result)
Is "client_order_items" a pivot table?
How can you distinguish a pivot table? Is it because the table has multiple
foreign keys?
How can I access "images" table from "Manufacturer" model? Can "hasOneThrough / hasManyThrough" achieve this?
Is the desired result achievable purely using Eloquent or DB Query?
//sample:
Manufacturers::find(2)->client_order_items->cars->images->car_image
I tried considering "client_order_items" as a pivot table then making relationships using "belongsToMany" in "Manufacturer", "Car" and "ClientOrder" models.
// App\Model\Manufacturer.php
public function client_orders() {
return $this->belongsToMany(ClientOrder::class,
"client_order_items", "manufacturer_id", "client_order_id")
->withPivot('id', 'quantity', 'car_id');;
}
public function cars() {
return $this->belongsToMany(Car::class,
"client_order_items", "manufacturer_id", "car_id");
}
// App\Model\Car.php
public function client_orders() {
return $this->belongsToMany(ClientOrder::class,
"client_order_items", "car_id", "client_order_id");
}
public function manufacturers() {
return $this->belongsToMany(Manufacturer::class,
"client_order_items", "car_id", "manufacturer_id");
}
// App\Model\ClientOrder.php
public function cars() {
return $this->belongsToMany(Manufacturer::class,
"client_order_items", "client_order_id", "car_id");
}
public function manufacturers() {
return $this->belongsToMany(Manufacturer::class,
"client_order_items", "client_order_id", "manufacturer_id");
}
I also tried "hasMany" relationship:
// App\Model\Manufacturer.php
public function client_order_items() {
return $this->hasMany(ClientOrderItems::class);
}
// App\Model\Cars.php
public function client_order_items() {
return $this->hasMany(ClientOrderItems::class);
}
// App\Model\ClientOrder.php
public function client_order_items() {
return $this->hasMany(ClientOrderItems::class);
}
// App\Model\ClientOrderItems.php
public function car() {
return $this->belongsTo(Car::class);
}
public function manufacturer() {
return $this->belongsTo(Manufacturer::class);
}
public function client_order() {
return $this->belongsTo(ClientOrder::class);
}
My goal is to get the result if i did something like this:
// Manufacturer::find(2)->client_orders
// Desired Result:
[
{
"client_order_id": 88062,
"first_name": "Clark",
"last_name": "Kent",
"order_items": [
{
"client_order_item_id": 37394,
"quantity": 1,
"car_id": 68,
"image": "path_img1"
}
]
},
{
"client_order_id": 60978,
"first_name": "Bruce",
"last_name": "Wayne",
"order_items": [
{
"client_order_item_id": 79913,
"quantity": 1,
"car_id": 68,
"image": "path_img1"
},
{
"client_order_item_id": 84743,
"quantity": 1,
"car_id": 95,
"image": "path_img2"
}
]
}
]
But the result I'm currently getting (with "belongsToMany") is:
// Manufacturer::find(2)->client_orders
// Current Result:
[
{
"id": 88062,
"first_name": "Clark",
"last_name": "Kent",
"pivot": {
"manufacturer_id": 2,
"client_order_id": 88062,
"id": 37394,
"quantity": 1,
"car_id": 68
}
},
{
"id": 60978,
"first_name": "Bruce",
"last_name": "Wayne",
"pivot": {
"manufacturer_id": 2,
"client_order_id": 60978,
"id": 79913,
"quantity": 1,
"car_id": 68
}
},
{
"id": 60978,
"first_name": "Bruce",
"last_name": "Wayne",
"pivot": {
"manufacturer_id": 2,
"client_order_id": 60978,
"id": 84743,
"quantity": 1,
"car_id": 95
}
}
]
Sorry again for the long post.
Thank you in advance.
After experimenting and reading for couple of hours, I was able to get the result I want.
Making the relationships:
Manufacturer model has "belongsToMany" relationship with "ClientOrder"
// App\Model\Manufacturer
public function client_orders() {
return $this->belongsToMany(ClientOrder::class, 'client_order_items', 'manufacturer_id', 'client_order_id');
}
"Car" and "Image" model has "hasMany" relationship. This will give us access to "Image" model from "ClientOrderItems" model.
// App\Models\Car
public function image() {
return $this->belongsTo(Image::class);
}
// App\Models\Image
public function cars() {
return $this->hasMany(Car::class);
}
Creating Accessors to Models
In "ClientOrder" model, create an accessor for the "order_items" key. This will fetch rows from "OrderLineItems" that are related to the "manufacturer_id" and "client_order_id".
// App\Models\ClientOrder
protected $appends = ["order_items"];
public function getOrderItemsAttribute() {
$items = ClientOrderItems::where("manufacturer_id", '=', $this->pivot->manufacturer_id)
->where("client_order_id","=",$this->pivot->client_order_id)
->select(["id as client_order_item_id","car_id","quantity"])
->get();
return $items;
}
In "ClientOrderItems" pivot model, create an accessor for the "image".
// App\Models\ClientOrderItems
protected $appends = ["image"];
public function getImageAttribute() {
return Car::find($this->car_id)->image->car_image;
}
The result of the 2 codes above looks something like this:
[
{
"client_order_item_id": 79913,
"quantity": 1,
"car_id": 68,
"image": "path_img1"
},
{
"client_order_item_id": 84743,
"quantity": 1,
"car_id": 95,
"image": "path_img2"
}
]
More on Accessors & Mutators:
https://laravel.com/docs/8.x/eloquent-mutators#accessors-and-mutators
Retrieving Records
To get the "client orders" that are related to manufacturer_id, use:
/// App\Http\Controllers\ManufacturerController;
public function index() {
$client_order = Manufacturer::find($manufacturer_id)->client_orders->unique("id")->values();
}
Above code will only fetch unique rows from "client_orders" table. Appending the related "order_items" for each row with the help of accessors.
[
{
"client_order_id": 88062,
"first_name": "Clark",
"last_name": "Kent",
"order_items": [
{
"client_order_item_id": 37394,
"quantity": 1,
"car_id": 68,
"image": "path_img1"
}
]
},
{
"client_order_id": 60978,
"first_name": "Bruce",
"last_name": "Wayne",
"order_items": [
{
"client_order_item_id": 79913,
"quantity": 1,
"car_id": 68,
"image": "path_img1"
},
{
"client_order_item_id": 84743,
"quantity": 1,
"car_id": 95,
"image": "path_img2"
}
]
}
]
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
I have the following which I would like to order alphabetically by the Key i.e first for each array group would be "bname", followed by "created_at".
{
"leads": [
{
"lead_id": 1,
"zoho_lead": null,
"bname": "ABC Limited",
"tname": "ABC",
"source_id": 11,
"industry_id": 1,
"user_id": 1,
"created_at": "2017-09-06 15:54:21",
"updated_at": "2017-09-06 15:54:21",
"user": "Sean McCabe",
"source": "Unknown",
"industry": "None"
},
{
"lead_id": 2,
"zoho_lead": 51186111981,
"bname": "Business Name Limited",
"tname": "Trading Name",
"source_id": 11,
"industry_id": 1,
"user_id": 1,
"created_at": "2017-06-01 12:34:56",
"updated_at": null,
"user": "John Doe",
"source": "Unknown",
"industry": "None"
}
]
}
I'm trying to use ksort like so in the foreach loop:
class LeadController extends Controller
{
use Helpers;
public function index(Lead $leads)
{
$leads = $leads->all();
foreach($leads as $key => $lead){
$lead->user = User::where('id', $lead->user_id)->first()->name;
$lead->source = Source::where('id', $lead->source_id)->first()->name;
$lead->industry = Industry::where('id', $lead->industry_id)->first()->name;
$lead->ksort();
}
return $leads;
}
But I get the following error:
Call to undefined method Illuminate\\Database\\Query\\Builder::ksort()
How do I use this function, or is there a Laravel way of doing this, or a better way altogether?
Thanks.
Managed to get it to return with the Keys in alphabetical order, so below is the solution in-case someone else should require it:
public function index(Lead $leads)
{
$leadOut = Array();
$leads = $leads->all();
foreach($leads as $key => $lead){
$lead->user = User::where('id', $lead->user_id)->first()->name;
$lead->source = Source::where('id', $lead->source_id)->first()->name;
$lead->industry = Industry::where('id', $lead->industry_id)->first()->name;
//Convert to Array
$leadOrder = $lead->toArray();
//Sort as desired
ksort($leadOrder);
//Add to array
$leadOut[] = $leadOrder;
}
return $leadOut;
}
There is likely a cleaner way to do this, but it works for my instance, and perhaps additional answers may be posted that are better.
You could do something like:
return Lead::with('user', 'source', 'industry')->get()->map(function ($lead) {
$item = $lead->toArray();
$item['user'] = $lead->user->name;
$item['source'] = $lead->source->name;
$item['industry'] = $lead->industry->name;
ksort($item);
return $item;
});
This should be much more efficient as it will eager load the relationships rather than make 3 extra queries for each iteration.
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);
?>