Rediska's avatar

How to get all unique attributes of a collection through a many-to-many relationship?

It seems I wrote nonsense in the question)) But I will try to explain in more detail. Implemented in laravel. I need to get all available "color" attributes from a collection of products. I do like this (many-to-many relationship through an intermediate table):

$colors = $products->pluck('colors')
->filter(function($value, $key) {
                return  $value != '[]';
            })
   ->unique();

But that doesn't work. Because in this case, unique is applied to the first nested collections (otherwise the products themselves), and each product has its own nested collection of the "color" attribute.

That is, if you call dd($products), then I get this (Excess removed for readability):

Illuminate\Pagination\LengthAwarePaginator {#1594 ▼
  #total: 7
  #items: Illuminate\Database\Eloquent\Collection {#1814 ▼
    #items: array:7 [▼
      0 => App\Models\Product {#1681 ▼
        #fillable: array:17 [▶]
        ...
        #attributes: array:20 [▶]
        ...
        #relations: array:9 [▼
          "colors" => Illuminate\Database\Eloquent\Collection {#1760 ▼
            #items: array:2 [▼
              0 => App\Models\Color {#1782 ▼
                ...
                #attributes: array:12 [▼
                  "id" => 1
                  "title" => "blue"
                ]
              ...
              1 => App\Models\Color {#1778 ▼
                ...
                #attributes: array:12 [▼
                  "id" => 3
                  "title" => "red"
                ]
              ...
      1 => App\Models\Product {#1649 ▶}
...

Each item has a color. Some have it, some have not, some have several. I need to get unique colors (with id and title) without repetition.

How can I implement this correctly?

0 likes
12 replies
aruszala's avatar

@rediska Try:


// ...

$colors = $products->pluck('colors.id', 'colors.title')
    ->filter(function($value, $key) {
        return  $value != '[]';
    })->unique();

//...

Snapey's avatar

I don't understand, you are saying that a single product might have a color repeated more than once?

What do you want the output to look like for a collection of products?

Rediska's avatar

@Snapey I am getting a list of products by category. For example, there are 5 of them. Each product has colors (via many-to-many relationship) 1 product - red and blue 2 product - blue 3 product - black 4 product - [] 5 product - [] To implement a product filter, I need to get all available colors without repetition. That is:

0 => 
            "id" => 1
            "title" => "red"
1 => 
            "id" => 3
            "title" => "blue"
2=> 
            "id" => 10
            "title" => "black"
Rediska's avatar

So what I do is I get:

Illuminate\Support\Collection {#1617 ▼
  #items: array:3 [▼
    2 => Illuminate\Database\Eloquent\Collection {#1844 ▼
      #items: array:1 [▼
        0 => App\Models\Color {#1861 ▶}
      ]
      #escapeWhenCastingToString: false
    }
    4 => Illuminate\Database\Eloquent\Collection {#1845 ▼
      #items: array:1 [▼
        0 => App\Models\Color {#1859 ▶}
      ]
      #escapeWhenCastingToString: false
    }
    6 => Illuminate\Database\Eloquent\Collection {#1846 ▼
      #items: array:1 [▶]
      #escapeWhenCastingToString: false
    }
  ]
  #escapeWhenCastingToString: false
}

It is now necessary to apply unique to these nested collections in the array

Snapey's avatar

so you dont need to know which colors go with which product

something like;

    $colors = $category->products()
            ->with('colors')
            ->get(['id'])
            ->map(function($product) {
                return $product->colors->pluck('title','id');
            })
            ->unique();
Rediska's avatar

@Snapey And again nothing happens. I'm desperate))

SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in field list is ambiguous (SQL: select `id`, `category_product`.`category_id` as `pivot_category_id`, `category_product`.`product_id` as `pivot_product_id` from `products` inner join `category_product` on `products`.`id` = `category_product`.`product_id` where `category_product`.`category_id` = 49 and `products`.`deleted_at` is null)
Snapey's avatar

@Rediska

ok, you need to qualify the id

    $colors = $category->products()
            ->with('colors')
            ->get(['products.id'])
            ->map(function($product) {
                return $product->colors->pluck('title','id');
            })
            ->unique();
Rediska's avatar

@Snapey Already almost it turns out, but all the same are duplicated. And I get some kind of nested friend in suddenly collections. Did it like this:

$colors = $products->pluck('colors ')
            ->filter(function($value, $key) {
                return  $value != '[]';
            })
            ->map(function($product) {
                return $product->pluck('title', 'id');
            })
            ->unique();

Will get this:

Illuminate\Support\Collection {#1618 ▼
  #items: array:3 [▼
    0 => Illuminate\Support\Collection {#1559 ▼
      #items: array:2 [▼
        5 => "red"
        6 => "blue"
      ]
      #escapeWhenCastingToString: false
    }
    2 => Illuminate\Support\Collection {#1590 ▼
      #items: array:1 [▼
        5 => "red"
      ]
      #escapeWhenCastingToString: false
    }
    5 => Illuminate\Support\Collection {#613 ▶}
  ]
  #escapeWhenCastingToString: false
}

And I would like this (and without repetition):

0 => 
            "id" => 1
            "title" => "red"
1 => 
            "id" => 3
            "title" => "blue"
2=> 
            "id" => 10
            "title" => "black"

  • 0, 1 and 2 are not product IDs. It's just color numbering.
Rediska's avatar

I can of course add ->toArray(). And then go through the loop and remove all repetitions. But it would be desirable to make it all in one request.

Rediska's avatar

@Snapey I think I found a solution =)

$colors= $products->pluck('colors')
            ->filter(function($value, $key) {
                return  $value != '[]';
            })
            ->collapse()
            ->unique('title');
PovilasKorop's avatar

@rediska if you need colors and not products, wouldn't it be better to query Colors? from the other way around?

Haven't tested but something like this:

Color::whereHas('products', function ($query) use ($category) {
    $query->where('category_id', $category);
})->groupBy('name')->groupBy('id')->pluck('name', 'id');

And yes, I do realize it's another query which is almost duplicate to what you already have in category products, but thought that sometimes two queries are ok to run for readability instead of trying to have less-readable code from Collection "magic".

Please or to participate in this conversation.