Group Attributes into Sets from Variant Attribute Relation - php

I'm trying to create a collection of Attributes using model bindings and group the attributes into sets before outputting as a resource. I have my relationships set as follows:
Variant Model App\Variant
public function attributes()
{
return $this->belongsToMany(Attribute::class, 'variant_attributes')
->withPivot('value');
}
Attributes Model App\Attribute
public function variants()
{
return $this->belongsToMany(Variant::class, 'variant_attributes');
}
public function set()
{
return $this->belongsTo(AttributeSet::class, 'attribute_set_id');
}
Attribute Sets Model App\AttributeSet
public function attributes()
{
return $this->hasMany(Attribute::class);
}
Ideal Output
Illuminate\Database\Eloquent\Collection {#718 ▼
#items: array:2 [▼
"essentials" => array:2 [▼
"name" => "Essentials"
"attributes" => array:2 [▼
"repellendus" => array:2 [▼
"name" => "repellendus"
"value" => "1"
]
"incidunt" => array:2 [▶]
]
]
"interior" => array:2 [▶]
]
}
Essentials and Interior are the Attribute Sets and their associated attributes are the children with the key of attributes. The pivot value comes from the variant_attributes table. I am also binding the Product and Variant to a controller...
Now, this is what I have done, however it doesn't feel right whatsoever calling $variant = $attribute->variants->first(); on a belongsToMany in this instance. I also feel this is overcomplicated.
$attributeSets = AttributeSet::with(['attributes.variants' => fn($query) => $query->withPivot('value')->where('id', $variant->id)])
->whereHas('attributes.variants', fn($query) => $query->where('id', $variant->id))
->get();
$attributeSets = $attributeSets->mapWithKeys(function ($attributeSet) {
$attributes = $attributeSet['attributes']->filter(function ($attribute) {
return $attribute->variants->count();
})->mapWithKeys(function ($attribute) {
$variant = $attribute->variants->first();
return [
$attribute['identifier'] => [
'name' => $attribute['name'],
'value' => $variant['pivot']['value']
]
];
})->toArray();
return [
$attributeSet['identifier'] => [
'name' => $attributeSet['name'],
'attributes' => $attributes
]
];
});
Thanks in advance!

Related

Merge arrays on Laravel

I'm new in Laravel and I'm trying to merge or join to arrays in one array, which have a one-to-many relationship.
These are the models:
class GroupMenu extends Model
{
public function optionmenu()
{
return $this->hasMany(OptionMenu::class, 'groupmenu_id');
}
}
class OptionMenu extends Model
{
public function groupmenu()
{
return $this->belongsTo(GroupMenu::class, 'groupmenu_id');
}
}
Also I have this function which returns the following arrangement.
public function getOptionMenus()
{
$optionmenu = OptionMenu::whereHas('tipousuario', function ($query) {
$query->where('tipousuario_id', session()->get('tipousuario_id'))->orderBy('orden');
})->get()->toArray();
return $optionmenu;
}
The output is like that:
array:17 [▼
0 => array:2 [▼
"id" => 1
"groupmenu_id" => 1
]
1 => array:2 [▼
"id" => 2
"groupmenu_id" => 1
]
2 => array:2 [▼
"id" => 3
"groupmenu_id" => 1
]
3 => array:2 [▼
"id" => 4
"groupmenu_id" => 2
]
4 => array:2 [▼
"id" => 5
"groupmenu_id" => 2
]
My problem is that I want to have an array where for each groupmenu has within it the array of the optionmenu, something like that:
0 => array:2 [▼
"id" => 1
"optionmenu" => array:3[array of all the optionsmenu that belongs to the groupmenu]
]
1 => array:2 [▼
"id" => 2
"optionmenu" => array:1[array of all the optionsmenu that belongs to the groupmenu]
]
If you want to get all GroupMenu records with related OptionMenu records without any constraint
$data = GroupMenu::with('optionmenu')->get();
However if you want to constrain the related OptionMenu records for each GroupMenu parent record based on the id of tipousuario - which you get from session as shown in your question then you can try something like
$data = GroupMenu::with([
'optionmenu' => fn($query) =>
$query->whereHas('tipousuario', fn($q) =>
$q->where('tipousuario_id', session()->get('tipousuario_id'))->orderBy('orden')
)
])->get();

Sql query where table is connected to it self and create a array using php

I have 1 table name page_live. Now I want to display it if isMenu = 1. Now it is connected to it self with filed name parent_Id.
As an example -
I have page_live named test_page and this row is a parent of a row named inside-1. And inside-1 is parent of inside-2.
Now, I have to create array which will look like -
[0]=>{
'name' => 'test_page'
[0]=> {
'name' => 'inside-1'
[0] => {
'name' => 'inside-2'
}
}
}
This is my table -
Model PageLive
<?php
namespace App\Http\Models;
use Illuminate\Database\Eloquent\Model;
class PageLive extends Model
{
protected $table = 'page_live';
protected $fillable = ['name', 'adminId', 'slugName', 'description',
'imageId', 'metaTitle', 'metaDesc', 'metaKeyword', 'pageTypeId', 'parent_id',
'isHome', 'pageDraftId', 'themeLayoutId'];
public function parent()
{
return $this->belongsTo(App\Http\Models\PageLive::class, 'parent_id');
}
public function children()
{
return $this->hasMany(App\Http\Models\PageLive::class, 'parent_id');
}
}
Please help me.
Thank you.
You Need to use recursive relations in your Model:
public function childrenPages()
{
return $this->hasMany(PageLive::class, 'parent_id', 'id');
}
public function allChildrenPages()
{
return $this->childrenPages()->with('allChildrenPages');
}
Then in Controller:
$page = PageLive::with('allChildrenPages')->first();
$page->allChildrenPages; // collection of recursively loaded children
// each of them having the same collection of children:
$page->allChildrenPages->first()->allChildrenPages; // .. and so on
I can't guarantee it will be efficient for your data, I tried to give you the idea and part of the code, you need to test it definitely.
You can use recursive function to achieve this.
public function abc( $ar, $pid = null ) {
$op = array();
foreach( $ar as $item ) {
if( $item['parent_id'] == $pid ) {
$op[$item['id']] = $item;
// using recursion
$children = $this->abc( $ar, $item['id'] );
if( $children ) {
$op[$item['id']]['children'] = $children;
}
}
}
return $op;
}
Use function something like this and call it wherever you want.
You will get array structure like -
array:1 [▼
1 => array:5 [▼
"id" => 1
"name" => "Test Page"
"slugName" => "test-page"
"parent_id" => null
"children" => array:1 [▼
3 => array:5 [▼
"id" => 3
"name" => "Inside 1"
"slugName" => "test-page2"
"parent_id" => "1"
"children" => array:1 [▼
4 => array:4 [▼
"id" => 4
"name" => "Inside 2"
"slugName" => "test-page3"
"parent_id" => "3"
]
]
]
]
]
]
Hope this will help you.

Why my structure of collection is not expected (not as I need)

I have the following method of my cart class:
public function add($productId) {
$product = Product::where('id', $productId)->first();
if (!$product) {
return false;
}
if ($this->items->has($productId)) {
$this->items->$productId->qty++;
} else {
$this->items->push([$productId => [
'name' => $product->title,
'price' => $product->price,
'is_sale' => $product->is_sale,
'sale_price' => $product->sale_price,
'sale_percent' => $product->sale_percent,
'can_use_promocode' => $product->can_use_promocode,
'qty' => 1,
]
]);
}
$this->save();
return true;
}
But on dump($cart) in controller, I got this:
+items: Collection {#176 ▼
#items: array:1 [▼
0 => {#171 ▼
+"2": {#164 ▼
+"name": "101 роза"
+"price": 4999
+"is_sale": 0
+"sale_price": null
+"sale_percent": null
+"can_use_promocode": 1
+"qty": 1
}
}
]
}
But I need to:
+items: Collection {#176 ▼
#items: array:1 [▼
2 => {
+"name": "101 роза"
+"price": 4999
+"is_sale": 0
+"sale_price": null
+"sale_percent": null
+"can_use_promocode": 1
+"qty": 1
}
]
}
In this case, I thought that $this->items->push([$productId => ...] will push the key => value pair to collection, but collection creates its own pair, and my pair goes into collection pair. (idk how to describe it correctly, but I think you understand me :) )
Use the put() method instead:
->put($key, $data)
The put method sets the given key and value in the collection

How do I flatten laravel recursive relationship collection (tree collections)?

How do I flatten a collection with hierarchy self referenced models, tree collections into a single dimension collection. I have a self referencing model having parents and children.
I want the result to return a eloquent collection, not a simple collection or an array. array has been used as result results for easy demonstration
relationships are declared like this.
public function parent()
{
return $this->belongsTo(self::class, 'parent_id');
}
public function parentRecursive()
{
return $this->parent()->with('parentRecursive');
}
public function children()
{
return $this->hasMany(self::class, 'parent_id');
}
public function childrenRecursive()
{
return $this->children()->with('childrenRecursive');
}
so when i call the model->childrenRecursive it returns the collection as it should be. like this. i have changed it toArray() to make it easy to read.
array:1 [
0 => array:6 [
"id" => 5
"name" => "I am a child of 1"
"parent_id" => "1"
"created_at" => "2016-12-26 13:53:50"
"updated_at" => "2016-12-26 13:53:50"
"children_recursive" => array:1 [
0 => array:6 [
"id" => 6
"name" => "I am child of 5"
"parent_id" => "5"
"created_at" => "2016-12-26 13:53:50"
"updated_at" => "2016-12-26 13:53:50"
"children_recursive" => array:2 [
0 => array:6 [
"id" => 7
"name" => "I am child of 6"
"parent_id" => "6"
"created_at" => "2016-12-26 13:53:50"
"updated_at" => "2016-12-26 13:53:50"
"children_recursive" => []
],
1 => array:6 [
"id" => 8
"name" => "I am child of 6 too"
"parent_id" => "6"
"created_at" => "2016-12-26 13:53:50"
"updated_at" => "2016-12-26 13:53:50"
"children_recursive" => []
]
]
]
]
]
]
what I want to achieve is the collection to be single dimension. here is how the toArray() to that collection should look like.
array:4 [
0 => array:6 [
"id" => 5
"name" => "I am a child of 1"
"parent_id" => "1"
"created_at" => "2016-12-26 13:53:50"
"updated_at" => "2016-12-26 13:53:50"
],
1 => array:6 [
"id" => 6
"name" => "I am child of 5"
"parent_id" => "5"
"created_at" => "2016-12-26 13:53:50"
"updated_at" => "2016-12-26 13:53:50"
],
2 => array:6 [
"id" => 7
"name" => "I am child of 6"
"parent_id" => "6"
"created_at" => "2016-12-26 13:53:50"
"updated_at" => "2016-12-26 13:53:50"
],
3 => array:6 [
"id" => 8
"name" => "I am child of 6 too"
"parent_id" => "6"
"created_at" => "2016-12-26 13:53:50"
"updated_at" => "2016-12-26 13:53:50"
]
]
I have tried many collection methods like filter, flatMap, flatten and multiple array methods. but haven't found an appropriate solution.
It's a bit late, but I'm going to post what I wish I had been able to find before I ended up writing it myself.
Similar to the original post, I have a recursive parent/child relationship in my categories table (but this could apply to any table with a self-referencing parent_id column). You can set up your Model like this:
Category.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Category extends Model {
// Relationships
public function parent()
{
return $this->belongsTo('App\Models\Category', 'parent_id');
}
public function children()
{
return $this->hasMany('App\Models\Category', 'parent_id');
}
public function nested_ancestors()
{
return $this->belongsTo('App\Models\Category', 'parent_id')->with('parent');
}
public function nested_descendants()
{
return $this->hasMany('App\Models\Category', 'parent_id')->with('children');
}
// Attributes
public function getFlatAncestorsAttribute()
{
return collect(flat_ancestors($this));
}
public function getFlatDescendantsAttribute()
{
return collect(flat_descendants($this));
}
}
Then somewhere in your application, you need to have a place to put some global helper functions. You could follow the instructions found here, and then just paste in the following helper functions:
Helpers.php
function flat_ancestors($model) {
$result = [];
if ($model->parent) {
$result[] = $model->parent;
$result = array_merge($result, flat_ancestors($model->parent));
}
return $result;
}
function flat_descendants($model) {
$result = [];
foreach ($model->children as $child) {
$result[] = $child;
if ($child->children) {
$result = array_merge($result, flat_descendants($child));
}
}
return $result;
}
The code above will then allow you to use $category->flat_ancestors, which will produce a flat collection of all the category's ancestors, no matter how many there are. Similarly, using $category->flat_descendants will yield a flat collection of all the child categories, and the child's children categories, and so on until all the posterity categories have been accounted for.
Some things to be careful of:
This type of approach could potentially lead to an infinite loop if
you have Category 1 referencing Category 2 as its parent, and
then Category 2 has Category 1 as its parent. Just be careful
that parent/child relationships are incest free :-)
This type of approach also isn't very efficient. It'll be fine for a bunch of
parent/child recursive relationships, but especially for the
flat_descendants functions, the number of database queries grows
exponentially for each generation level.
I didn't find any builtin method into theLaravel collection either. You may try something like this (Use it as a global function or as a dedicated class method, it's up to you. here is the idea):
function flatten($array) {
$result = [];
foreach ($array as $item) {
if (is_array($item)) {
$result[] = array_filter($item, function($array) {
return ! is_array($array);
});
$result = array_merge($result, flatten($item));
}
}
return array_filter($result);
}
Then use it like this:
// When available into global scope as a function
$flattenArray = flatten($arrayFromTheCollection);
This will will recursively flatten. It doesn't prevent duplicates though, so you'll need to filter them out if that's an issue.
In your AppServiceProvider::boot method
use Illuminate\Support\Collection;
//...
Collection::macro('flattenTree', function ($childrenField) {
$result = collect();
foreach ($this->items as $item) {
$result->push($item);
if ($item->$childrenField instanceof Collection) {
$result = $result->merge($item->$childrenField->flattenTree($childrenField));
}
}
return $result;
});
Then
$flattened = $myCollection->flattenTree('childrenRecursive');
// or in the case of the question
$flattened = $model->childrenRecursive->flattenTree('childrenRecursive');
this is my code, it might help ^_^
Collection::macro('flattenTree', function ($childrenField = 'children', $levelAttribute = 'level')
{
$toProcess = $this->items;
$processed = [];
while($item = array_shift($toProcess))
{
$item->$levelAttribute ++;
$processed[] = $item;
if (count($item->$childrenField) > 0) {
$children = array_reverse($item->$childrenField->items);
foreach ($children as $child) {
$child->$levelAttribute = $item->$levelAttribute;
array_unshift($toProcess,$child);
}
}
}
return Collection::make($processed);
});
you should put this code in the boot method of AppServiceProvider.php or any provider you wish, and then you can use it like this
Category::where('parent_category_id', null)->get()->flattenTree();
this will flat the tree and add a level attribute to each object to indicate the depth level of the object
good luck for everyone
For these who does run into a dead loop because of incest relationship, I used this solution to retrieve descendants' attributes through eager loaded relationship - worked like fully flattening the relationship but avoid running into dead loop by foreach.
Solution link

Laravel MongoDB group by

I'm currently using jenseggers-mongodb eloquent in laravel 5 with mongodb 3.0.3.
I'm trying to join two different tables as follows:
User Model
public function userpayment() {
return $this->hasMany('App\Models\Userpayment', 'user_id', '_id');
}
Userpayment model
public function user() {
return $this->belongsTo('App\Models\User','user_id');
}
I'm using repository pattern
public function __construct() {
$this->model = new User;
}
return $this->model->with(array('userpayment'=>function($query){
$query
->groupBy('dealCodeNumber')
->get();
}))
->where('_id',$sid)
->get();
groupBy dealCodeNumber returns NULL.
array:1 [▼
0 => array:15 [▼
"_id" => "55813f4cbc59a3f1372e9e6f"
"full_name" => "Varathan"
"username" => "shopsyadmin"
"web_url" => ""
"userpayment" => []
]
without groupby returns all.
array:1 [▼
0 => array:15 [▼
"_id" => "55813f4cbc59a3f1372e9e6f"
"full_name" => "Varathan"
"username" => "shopsyadmin"
"userpayment" => array:2 [▼
0 => array:57 [▼
"_id" => "55816ce7cc7d4bac0d000029"
"dealCodeNumber" => "1398410837"
"inserttime" => "1398411401"
"status" => "Pending"
]
1 => array:57 [▶]
]
Querylog with groupby
[query] => shopsy_user_payment.aggregate([{"$match":{"user_id":{"$in":["55813f4cbc59a3f1372e9e6f"]}}},{"$group":{"_id":{"dealCodeNumber":"$dealCodeNumber"}
Why groupby returns null?
To group an column it must be selected before grouping. Changing code to below works:
return $this->model->with(array('userpayment'=>function($query){
$query
->select('product_id','user_id','dealCodeNumber')
->groupBy('dealCodeNumber')
->get();
}))
->where('_id',$sid)
->get();

Categories