The following example is used to populate a tree and use a table with a parent_id column.
The data is obtained with a recursive query.
$data = [{
"id": 1,
"name": "parent 1"
"note": "note 1",
}, {
"id": 2,
"name": " parent 2",
"note": "note 2",
"children": [{
"id": 21,
"name": "child A of 2",
"note": "note A of 2",
},{
"id": 22,
"name": "child B of 2",
"note": "note B of 2",
},{
"id": 23,
"name": "child C of 2",
"note": "note C of 2",
"children": [{
"id": 231,
"name": "child A of 23",
"note": "note A of 23",
"children": [{
"id": 2311,
"name": "child A of 231",
"note": "note A of 231",
"children": []
}]
}]
}]
}];
And the query:
$myData= Hierarchy::whereNull('parent_id')
->with('children')
->get();
So far so good.
Problem to solve:
It is necessary to obtain a simple (non-hierarchical) list of the id and name attributes of the parents and children.
Example:
"id": 1,
"name": "parent 1",
"id": 2,
"name": " parent 2",
"id": 21,
"name": "child A of 2",
"id": 23,
"name": "child C of 2",
"id": 231,
"name": "child A of 23",
"id": 2311,
"name": "child A of 231"
While this can be solved on the client side with javascript, I intended to do it with eloquent or PHP functions.
I made some attempts with the array_walk() and array_walk_recursive() PHP functions (without success).
Is there any way to solve with eloquent, bearing in mind that the number of children nodes can be infinite?
Thanks.
EDITED:
Example attempt with array_walk_recursive() PHP function
public function getList()
{
$myData= Hierarchy::whereNull('parent_id')
->with('children')
->get();
$data = array_walk_recursive($myData, "self::myFunction");
return response()->json(['success' => true, "data" => $data]);
}
public function myFunction($item, $key){
???
}
You can use the API Resouce recursively or use the recursive function to generate the hierarchy array.
Example with recursive function:
function makeHierarchy($values)
{
$result = [];
foreach($values as $item) {
$result[] = [
'id' => $item->id,
'name' => $item->name,
'children' => makeHierarchy($item->children),
];
}
return $result;
}
$values = Hierarchy::whereNull('parent_id')->with('children')->get();
$hierarchical = makeHierarchy($values);
If you want to get all values as a flat list:
$values = Hierarchy::get();
$result = [];
foreach($values as $item) {
$result[] = [
'id' => $item->id,
'name' => $item->name,
];
}
# now the result contains all the parents and children in a flat list
In the cleaner way:
$result = Hierarchy::select(['id', 'name'])->all();
Related
I'm just join 3 eloquent tables. it works but the response json format is not what I want. I need to customizing the format response so it will easy to read.
this is my join table code
$showPresensi = Event::join('presensis', 'presensis.event_id', '=', 'events.id')
->join('anggotas', 'presensis.anggota_id', '=', 'anggotas.id')
->where('presensis.event_id', 2)
->get(['events.*', 'anggotas.nama_lengkap','presensis.kehadiran']);
return $this->ok($showPresensi, "Success");
and this is my current response json
{
"message": "Success",
"success": true,
"code": 200,
"data": [
{
"id": 2,
"nama": "Event 2",
"tanggal": "Sabtu, 12 Februari 2022",
"permintaan_dari": "Yang bersangkutan",
"hasil_kegiatan": "nothing",
"created_at": "2022-02-12T21:53:14.000000Z",
"updated_at": "2022-02-12T21:53:14.000000Z",
"nama_lengkap": "user 1",
"kehadiran": "0"
},
{
"id": 2,
"nama": "Event 2",
"tanggal": "Sabtu, 12 Februari 2022",
"permintaan_dari": "Yang bersangkutan",
"hasil_kegiatan": "nothing",
"created_at": "2022-02-12T21:53:14.000000Z",
"updated_at": "2022-02-12T21:53:14.000000Z",
"nama_lengkap": "user 3",
"kehadiran": "0"
},
{
"id": 2,
"nama": "Event 2",
"tanggal": "Sabtu, 12 Februari 2022",
"permintaan_dari": "Yang bersangkutan",
"hasil_kegiatan": "nothing",
"created_at": "2022-02-12T21:53:14.000000Z",
"updated_at": "2022-02-12T21:53:14.000000Z",
"nama_lengkap": "user 2",
"kehadiran": "1"
}
]
}
I want to column of nama_lengkap and kehadiran become an array so it will become like this:
{
"message": "Success",
"success": true,
"code": 200,
"data": [
{
"id": 2,
"nama": "Event 2",
"tanggal": "Sabtu, 12 Februari 2022",
"permintaan_dari": "Yang bersangkutan",
"hasil_kegiatan": "nothing",
"created_at": "2022-02-12T21:53:14.000000Z",
"updated_at": "2022-02-12T21:53:14.000000Z",
"presensi": [
{
"nama_lengkap": "user 1",
"kehadiran": "0"
},
{
"nama_lengkap": "user 3",
"kehadiran": "0"
},
{
"nama_lengkap": "user 2",
"kehadiran": "1"
}
]
}
]
}
is there any solution for this? any clues are helps. thank you
The easiest way would be to use nested eager loading with Eloquent.
Then you could do:
$showPresensi = Event::with('presensis', 'presensis.anggotas').find(2);
The structure should match more closely what you're going for. Then you could either modify the collection to get exactly what you want, or add accessors to your models.
I use join in query builder and here is the output
[
{
"id": 7,
"name": "class 1",
"idSubject": 17,
"nameSubject": "Math"
},
{
"id": 7,
"name": "class 1",
"idSubject": 16,
"nameSubject": "history"
},
{
"id": 8,
"name": "class 2",
"idSubject": 15,
"nameSubject": "Computer"
},
{
"id": 8,
"name": "class 2",
"idSubject": 19,
"nameSubject": "geography"
}
]
You can see id and name of class 1, 2 appearing twice. So, how to make it appear once.
this is my query:
$data = DB::table('class')->join('class_subject','class.id','=','class_subject.class_id')
->join('subject','class_subject.subject_id','=','subject.id')
->select('class.id','class.name','subject.id as idSubject','subject.name as nameSubject') ->get();
I want it:
[
{
"id": 7,
"name": "class 1",
"subject": [{"idSubject":"17","nameSubject: "Math"},
{"idSubject":"16","nameSubject: "history"}]
}
]
The distinct method allows you to force the query to return distinct results:
Try this
$data = DB::table('class')->join('class_subject','class.id','=','class_subject.class_id')
->join('subject','class_subject.subject_id','=','subject.id')
->select('class.id','class.name','subject.id as idSubject','subject.name as nameSubject')->distinct()->get();
I have a Laravel collection of pages - each page has a "parent_id" property. It resembles this.
"pages": [
{
"id": 1,
"title": "Page 1 Level 1",
"parent_id": 0
},
{
"id": 2,
"title": "Page 2 Level 2",
"parent_id": 1
},
{
"id": 3,
"title": "Page 3 Level 3",
"parent_id": 2
},
{
"id": 4,
"title": "Page 4 Level 1",
"parent_id": 0
},
{
"id": 5,
"title": "Page 5 Level 2",
"parent_id": 4
},
{
"id": 6,
"title": "Page 6 Level 3",
"parent_id": 5
},
{
"id": 7,
"title": "Page 7 Level 1",
"parent_id": 0
},
{
"id": 8,
"title": "Page 8 Level 2",
"parent_id": 7
}
]
What I am trying to do is format the output so they are nested with the correct hierarchy. So for example:
"pages": [
{
"id": 1,
"title": "Page 1 Level 1",
"parent_id": 0,
"children": [
{
"id": 2,
"title": "Page 2 Level 2",
"parent_id": 1,
"children": [
{
"id": 3,
"title": "Page 3 Level 3",
"parent_id": 2,
"children": []
}
]
},
]
},
{
"id": 4,
"title": "Page 4 Level 1",
"parent_id": 0,
"children": [
{
"id": 5,
"title": "Page 5 Level 2",
"parent_id": 4,
"children": [
{
"id": 6,
"title": "Page 6 Level 3",
"parent_id": 5,
"children": []
}
]
},
]
},
{
"id": 7,
"title": "Page 7 Level 1",
"parent_id": 0,
"children": [
{
"id": 8,
"title": "Page 8 Level 2",
"parent_id": 7,
"children": []
},
]
},
]
The hierarchy can be any number of levels deep. I have a nearly working version as shown below, but it does contain a bug. Whilst the various child objects are nesting with their parent, they also remain at the root level. So it looks like duplicates are actually placed in their nested positions.
Can anyone help me finish this off?
PageController
$pages = Page::with('children')->get();
Page
public function directChildren(): HasMany
{
return $this->hasMany($this, 'parent_id', 'id');
}
public function children(): HasMany
{
return $this->directChildren()->with('children'));
}
You could try something like this to nest these records:
$c = collect($data)->keyBy('id');
$result = $c->map(function ($item) use ($c) {
if (! ($parent = $c->get($item->parent_id))) {
return $item;
}
$parent->children[] = $item;
})->filter();
All your parent elements seem to have 0 as parent_id. If this is the case, the following query should work:
$pages = Page::where('parent_id', 0)->with('children')->get();
You are basically selecting all parent pages first, then attaching its children tree. Without the where, you are selecting all pages, and attaching their children.
The other option is to define a parent relationship in the model, and select parents with children using something like whereDoesntHave('parent'..)->with(children)
Building on #lagbox's answer:
/**
* Build a tree from a flat list of Pages.
*
* #param Collection|Page[] $pages
* #return Collection|Page[]
*/
public static function asTree(Collection $pages)
{
$pages = $pages->keyBy('id');
return $pages->map(function (Page $page) use ($pages) {
if (is_null($page->parent_id)) {
// or $page->parent_id === 0 if using 0 to indicate top-level pages
return $page;
}
/** #var Page $parent */
$parent = $pages->get($page->parent_id);
// manually set the relation to avoid re-querying the database
if (!$parent->relationLoaded('children')) {
$parent->setRelation('children', collect()->push($page));
} else {
$parent->children->push($page);
}
return null;
})
->filter()
->values();
}
I've also simplified the relationships to just a single hasMany referring to direct descendants only.
// Page.php
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function children()
{
return $this->hasMany(Page::class, 'parent_id', 'id');
}
I try to foreach a result then if name equal to "Apple "then i add a new row to next.
$list= Food::select('id', 'name')->get();
foreach ($list as $l) {
if($l->name == 'Apple'){
//Add 2 more item to next row
}
}
And my result output:
[
{
"id": 1,
"name": "Apple"
},
{
"id": 2,
"name": "Orange"
},
{
"id": 3,
"name": "Blueberry"
},
]
Here the result that i want to, for each to check if name is apple, then i need to add 2 more item manually.
[
{
"id": 1,
"name": "Apple"
},
{
"id": 1.1,
"name": "Apple Pie"
},
{
"id": 1.2,
"name": "Apple Juice"
},
{
"id": 2,
"name": "Orange"
},
{
"id": 3,
"name": "Blueberry"
},
]
For my own part, i use a temp array.
$list= Food::select('id', 'name')->get();
$tempList = array();
foreach ($list as $l) {
$tempList[] = $l;
if($l->name == 'Apple'){
$tempList[] = array('id' => 1.1, 'name' => 'Apple Pie');
}
}
But i think you can use Collection methods like "push"
Collections Push
I have the following code:
$orders = OrderProduct::where(function ($query) use ($request) {
})->with('order_products')->orderBy('status', 'desc')->paginate(50)->toArray();
And order_products function is:
public function order_products()
{
return $this->hasMany("App\Order", "order_id", "order_id");
}
It gives me output result:
{
"user_id": "1",
"created_at": "2016-12-18 14:06:11",
"status": "2",
"note": "34535345",
"order_id": "2",
"address": "Kiev",
"courier_id": null,
"payment_type": "0",
"order_products": [
{
"id": 73,
"product_id": "1265",
"amount": "1"
},
{
"id": 74,
"product_id": "1266",
"amount": "1"
}
]
I need to join order_products with products_details, that to give title of product for each order like as:
"order_products": [
{
"id": 73,
"product_id": "1265",
"amount": "1",
"title": "Product name"
},
{
"id": 74,
"product_id": "1266",
"amount": "1",
"title": "Product name"
}
]
Instead this I get a new model output in response:
"products_details": [
{}, {}
]
How can I join two with models?
without joining, just using your first code:
in order_products, override the toArray() method.
function toArray() {
return [
id => $this->id,
product_id => $this->product_id,
amount => $this->amount,
title => $this->details->name
];
}
wich $this->details->name is the relation between order_products and products_details