Search code examples
laraveleloquentpivot

Grouping by an extra field of a pivot table (many-to-may relationship)


In my application I'm using a many to many relationship between an SKU model and an Attributes model. So the pivot table per default is structured with two cols, namely sku_id and attribute_id. I added an extra field to the pivot table holding a value for a specific combination of sku/attribute.

Schematically it looks like the follwing:

attribute_id | sku_id | value
1              1        red
2              1        xs
3              1        v-neck
4              1        linen
1              2        red
2              2        xs
3              2        v-neck
4              2        silk
1              3        red
2              3        xs
3              3        v-neck
4              3        cotton

Problem: In the front end I want to display the values grouped by the attributes and then display the unique values. So coresponding data for the above mentioned rows would be

attribute_id 1 ==> red
attribute_id 2 ==> xs
attribute_id 3 ==> v-neck
attribute_id 4 ==> linen + silk + cotton

As one can guess it is about product variants in an ecommerce system.

Overall an example product consists out of 144 skus (4 colors, 6 sizes, 2 collars, 3 materials). So coresponding data for the full example would be

attribute_id 1 ==> red + yellow + green + blue
attribute_id 2 ==> xs + s  + m + l  + xl  + xxl
attribute_id 3 ==> v-neck + round
attribute_id 4 ==> linen + silk + cotton

My SKU model look slike

    public function attributes(){
       return $this->belongsToMany(Attribute::class, 'attribute_sku', 'sku_id', 'attribute_id')->withPivot('value');
    }

I already read the laravel documentation regarding pluck, filter, map and many more eloquent methods. However, I didnt find any hints.

Can anyone point me in the right direction (eg. laravel docs, tutorial, stackoverflow)


Solution

  • You can use laravel collections mapToGroups for desired result

    I created a sample collection to check

    $collection = collect([
        ['attribute_id' => 1, 'sku_id' => 1, 'value' => 'red'],
        ['attribute_id' => 2, 'sku_id' => 1, 'value' => 'xs'],
        ['attribute_id' => 3, 'sku_id' => 1, 'value' => 'v-neck'],
        ['attribute_id' => 4, 'sku_id' => 1, 'value' => 'linen'],
        ['attribute_id' => 1, 'sku_id' => 2, 'value' => 'red'],
        ['attribute_id' => 2, 'sku_id' => 2, 'value' => 'xs'],
        ['attribute_id' => 3, 'sku_id' => 2, 'value' => 'v-neck'],
        ['attribute_id' => 4, 'sku_id' => 2, 'value' => 'silk'],
        ['attribute_id' => 1, 'sku_id' => 3, 'value' => 'red'],
        ['attribute_id' => 2, 'sku_id' => 3, 'value' => 'xs'],
        ['attribute_id' => 3, 'sku_id' => 3, 'value' => 'v-neck'],
        ['attribute_id' => 4, 'sku_id' => 3, 'value' => 'cotton'],
    ]);
    
    $grouped = $collection->mapToGroups(function (array $item) {
        return [$item['attribute_id'] => $item['value']];
    })->map->unique()->toArray();
    
    dd($grouped);
    

    see result below

    enter image description here

    You can use:

    $productAttributes = $product->attributes->mapToGroups(function ($item) {
            return [$item->attribute_id => $item->pivot->value];
        })->map->unique()->toArray();