3

I have a product table in my Laravel project, products. I have another table, product_assocs:

CREATE TABLE `product_assocs` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `product_id_primary` bigint(20) UNSIGNED DEFAULT NULL,
  `product_id_assoc` bigint(20) UNSIGNED DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

which allows me to connect related products to each other. I want these relations to be symmetrical. I.e., if you create one record in the product_assocs table, it creates a relation that works both ways.

I'm defining a method in my product model to fetch the ids of associated products:

$associationsForProduct = $product->associated()->get();

This method draws on a function in the Products model like so:

public function associated() {
      return $this->belongsToMany(Product::class, 'product_assocs', 'product_id_primary', 'product_id_assoc');

I'm having some trouble doing this 'the laravel way'. Obviously, when I get associated products for a particular product $id, I have to fetch all records in product_assocs where id1=$id OR id2=$id. I can imagine an old-school PHP loop that might accomplish this:

// assume $assocs is an array of records fetched where product_id_primary=$id or product_id_assoc=$id
// assume $id is the current product for which we are fetching records
$clean = array();
foreach($assocs as $asc) {
  if ($asc["product_id_primary"] != $id && !in_array($asc["product_id_primary"], $clean)) {
    $clean[] = $asc["product_id_primary"];
  }
  if ($asc["product_id_assoc"] != $id && !in_array($asc["product_id_assoc"], $clean)) {
    $clean[] = $asc["product_id_assoc"];
  }

}

Am I to understand that if I made a reciprocal function in the Product model to query the second ID from the pivot table I could somehow join the function in the controller? Maybe something like this:

public function related() {
      return $this->belongsToMany(Product::class, 'product_assocs', 'product_id_assoc', 'product_id_primary');

Then how's the correct way to formulate the controller statement so it looks for relations in both the associated() and the related() functions here:

$associationsForProduct = $product->[--associated()--OPERAND --related()]->get();

I'm wondering if maybe there's a better way to pull this off. Can someone show me how it is recommended to handle this in Laravel?

1 Answers1

0

With a little help from a friend, I managed to resolve this. I was missing the inverse relationship in my product controller.

if (method_exists($data, 'associated')) {
    $association = ($request->has('association')) ? $request->get('association') : [];
    $data->associated()->sync($association);

    // create the inverse relationships
    $associated = Product::whereIn('id', $association)->get()->each(function($product) use ($data) {
        // doing a remove first to prevent duplicate entries
        $product->associated()->detach($data->id);
        $product->associated()->attach($data->id);
    });
}
jberculo
  • 972
  • 7
  • 24