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

fromthedark_14's avatar

Laravel - Many to Many Relationship

Hello everyone! I am trying to build a blog and my problem is how to connect the two tables (tags and posts) and store their ID's in my pivot table.

tags table: id name posts table: id name

post_table id post_id tag_id

I have already stored some values to my tag and post table. All I want to do is to get id from the post table and also from my tag table and connect them through many to many relationship.

2018_03_17_221539_create_tags_table.php

Schema::create('tags', function(Blueprint $table){
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });

2018_03_17_221539_create_posts_table.php

Schema::create('posts', function(Blueprint $table){
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });

2018_03_17_221539_create_post_tag_table.php

Schema::create('post_tag', function(Blueprint $table){
            $table->increments('id');
            $table->integer('post_id')->unsigned();
            $table->foreign('post_id')->references('id')->on('posts');
            $table->integer('tag_id')->unsigned();
            $table->foreign('tag_id')->references('id')->on('tags');
            $table->timestamps();
        });

Tag Model

public function tags(){
        return $this->belongsToMany('Tag', 'post_tag', 'post_id', 'tag_id');
    }

PostModel

public function posts(){
        return $this->belongsToMany('Post', 'post_tag', 'post_id', 'tag_id');
    }

controller

public function store(Request $request)
    {

        $brands =  Input::get('posts');
        $brands->tags()->sync($request->tags, false);
        return Redirect::back();

    }

route

    Route::post('brand/create', 'AdminController@store')->name('brand/create');

views

{{Form::open(array('url'=>'brand/create', 'method' => 'post', 'files'=>true))}}
    <div class="form-group">
                            <label for="">Post</label>
                            <select name="posts[]" id="brands" class="input-sm">
                                    @foreach($postsas $post)
                                        <option value="{{$post->id}}">{{$post->name}}</option>
                                     @endforeach
                            </select>
         </div>

          <div class="form-group">
                            <label for="">Category</label>
                            <select name="tags[]" class="input-sm">
                                    @foreach($tagsas $tag)
                                            <option value="{{$tag->id}}">{{$tag->name}}</option>
                                     @endforeach
                            </select>
                            <a href="#" class="btn btn-danger btn-xs btn-remove-cat">Remove</a>
          </div>
                        <button class="btn btn-sm btn-primary">Save</button>
 {{Form::close()}}

I got this error

(1/1) FatalThrowableError Call to a member function tags() on array.

Thanks!

0 likes
34 replies
rin4ik's avatar

this code returns array of posts $brands = Input::get('posts');

Snapey's avatar

One of your relationships is wrong.

In addition to customizing the name of the joining table, you may also customize the column names of the keys on the table by passing additional arguments to the belongsToMany method. The third argument is the foreign key name of the model on which you are defining the relationship, while the fourth argument is the foreign key name of the model that you are joining to:

Your models seem to be wrong. Tag model should have a posts method, and Post a tags method

Post Model

public function tags()
{
    return $this->belongsToMany('Tag', 'post_tag', 'post_id', 'tag_id');
}

Tag Model

public function posts()
{
    return $this->belongsToMany('Post', 'post_tag', 'tag_id', 'post_id');
}

... and the 3rd and 4th parameters should be opposite.

I find it helpful to test the relationships in Tinker before going any further.

Snapey's avatar

oh, and if you are going to state an error, tell us what line of code in what file it failed on

rin4ik's avatar

or you can use this

PostModel

public function tags(){
        return $this->belongsToMany(Tag::class);
    }

TagModel

public function posts(){
        return $this->belongsToMany(Post::class);
    }
fromthedark_14's avatar

Hello @Snapey This is where my error, on my controller, the tags():

$brands->tags()->sync($request->tags, false);

I think that this causes the error: $brands = Input::get('posts');

rin4ik's avatar

@fromthedark_14 I think you are doing wrong operations here. you want to attach many posts to many tags.(which is from my experience impossible) instead you should for one post sync many tags or for one tag many post ids :)

fromthedark_14's avatar

I tried this in my controller and it works, but this is not what I want to do.

    $brands = Post::find(1);
        $brands->tags()->sync($request->tags, false);

So I think $brands = Input::get('posts'); is my error

rin4ik's avatar

like I said earlier this returns array of posts $brands = Input::get('posts');you can't sync array of elements in relationship.

fromthedark_14's avatar

Thanks @rin4ik, how to attach one post many tags? What should I replace to this? $brands = Input::get('posts');

rin4ik's avatar

you have to expect post from view. and pass that. you have to change all logic here routes as well.

public function store(Request $request, Post $post)
        $post->tags()->sync($request->tags, false);

it is just my thoughts maybe @Snapey has more better approach of doing this

2 likes
Snapey's avatar

@rin4ik is correct if you are getting an array of tag id's back from the form.

but to be honest, I don't understand the requirement. You have a form that presents a list of posts and a list of tags. You select one of each? How does this relate to 'brands' ?

What exactly are you looking to achieve?

by the way, did you fix the relationships?

fromthedark_14's avatar

Yes @Snapey, I already fix the relationships. Thanks...

You're right, I want to have a form with list of posts and tags. Sorry I did not explain it clearly from the beginning.

Actually, the 'brands', I just randomly pick a word. That should be $post.

Snapey's avatar

If your form still looks like this;

{{Form::open(array('url'=>'brand/create', 'method' => 'post', 'files'=>true))}}
    <div class="form-group">
                            <label for="">Post</label>
                            <select name="posts[]" id="brands" class="input-sm">
                                    @foreach($postsas $post)
                                        <option value="{{$post->id}}">{{$post->name}}</option>
                                     @endforeach
                            </select>
         </div>

          <div class="form-group">
                            <label for="">Category</label>
                            <select name="tags[]" class="input-sm">
                                    @foreach($tagsas $tag)
                                            <option value="{{$tag->id}}">{{$tag->name}}</option>
                                     @endforeach
                            </select>
                            <a href="#" class="btn btn-danger btn-xs btn-remove-cat">Remove</a>
          </div>
                        <button class="btn btn-sm btn-primary">Save</button>
 {{Form::close()}}

then as you don't use multiple on the select elements it won't ever return multiple tags or posts, so in both cases, the name should be 'tag' and 'post' and not with the array[] characters

Is this your whole form? I still don't understand the use case?

fromthedark_14's avatar

I am new to laravel, I just want to test if it possible to do this because I am going to build another project later on.

I've already stored some values to my posts and tags table. The next thing would be to select one from post and many tags @Snapey

fromthedark_14's avatar

Hi @rin4ik I tried this:

public function store(Request $request, Post $post)
        $post->tags()->sync($request->tags, false);

Route:

Route::post('/blog/create/{post}',['as'=>'blog.create','uses'=>'AdminController@store']);

View:

{{ Form::open(['route' => ['blog.create', $post->id], 'method' => 'POST']) }}
                        <div class="form-group">
                            <label for="">Post</label>
                            <select name="post" class="input-sm">
                                @foreach($post as $row)
                                    <option value="{{$row->id}}">{{$row->name}}</option>
                                @endforeach
                            </select>
                        </div>

                        <div class="form-group">
                            <label for="">Tags</label>
                            <select name="tags[]" class="input-sm">
                                @foreach($tags as $tag)
                                    <option value="{{$tag->id}}">{{$tag->name}}</option>
                                @endforeach
                            </select>
                        <button class="btn btn-sm btn-primary">Save</button>
                    {{Form::close()}}

I got this error:

(1/2) Exception
Property [id] does not exist on this collection instance.

in Collection.php (line 1650)
Snapey's avatar

You would probably not use create with a {post} variable in your route since that implies that the post already exists. If thats the case then it is not a create that you are performing

2 likes
rin4ik's avatar

@fromthedark_14 examle of creating post with tags

View

<form action="/posts" method="POST" >
         <!-- Default box -->
    {{csrf_field()}}
         <div class="box">
        <div class="box-header with-border">
          <h3 class="box-title">Add Post</h3>
        </div>
        <div class="box-body">
          <div class="col-md-6">
            <div class="form-group">
              <label for="title">Title</label>
              <input name="title" type="text" class="form-control" id="title" placeholder="" value="{{old('title')}}">
        
            </div>
            <div class="form-group">
              <label for="title">Title</label>
              <textare name="body" type="text" class="form-control" id="body" placeholder="">
        {{old('body')}}
            </div>
            
       
              <div class="form-group">
                <label>Tags</label>
                <select class="form-control select2" multiple="multiple" data-placeholder="Choose Tags" name="tags[]">
                      
                          @if ($tags->count())
                      
                              @foreach($tags as $tag)
                                  <option value="{{ $tag->id }}">{{ $tag->title }}</option>    
                              @endforeach
                          @endif
                      
                      </select>
              </div>
          </div>      
      </div>

        <!-- /.box-body -->
        <div class="box-footer"> 
          <button type="submit" class="btn btn-success pull-right">Submit</button>
        </div>
        <!-- /.box-footer-->
      </div>
      <!-- /.box -->
</form>

Route

Route::post('/posts', PostsController@store);

store method in

PostsController

    public function store(Request $request)
    {
        request()->validate([
            'title' => 'required',
            'body' => 'required', 
        ]);
        $post = Post::add($request->all());
       $post->setTags($request->get('tags'));
            return redirect()->route('posts.index')->with('flash', 'Post created!');
    }

Post Model

 public static function add($fields)
    {
        $post = new static;
        $post->fill($fields);
        $post->user_id = 1;
        $post->save();
        return $post;
    }
   public function setTags($ids)
    {
        if ($ids == null) {
            return;
        }
        $this->tags()->sync($ids);
    }
fromthedark_14's avatar

@rin4ik I have already stored some values to my posts and tags table. My goal is to select one from post and select many tags then sync both of them :)

rin4ik's avatar

ok with your setup try this

{{ Form::open(['route' => ['blog.create'], 'method' => 'POST']) }}
Route::post('/blog/create',['as'=>'blog.create','uses'=>'AdminController@store']);
public function store(Request $request)
{
    $post = Post::where('id', $request->post)->get();
    $post->tags()->sync($request->tags);
}  

rin4ik's avatar

do you have these relationships?

PostModel

public function tags(){
        return $this->belongsToMany(Tag::class);
    }

TagModel

public function posts(){
        return $this->belongsToMany(Post::class);
    }
rin4ik's avatar

@fromthedark_14 please show the output of this

    $post = Post::where('id', $request->post)->get();
dd($post);    
$post->tags()->sync($request->tags);
rin4ik's avatar

@fromthedark_14 yes you don't get Post instance now show me this

dd($request->all());
$post = Post::where('id', $request->post)->get();    
$post->tags()->sync($request->tags);
Next

Please or to participate in this conversation.