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

Filth's avatar

Polymorphic Many to Many relationships and Sync

I have multiple tables that will be tagged so someone here pointed me in the direction of Polymorphic Many to Many relationships. I read the documentation and watched Jeffreys video. My tags and tags and taggables table are set up the same as his in the video.

tags( id, name) taggables(tag_id, taggable_id, taggable_type)

I created the models as shown in his video:

// app\Product.php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = [
        'code_id',
        'title',
        'slug',
        'body',
        'display',
        'new',
        'published_at',
        'image_id', 
        'excerpt'
    ];
    
    public function categories() {
        
        return $this->belongsToMany('App\Category');
    
    }
    
         public function status()
    {
        return $this->belongsTo('App\Status', 'display', 'id');
    }
    
    public function image()
    {
        return $this->belongsTo('App\ImageModel');
    }
    
    public function codes()
    {
        return $this->hasMany('App\Code');
    }
    
    public function tags()
    {
        return $this->morphToMany('App\Tag', 'taggable');
    }
    
}
// app\Tag.php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    public function articles()
    {
        return $this->morphedByMany('App\Article', 'taggable');
    }
    
    public function anglers()
    {
        return $this->morphedByMany('App\Angler', 'taggable');
    }
    
    public function products()
    {
        return $this->morphedByMany('App\Product', 'taggable');
    }
    
    public function videos()
    {
        return $this->morphedByMany('App\Video', 'taggable');
    }

}

I have a textarea called tags in a view, My form submits and astores all the other relevant information. when it comes to my tags it inserts records into taggables table with a blank tag id and nothing is recorded in the tags table.

my Method:

    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
        $product->tags()->sync(tags_to_array($request->get('tags')));
        return redirect(route('dashboard.products'));
    }

my tags_to_array helper function:

function tags_to_array($string)
{
    $array = explode(',', $string);
    $result= array();
    foreach($array as $tag){
        $result[] = trim($tag);
    }
    return $result;
}

if I enter 3 keywords seperated by commas I recieve 3 records in taggables with blank tag ids and 0 records in tags.

Am I missing something?

NOTE: for anyone reading this thread, I accepted @bobbybouwmann first reply as the correct answer as that is technically what had to happen, how ever I had to change a few other things to get everything working as should so read rest of thread.

0 likes
11 replies
bobbybouwmann's avatar
Level 88

You need to create Tag models first before you can do that

function tags_to_array($string)
{
    $tags = explode(',', $string);

    // Create an empty collection
    $result = collect();

    foreach($tags as $tag){
        // Create a new tag if it doesn't exist and push it to the collection
        $result->push(Tag::firstOrCreate(['name' => $tag)); 
    }

    return $result;
}

The $result will now contain all the tags you need

1 like
Filth's avatar

Ok so I added use App\Tag to my helper, I now get an error however the tags are stored

ErrorException in BelongsToMany.php line 806: Argument 1 passed to Illuminate\Database\Eloquent\Relations\BelongsToMany::formatSyncList() must be of the type array, object given, called in /volume1/web/laravel/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php on line 771 and defined

if I turn it into an array I get another error:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'field list' (SQL: insert into `taggables` (`created_at`, `id`, `name`, `tag_id`, `taggable_id`, `taggable_type`, `updated_at`) values (2015-12-02 12:16:15, 1, test, 0, 234, App\Product, 2015-12-02 12:16:15))

the id column is called taggable_id but it also appears the insert values have the name and id the wrong way around and the id is 0 for some reason?

Filth's avatar

@bobbybouwmann Using PHP Tinker the following seems to work

>>> $product = Product::find(5)
>>> $product->tags()->firstOrCreate(['name' => 'great']);
=> App\Tag {#777
     name: "great",
     updated_at: "2015-12-02 12:46:03",
     created_at: "2015-12-02 12:46:03",
     id: 1,
   }

however if I add the same 'great' tag to another product

>>> $product = Product::find(9)
>>> $product->tags()->firstOrCreate(['name' => 'great']);
=> App\Tag {#780
     name: "great",
     updated_at: "2015-12-02 12:47:05",
     created_at: "2015-12-02 12:47:05",
     id: 2,
   }

My tags table now has great in there twice?

is firstOrCreate not suppose to prevent duplicates?

bobbybouwmann's avatar

Try this in tinker

$tag = Tag::firstOrCreate(['name' => 'great']);

$product = Product::find(9);
$product->tags()->attach($tag);

$product = Product::find(5);
$product->tags()->attach($tag);

Calling firstOrCreateon the relation method will make new instances of the model

1 like
hmreumann's avatar

@bobbybouwmann Hallo, I know this post is a little old, I'm trying to use firstOrCreate on a polimorphic relation, but it is creating new models with the same number:

		foreach($phones as $phone){
				$user->phones()->firstOrCreate(['number' => $phone->number ]);
		}

Do you know why it is happening? Thanks in advance. Hernán.

bobbybouwmann's avatar

@hmreumann I would expect that this works out of the box. I can't find anything in the code why this wouldn't work. The only thing I can think of is that the polymorphic fields are missing here (phoneable_id and phoneable_type)

1 like
hmreumann's avatar

@bobbybouwmann Ok, I'm sorry because I didn't explain the issue I was having. I wanted firstOrCreate to work as expected, I mean not creating a new phone with the same number (this was the problem). After some research I noticed that I had a mutator in the model. So, the number was changed prior being stored in the DB, so the method searched for a number that didn't exist, and created a new one. What I did was changing the mutator to an accesor and the issue was solved. Thanks.

Filth's avatar

Ok I see now, that works and has the desired result, now I have it working in tinker, time to translate this into my project and get it working there.

Filth's avatar

Im getting a lot closer, tags and taggable are being populated by the tags I enter however for each tag an additional record is created in taggables with tag_id as 0. I assume this is something to do with converting the collection into an array?

    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){
            $product->tags()->attach($tag);
        }
        return redirect(route('dashboard.products'));
    }
function tags_to_array($string)
{
    $tags = explode(',', $string);

    // Create an empty collection
    $result = collect();

    foreach($tags as $tag){
        // Create a new tag if it doesn't exist and push it to the collection
        $result->push(Tag::firstOrCreate(['name' => trim($tag)]));
    }

    return $result->toArray();
}
Filth's avatar

It was something to do with the collection being cast to an array. The following helper function has fixed my issue and everything works as should. thank you for your help @bobbybouwmann

function tags_to_array($string)
{
    $tags = explode(',', $string);

    // Create an empty array
    $result = array();

    foreach($tags as $tag){
        // Create a new tag if it doesn't exist and push it to the collection
        $result[] = Tag::firstOrCreate(['name' => trim($tag)]);
    }

    return $result;
}

Please or to participate in this conversation.