113

Can anyone help me on how to save many to many relationship? I have tasks, user can have many tasks and task can have many users (many to many), What I want to achieve is that in update form admin can assign multiple users to specific task. This is done through html multiple select input

name="taskParticipants[]"

The catch here is that through the same form (input) you can add/remove users, that's why I have to use sync(). Maybe I should start from the beginning but don't know where to start...

This is my User model:

public function tasks()
{
    return $this->belongsToMany('Task','user_tasks');
}

Task model

public function taskParticipants()
{
    return $this->belongsToMany('User','user_tasks');
}

TaskController

public function update($task_id)
{
    if (Input::has('taskParticipants'))
    {
        foreach(Input::get('taskParticipants') as $worker)
        {
            $task2 = $task->taskParticipants->toArray();
            $task2 = array_add($task2,$task_id,$worker);
            $task->taskParticipants()->sync(array($task2));
        }
    }
}

This is structure of tables tasks id|title|deadline

user_tasks
id|task_id|user_id
SuperManSL
  • 1,306
  • 2
  • 12
  • 17

6 Answers6

244

tldr; Use sync with 2nd param false


Many-to-many relationship is belongsToMany on both models:

// Task model
public function users()
{
  return $this->belongsToMany('User', 'user_tasks'); // assuming user_id and task_id as fk
}

// User model
public function tasks()
{
  return $this->belongsToMany('Task', 'user_tasks');
}

In order to add new relation use attach or sync.

Difference between the two is:

1 attach will add new row on the pivot table without checking if it's already there. It's good when you have additional data linked to that relation, for example:

User and Exam linked with pivot table attempts: id, user_id, exam_id, score

I suppose this is not what you need in your situation:

$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6]

$user->tasks()->attach([5,6,7]);
// then
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6,5,6,7]

2 sync on the other hand, will either remove all relations and set them up anew:

$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6]

$user->tasks()->sync([1,2,3]);
// then
$user->tasks()->getRelatedIds(); // [1,2,3]

or it will setup new relations without detaching previous AND without adding duplicates:

$user->tasks()->sync([5,6,7,8], false); // 2nd param = detach
// then
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6,7,8]
Alexxus
  • 893
  • 1
  • 11
  • 25
Jarek Tkaczyk
  • 78,987
  • 25
  • 159
  • 157
  • 14
    It'd be nice if that was documented in the main docs rather than the API docs! Rock on. +1. – ceejayoz Jul 11 '14 at 21:25
  • 1
    I really like second solution with sync and 2nd parameter. Like I said in comment bellow, I can't afford not to use detach. The story is that admin can assign task to users. He select users from dropdown (multiple), field is participants[]. So... : Step 1: admin assigns task A to three users (your method works, we have 3 records in DB) Step 2: admin updates task A and adds two users (your method works, we have 5 records in DB) Step 3: admin updates task A and removes 1 user (your method fails, we still have 5 users instead of 4) [my update method my code](http://laravel.io/bin/21KR1) – SuperManSL Jul 11 '14 at 21:30
  • 1
    You can simplify your relationship query to just `$this->belongsToMany('User')` if you use name the table alphabetically and singular (so `task_user` instead of `user_tasks`) – Kousha Jul 11 '14 at 21:50
  • @Rok if you always pass an array of all related users, then use `sync` with detaching, no worries. I suggest using 2nd param set to false whenever you'd like to 'add new task for a user' or 'assign user to a task', when you pass single `id` of the related model. – Jarek Tkaczyk Jul 12 '14 at 08:43
  • Is there any such thing for 'oneToMany' relationship? – halkujabra Jan 18 '15 at 10:49
  • @halkujabra similiar in what way? – Jarek Tkaczyk Jan 18 '15 at 10:59
  • @JarekTkaczyk anything like 'sync' – halkujabra Jan 18 '15 at 11:46
  • @halkujabra just `->relation()->saveMany(array $models)` – Jarek Tkaczyk Jan 18 '15 at 13:13
  • How do you know if sync or attach ran successfully? – Fabio Antunes Apr 13 '15 at 10:08
  • 1
    @FabioAntunes `sync` returns an array with `detached`, `attached` and `updated` lists. `attach` doesn't return anything, but in both cases you would get an exception if something unexpected happend in the db calls. – Jarek Tkaczyk Apr 13 '15 at 12:37
  • 1
    Awesome @JarekTkaczyk! `getRelatedIds` changed to `allRelatedIds`. Please update your answer for later comers. – Manh Le Sep 28 '20 at 10:37
  • In Laravel 9.X there is now a new method for this: `->syncWithoutDetaching()` which does not require the second `false` parameter. – D1__1 Nov 14 '22 at 22:29
136

Here's my notes on how to save and update on all the Eloquent relationships.

in One to One:

You have to use HasOne on the first model and BelongsTo on the second model

to add record on the first model (HasOne) use the save function

example:    $post->comments()->save($comment);

to add record on the second model (BelongsTo) use the associate function

example:    $user->account()->associate($account);    $user->save();


in One to Many:

You have to use HasMany on the first model and BelongsTo on the second model

to add record on the first table (HasMany) use the save or saveMany functions

example:    $post->comments()->saveMany($comments);

to add record on the second model (BelongsTo) use the associate function

example:    $user->account()->associate($account);    $user->save();


in Many to Many:

You have to use BelongsToMany on the first model and BelongsToMany on the second model

to add records on the pivot table use attach or sync functions

  • both functions accepts single ID or array of ID’s 

  • the difference is attach checks if the record already exist on the pivot table while sync don’t

example: $user->roles()->attach($roleId);


in Polymorphic One to Many:

You have to use MorphMany on the main model and MorphTo on all the (***able) models

to add records on all the other models use the save

example:    $course->tags()->save($tag);

the pivot table should have the following columns:

. main model ID

. (***able) ID

. (***able) Type


in Polymorphic Many to Many:

You have to use MorphByMany on the main model and MorphToMany on all the (***able) models

to add records on all the other models use the save or saveMany

example:    $course->tags()->save($tag);

example:    $course->tags()->saveMany([$tag_1, $tag_2, $tag_3]);

the pivot table should have the following columns:

. main model ID

. (***able) ID

. (***able) Type


in Has Many Through (shortcut):

You have to use HasManyThrough on the first table and have the normal relations on the other 2 tables

this doesn’t work for ManyToMany relationships (where there’s a pivot table)

however there’s a nice and easy solution just for that.


Here's an article I wrote, inspired by this answer. Important to check it: https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209

Mahmoud Zalt
  • 30,478
  • 7
  • 87
  • 83
  • I actually placed this answer when they already had over 40 likes on the correct answer, but yes I know how useful this is for me, glad you like it :) – Mahmoud Zalt Apr 23 '16 at 07:09
  • 1
    how to update one to may relation. u explained how to add . could you please explain the update too? is there a method to update records like `updateMany` something like that? thanks – Hamidreza Nov 18 '18 at 09:16
4

syncWithoutDetaching([$id_one, $id_two, $id_three]); is what you are looking for. Actually it does the exact thing [sync with 2nd param false] does!

Moslem Deris
  • 196
  • 1
  • 7
2

Solved: Use the updateOrInsert(array $attributes, array $values = [])

     DB::table('your_pivot_table')->updateOrInsert([
                'col' => $someValue
                

            ],[

                    'otherColumn' => $otherVlaue,
                   
                ]);
            

        }
Hashmat Waziri
  • 476
  • 9
  • 9
0

The sync function obliterates the exiting relationships and makes your array the entire list of relations. You want attach instead to add relations without removing others.

ceejayoz
  • 176,543
  • 40
  • 303
  • 368
  • I can't use attach because I'm using this code inside update method. The story is that admin can update task and fill in input participants[] with users that will participate in task. So I need to check if it exists and delete (or not to add new record) it or if it doesn't exist add it. – SuperManSL Jul 11 '14 at 21:18
0

for those who are searching for adding pivot attributes (the middle table attributes), you can use syncWithPivotValues and it also has the second parameter like this

$user->tasks()->syncWithPivotValues($tasksIDs,['day_number' => $day],false);