Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Filth's avatar

saving Polymorphic Many to Many

This is giving me a big headache now. when adding tags to a product they are saving fine and both tags and taggables table are as should be, when editing that product and adding a new tag the tags table is updated correctly (no duplicates) however the taggables table has duplicate relationships.

public function update(Request $request, $slug)
    {
        $product = Product::where('slug', $slug)->firstOrFail();
        $request->merge([
            'slug' => str_slug($request->get('title'))
        ]);
        $product->fill($request->input())->save();
        //sync categories array
        $product->categories()->sync($request->get('categories'));
        // use helper function tags_to_array to turn string into array and remove whitespace
        $tags = tags_to_array($request->get('tags'));
        foreach($tags as $tag){
            $tag = Tag::firstOrCreate(['name' => $tag]);
            $product->tags()->attach($tag);
        }
        return redirect(route('dashboard.products'));
    }

I add tag1,tag2 and save, then edit and add add tag3 (processes tag1,tag2,tag3) the tags table only has the three tags however the taggables table now has 5 rows.

tag_id taggable_id taggable_type

1 234 App\Product

2 234 App\Product

1 234 App\Product

2 234 App\Product

3 234 App\Product

Its like I need to check if the relation exists before attaching?

0 likes
4 replies
igorblumberg's avatar

Hello, Yes, you need to check if the relation exists to avoid duplication

Another option you have is to use the method sync instead of attach. Eloquent will remove all tags that you don't pass and only add the new ones.

If you don't want to remove all tags that are not passed you can pass "false" as a second parameter to the sync method:

http://laravel.com/api/5.1/Illuminate/Database/Eloquent/Relations/BelongsToMany.html#method_sync

So, inside your foreach just call

$product->tags()->sync($tag,false);

And it should work.

Filth's avatar

@igorblumberg I tried previously with sync however sync requires an array so I have to remove sync from my foreach loop which creates the tags in the table. sync then adds the taggable records with a tag_id of 0.

public function update(Request $request, $slug)
    {
        $product = Product::where('slug', $slug)->firstOrFail();
        $request->merge([
            'slug' => str_slug($request->get('title'))
        ]);
        $product->fill($request->input())->save();
        //sync categories array
        $product->categories()->sync($request->get('categories'));
        // use helper function tags_to_array to turn string into array and remove whitespace
        $tags = tags_to_array($request->get('tags'));
        
            $product->tags()->sync($tags);
        
        return redirect(route('dashboard.products'));
    }

I need to run a foreach loop for firstOrCreate Tag but when I do this I cant use sync as sync needs an array(), I pass sync an array and I cant firstOrCreate the tags?

igorblumberg's avatar

@Philwn you're correct, sync needs an array.

A dirty solution could be

$product->tags()->sync(array($tag->id),false);

Or, back to your first code:

$new_tags = array();
foreach($tags as $tag){
            $tag = Tag::firstOrCreate(['name' => $tag]);
            $new_tags[] = $tag->id;
        }
$product->tags()->sync($new_tags,false);

Would this work?

1 like
Filth's avatar
Filth
OP
Best Answer
Level 4

The following works without creating duplicates, I have to detach each tag before attaching to do it in my loop? Is this the intention with trying to update records with polymorphic many to many relationships?

I think it would be beneficial for @JeffreyWay to make a video which delves into polymorphic more, I have spent hours searching other help forums, alot of epople seem to struggle with these.

His video explaining the model setup etc is great but it doesnt touch on updating the data in a real world app.

   public function update(Request $request, $slug)
    {
        $product = Product::where('slug', $slug)->firstOrFail();
        $request->merge([
            'slug' => str_slug($request->get('title'))
        ]);
        $product->fill($request->input())->save();
        //sync categories array
        $product->categories()->sync($request->get('categories'));
        // use helper function tags_to_array to turn string into array and remove whitespace
        $tags = tags_to_array($request->get('tags'));
        foreach($tags as $tag){
            $tag = Tag::firstOrCreate(['name' => $tag]);
            $product->tags()->detach($tag);
            $product->tags()->attach($tag);
        }
        return redirect(route('dashboard.products'));
    }

Please or to participate in this conversation.