1

I thought I was going quite well in developing my Laravel skills, but it seems I am stuck and can't find the solution online yet.

My project is set up in Laravel and Vue and I am trying to store a product with ingredients. The products are in the products table. The ingredients in the ingredients table. I succeeded in storing the product with a brand_id and later retrieve the brand (one-to-many), but I don't know how to do this for many-to-many: Ingredients to products.

Since I am working with Vue I have add JSON output that posts my data.

public function store()
{
    $product = new Product();

    $product->ean =             request('ean');
    $product->name =            request('productName');
    $product->brand_id =        request('brandId');

    $ingredients =              request('ingredients');

    $product->save();
}

As I explained above saving the product is going well. But now I need to do something with the $ingredients array, I've found lines like these:

$user->roles()->save($role);

So I think I have to do a foreach loop for all ingredients and within do this:

$product->ingredients()->save($ingredient);

What I fail to understand. This array will contain existing ingredients that are already stored, but also new ingredients that have to be added to the ingredients table. How to make this work?

So store new ingredients in it's table and also store the relation in the eloquent table. I might be close, I might be far off, anyone?

TheLD
  • 2,211
  • 4
  • 21
  • 26

1 Answers1

1

While looping the ingredients, look into the firstOrNew() method:

foreach($request->input("ingredients") AS $ingredient){
  $ingredient = Ingredient::firstOrNew(["name" => $ingredient]);
  // Note: assumed name for column that matches `$ingredient` from array, adjust as needed.
}

While in that loop, attach() your $ingredient to your new $product. Your code should look like this when fully implemented:

$product = new Product();
...
$product->save();

foreach($request->input("ingredients") AS $ingredient){
  $ingredient = Ingredient::firstOrNew(["name" => $ingredient]);
  $product->ingredients()->attach($ingredient->id); 
}

attach() will associate this new or existing $ingredient with the new $product by adding a record to the pivot between Product and Ingredient. The reason you can't use $product->ingredients()->save($ingredient); is that this is a many-to-many, which requires attach(). $model->relationship()->save(); is used for a one-to-many.

Full documentation on the creation methods can be found here: https://laravel.com/docs/5.8/eloquent#other-creation-methods

Tim Lewis
  • 27,813
  • 13
  • 73
  • 102
  • He could also firstOrNew in the first loop, then put the I'd value of the retrieved/inserted ingredient into an $ingredients and use `$product->ingredients()->sync($ingredients));` out of the foreach to bulky sync the relations – mdexp Jun 26 '19 at 23:13
  • Thank you for your answer. It seems to help me get one step further. Got a new error now, it seems theres no product ID send with the attach() call: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'product_id' cannot be null (SQL: insert into `product_ingredients` (`ingredient_id`, `product_id`) values (2, )) EDIT: Nvm, had to place $product->save() before my foreach – TheLD Jun 27 '19 at 09:28
  • Oh, btw, I changed firstOrNew() to firstOrCreate() so I didn't have to manually save the ingredients. – TheLD Jun 27 '19 at 12:09