Flat Laravel collection to tree - laravel

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');
}

Related

Laravel createMany error on nested relation

This is in relation to this question. I'm having this error:
TypeError
Illuminate\Database\Grammar::parameterize(): Argument #1 ($values) must be of type array, int given, called in /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php on line 920
While inserting data using createMany. This is the form request data:
{
"name": "My Order",
"orders": [
{
"date": "2022-05-17",
"product_id": [1],
"price": 1
},
{
"start_date": "2022-05-18",
"product_id": [2],
"price": 2
}
]
}
This is the store method:
$order = auth()->user()->orders()->create($request->validated());
$order_subs = $order->subOrders()->createMany($request->orders);
$order_sub_items = $request->only('orders')['orders'];
foreach ($order_subs as $key => $value) {
$value->subOrderProducts()->createMany([$order_sub_items[$key]);
}
However, if the product_id is not an array, it will store properly. Sample form request data:
{
"name": "My Order",
"orders": [
{
"date": "2022-05-17",
"product_id": 1,
"price": 1
},
{
"start_date": "2022-05-18",
"product_id": 2,
"price": 2
}
]
}
How to fix this error?

Laravel parent parameter in the relation

Good afternoon I ran into a problem
database structure
langs
id
title
1
ru
2
en
articles
id
alias
1
test_article_1
2
test_article_2
article_lang
article_id
lang_id
1
1
2
1
1
2
2
2
article_title_lang
article_id
lang_id
title
1
1
novost_1
2
1
novost_2
1
2
article_1
2
2
article_2
models:
Langs
public function articles()
{
return $this->belongsToMany(Articles::class, 'article_lang', 'lang_id', 'article_id');
}
Articles
public function articleTitleLang()
{
// return $this->hasOne(ArticlesTitleLang::class, 'article_id', 'id')->where('lang_id', '=', 1); // i can get titles lang 1
// return $this->hasOne(ArticlesTitleLang::class, 'article_id', 'id')->where('lang_id', '=', 2); // i can get titles lang 2
return $this->hasOne(ArticlesTitleLang::class, 'article_id', 'id'); // always lang id 1 (because the relation has one)
}
in controller:
$articles = Langs::with([
'articles',
'articles.articleTitleLang'
])->get();
return response($articles, 200)
return json:
[
{
"id": 1,
"title": "ru",
"articles": [
{
"id": 1,
"alias": "test_article_1",
"pivot": {
"lang_id": 1,
"article_id": 1
},
"article_title_lang": {
"title": "novost_1",
"article_id": 1,
"lang_id": 1,
}
},
{
"id": 2,
"alias": "test_article_2",
"pivot": {
"lang_id": 1,
"article_id": 2
},
"article_title_lang": {
"title": "novost_1",
"article_id": 2,
"lang_id": 1,
}
}
]
},
{
"id": 2,
"title": "en",
"articles": [
{
"id": 1,
"alias": "test_article_1",
"pivot": {
"lang_id": 2,
"article_id": 1
},
"article_title_lang": {
"title": "novost_1",
"article_id": 1,
"lang_id": 1,
}
},
{
"id": 2,
"alias": "test_article_2",
"pivot": {
"lang_id": 2,
"article_id": 2
},
"article_title_lang": {
"title": "novost_2",
"article_id": 2,
"lang_id": 1,
}
}
]
}
]
I need to change the dynamic value in the Articles model depending on the parent language value, so that when the language value is "en", the result is "title" = article_1, article_2
you can try this, in Articles Model
public function articleTitleLang()
{
return $this->hasMany(ArticlesTitleLang::class, 'article_id', 'id')
->where('lang_id', $this->lang_id)->first();
}

How to get data by query builder in laravel

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();

ReferenceManyFields (One to Many Relationship)

I am working on a project where I have to create one to many relationships which will get all the list of records referenced by id in another table and I have to display all the selected data in the multi-select field (selectArrayInput). Please help me out in this, if you help with an example that would be great.
Thanks in advance.
Example:
district
id name
1 A
2 B
3 C
block
id district_id name
1 1 ABC
2 1 XYZ
3 2 DEF
I am using https://github.com/Steams/ra-data-hasura-graphql hasura-graphql dataprovider for my application.
You're likely looking for "nested object queries" (see: https://hasura.io/docs/1.0/graphql/manual/queries/nested-object-queries.html#nested-object-queries)
An example...
query MyQuery {
district(where: {id: {_eq: 1}}) {
id
name
blocks {
id
name
}
}
}
result:
{
"data": {
"district": [
{
"id": 1,
"name": "A",
"blocks": [
{
"id": 1,
"name": "ABC"
},
{
"id": 2,
"name": "XYZ"
}
]
}
]
}
}
Or...
query MyQuery2 {
block(where: {district: {name: {_eq: "A"}}}) {
id
name
district {
id
name
}
}
}
result:
{
"data": {
"block": [
{
"id": 1,
"name": "ABC",
"district": {
"id": 1,
"name": "A"
}
},
{
"id": 2,
"name": "XYZ",
"district": {
"id": 1,
"name": "A"
}
}
]
}
}
Setting up the tables this way...
blocks:
districts:
Aside: I recommend using plural table names as they are more standard, "districts" and "blocks"

Laravel get only one column from relation

I have a table user_childrens whose contains id_parent and id_user.
I'm trying to list all childrens of the parent with this:
code:
//relation in model via belongsTo
$idparent = auth('api')->user()->id;
$list = UserChildren::where('id_parent',$idparent)
->with('child:id,name,email')
->get();
return $list->toJson();
The return is:
[
{
"id": 1,
"id_parent": 1,
"id_user": 1,
"created_at": null,
"updated_at": null,
"child": {
"id": 1,
"name": "Mr. Davin Conroy Sr.",
"email": "prempel#example.com"
}
},
{
"id": 4,
"id_parent": 1,
"id_user": 2,
"created_at": null,
"updated_at": null,
"child": {
"id": 2,
"name": "Krystel Lehner",
"email": "cernser#example.net"
}
}
]
But it's API so I want only the child column like:
[
{
"id": 1,
"name": "Mr. Davin Conroy Sr.",
"email": "prempel#example.com"
},
{..}
]
UserChildren Model:
public function child() {
return $this->belongsTo('App\User','id_user','id');
}
I know that I could do this via .map() on collection but maybe there is other solution already on this query
You can use this code
$idparent = auth('api')->user()->id;
$childs = User::whereHas('user_childrens', function ($query) use ($idparent) {
$query->where('id_parent', $idparent);
})->get(['id', 'name', 'email']);
dd($childs->toJson());
And User model define user_childrens relation.
public function user_childrens()
{
return $this->hasMany('App\UserChildren','id_user','id');
}
See also docs https://laravel.com/docs/5.5/eloquent-relationships#querying-relationship-existence

Resources