欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

laravel many to many pivot update 更新 修改 有大用 有大大用

Laravel pivot table create association once, then update

I have the following tables:

User:

userID
...

Lesson:

lessonID
...

Users_Lessons_Status (which acts as a pivot table and holds other information):

userID references User.userID
lessonID references Lessons.lessonID
latestSectionID
percentComplete

What I want to do is, for each user, for each lesson, there should be a row in the pivot table that tells how much the user has completed in that lesson and what their latest section ID was. That is, there should be a unique pair with userID and lessonID (primary keys?).

I have set up my models like so:

<?php

class User extends Eloquent implements UserInterface, RemindableInterface {

...


    public function lessonStatuses() 
    {
        return $this->belongsToMany('Lesson', 'users_lessons_status', 'lessonID', 'userID')->withPivot('latestSectionID', 'percentComplete');
    }

}

<?

class Lesson extends Eloquent {

protected $table = 'lessons';
protected $primaryKey = 'lessonID';

public function userStatuses()
{
    return $this->belongsToMany('User', 'users_lessons_status', 'userID', 'lessonID');
}


}

?>

My current route looks like this:

Route::post('dbm/users/setLatestSectionID', function() {
    if(Auth::check()) {
        $user = User::find(Input::get('userID'));
        $lesson = Lesson::find(Input::get('lessonID'));
        $us = $user->lessonStatuses(); 
        $us->attach($lesson->lessonID, 
            ["latestSectionID" => Input::get('latestSectionID'), "percentComplete" => Input::get('percentComplete')] );
    }
});

This works, however, it creates a new row every time I update it for the same userID and lessonID, so the pair is no longer unique. Which methods should I use for this purpose? I tried both save()attach() and push() in the documentation but I'm not sure which one to use here.

Edit: to clarify, the resulting table should look something like this:

id|userID|lessonID|latestSectionID|percentComplete
1    1      1             X             Y
2    1      2        
3    1      3         
4    2      1          
5    3      1
6    3      2
....

Edit 2: Fixed the User->belongsToMany() method and added the withPivot call.

2 Answers 正确答案

It seems like a bug, nevertheless you can do this:

...->sync([$id], false); // detaching set to false, so it will only insert new rows, skip existing and won't detach anything

edit: As said in comment - it will not work for you, as you want to set pivot data. So basically there is no method to do this at the moment, but something like this should do:

// belongsToMany.php
public function attachOrUpdate($id, array $attributes = array(), $touch = true)
{
    if ($id instanceof Model) $id = $id->getKey();

    if ( ! $this->allRelatedIds()->contains($id)) // getRelatedIds() in prior to v5.4
    {
        return $this->attach($id, $attributes, $touch); 
    }
    else if ( ! empty($attributes))
    {
        return $this->updateExistingPivot($id, $attributes, $touch);
    }
}

I'm gonna test it and if it passes, send a pull request to 4.1

    You should be using updateExistingPivot().

    Update your code to use

        $us->updateExistingPivot($lesson->lessonID, 
            ["latestSectionID" => Input::get('latestSectionID'), "percentComplete" => Input::get('percentComplete')], true );

    The last parameter will update the timestamps for all related models. If not, you can remove or set to false.

    If you want to attach only when a record doesn't exist, you could do something like this...

    Route::post('dbm/users/setLatestSectionID', function() {
        if(Auth::check()) {
            $user = User::find(Input::get('userID'));
    
            $lesson = [
                "latestSectionID" => Input::get('latestSectionID'), 
                "percentComplete" => Input::get('percentComplete')
            ];
    
            $num_lessons = $user->lessonStatuses()->where('id', Input::get('lessonID'))->count();
    
            if($num_lessons == 0) {
                $user->attach($lesson->lessonID, $lesson);
            } else {
                $user->updateExistingPivot($lesson->lessonID, $lesson);
    
            }
        }
    });

      来自  https://stackoverflow.com/questions/23019239/laravel-pivot-table-create-association-once-then-update


      Proper chaining for my Pivot table to execute Pivot Update


      Martney posted 3 years ago

      How do I properly execute this function cause currently when I run this function all data will be updated to Sold. What I need is to Put Sold into defined query only.

      $update=Item::find($item_id); $update_imei=$update->workArea()->where('imei','=','3')->get(); $update_imei->updateExistingPivot($warehouse_id, array('status' => 'Sold'), false);

      CleverCookie replied 3 years ago

      Try this:

      $update=Item::find($item_id); 
      $update_imei=$update->workArea()->where('imei','=','3')->get(); 
      $update_imei->pivot->status = 'Sold';
      $update_imei->pivot->save();
      
      Martney replied 3 years ago

      Error - "Creating default object from empty value "

      CleverCookie replied 3 years ago

      What line? What part of the code is throwing this? Are you able to access the pivot tables values at all? I can't really tell what is going on just by that error. Does workArea() return a collection or an instance?

      Martney replied 3 years ago

      its the line $update_imei->pivot->status = 'Sold'; - "Creating default object from empty value "

      Yes Im able to access those pivot table values, yes workArea returns collections

      CleverCookie replied 3 years ago

      Then instead of calling ->get(); use ->first(); unless you want to update more than one items pivot table, then you need a foreach statement.

      Martney replied 3 years ago

      It works now, But the issue now is when it updates the pivot table, it will also update other imei that has the same Item_ID.

      CleverCookie replied 3 years ago

      Sorry, I'm confused. I can't tell what you are referring to. Are you speaking of the item_id that is used to find the item, or are you referring to the ->where() queries item id of '3'? Is it updating another pivot table other than the one we are accessing?

      Martney replied 3 years ago

      Another row is updated other than what we access,

      What we want to access is Imei=3

      IMEI Item_id Status 6 2 Available 3 1 Available 5 1 Available

      But what happens after the query is, it updates same item_id rather than IMEI only.

      Result that we need IMEI Item_id Status 6 2 Available 3 1 Sold 5 1 Available

      Issue IMEI Item_id Status 6 2 Available 3 1 Sold 3 1 Sold

      it converts all item_id to same IMEI 3

      CleverCookie replied 3 years ago

      Can you paste your code in laravel.io/bin. Your last post just confused me even more. I don't understand the numbers you have posted. - "Status 6 2 Available 3 1 Sold 3 1 Sold".

      Also, I just noticed that you are passing in '3' as a string and not an int. $update_imei=$update->workArea()->where('imei', '=', 3)->first();

      It should only be updating the first imei's,that equals 3, pivot table.

      Martney replied 3 years ago

      CleverCookie here you go http://laravel.io/bin/qk2RO

      CleverCookie replied 3 years ago

      Martney, all the data in one table?

      Also, Model::find() gets a record by it's primary key, and primary keys are suppose to be unique. If all of that data is in the same table then you can use this bit of code

      $update=Item::where('item_id', '=', $item_id)->where('imei', '=', 3)->first();
      $update->status = 'Sold';
      $update->save();
      

      Otherwise you will need to specify a primary key for your items table. Then you can user Model::find() to select the item from the database. I suspect this is the issue. While I still don't know why the imei is being updated since we are only calling update on the pivot table which makes me want to see how you have your relationships set up. Does workArea() do anything other than return the relation?

      Martney replied 3 years ago

      its pivot table, yes IMEI and item_id is in my pivot table, I have my item table and a pivot table work it http://laravel.io/bin/lnVR1 here's the code for my relationship

      CleverCookie replied 3 years ago
      $update = Item::find($item_id)->workArea()->wherePivot('imei', '=', 3)->first(); 
      $update->pivot->status = 'Sold';
      $udpate->pivot->save();
      

      updated

      Martney replied 3 years ago

      Hi CleverCookie same error occur as for my first issue,

      Call to undefined method Illuminate\Database\Query\Builder::updateExistingPivot()

      CleverCookie replied 3 years ago

      Sorry, I should have realized sooner that we were using the where query on the WorkArea's table and not the actual pivot table. i updated my answer to use 'wherePivot()' method I just found when searching. Review the updated code.

      Martney replied 3 years ago

      Attached here is the issue

      http://laravel.io/bin/Noqae

      I'm currently debugging it.

      CleverCookie replied 3 years ago

      I don't know why it is updating two pivot tables and updating a value that we are not specifying.

      Does $update->pivot return two items in a collection when you var_dump()?

      Martney replied 3 years ago

      No it does not update two pivot table, but it updates 2 column of 1 pivot table.

      • It only return 1 item which is right for the IMEI we specified.

      I think this is a bug from laravel.

      CleverCookie replied 3 years ago

      Yea, two pivot table columns is what I meant.

      It does sound like a bug. Hopefully it is not something simple that we are missing.

      I guess you should report it and maybe we can get some answers because I'd like to know why it's not working as well.

      Martney replied 3 years ago

      Yeah hopefully were not haha

      Ive been debugging this issue for almost 2 days now

      where should I report this issue ?

      CleverCookie replied 3 years ago

      Well, either way I learned that I can use a query restriction on pivot tables so it's been fun. Now, I just need to know how to do it correctly. lol

      Report Issues on github: https://github.com/laravel/framework/issues

      Martney replied 3 years ago

      I already reported this issue and apparently same as issue as this https://github.com/laravel/framework/issues/3215

      Martney replied 3 years ago Solution

      What I do is a raw query cause it is still currently bug

          DB::table('item_work-area')
              ->where('imei', 11)
              ->update(array('status' => 'Sold'));
      
      lozadaOmr replied 3 years ago

      I experienced something similar and filed an issue Github.

      Basically from what I understand it seems that the ->where() gets ignored when using ->updateExistingPivot()


      来自  https://laravel.io/forum/07-08-2014-proper-chaining-for-my-pivot-table-to-execute-pivot-update

      update pivot table in case of many to many relation laravel4

      I have started working with Laravel4 recently. I am facing some problem while updating pivot table data, in case of many to many relation.

      The situation is: I have two table: ProductProductType. The relation between them is Many to many. My Models are

      class Product extends Eloquent {
          protected $table = 'products';
          protected $primaryKey = 'prd_id';
      
          public function tags() {
              return $this->belongsToMany('Tag', 'prd_tags', 'prta_prd_id', 'prta_tag_id');
          }
      }
      
      class Tag extends Eloquent {
          protected $table = 'tags';
          protected $primaryKey = 'tag_id';
              public function products()
          {
          return $this->belongsToMany('Product', 'prd_tags', 'prta_prd_id', 'prta_tag_id');
          }
      }

      While inserting data to the pivot table prd_tags, I did:

      $product->tags()->attach($tag->tagID);

      But now I want to update data in this pivot table, what is the best way to update data to the pivot table. Let's say, I want to delete some tags and add new tags to a particular product.

        Old question, but on Nov 13, 2013, the updateExistingPivot method was made public for many to many relationships. This isn't in the official documentation yet.

        public void updateExistingPivot(mixed $id, array $attributes, bool $touch)

        --Updates an existing pivot record on the table.

        As of Feb 21, 2014 you must include all three arguments.

        In your case, (if you wanted to update the pivot field 'foo') you could do:

        $product->tags()->updateExistingPivot($tag->tagID, array('foo' => 'value'), false);

        Or you can change the last boolean false to true if you want to touch the parent timestamp.

        Pull request:

        https://github.com/laravel/framework/pull/2711/files

          I know that this is an old question, but if you're still interested in the solution, here it is:

          Lets say your pivot table has 'foo' and 'bar' as the additional attributes, you can do this to insert data into that table:

          $product->tags()->attach($tag->tagID, array('foo' => 'some_value', 'bar'=>'some_other_value'));

          Another method for this while working with laravel 5.0+

          $tag = $product->tags()->find($tag_id);
          $tag->pivot->foo = "some value";
          $tag->pivot->save();

            this is full example :

             $user = $this->model->find($userId);
                $user->discounts()
                    ->wherePivot('discount_id', $discountId)
                    ->wherePivot('used_for_type', null)
                    ->updateExistingPivot($discountId, [
                        'used_for_id' => $usedForId,
                        'used_for_type' => $usedForType,
                        'used_date_time' => Carbon::now()->toDateString(),
                    ], false);

            来自  https://stackoverflow.com/questions/15621279/update-pivot-table-in-case-of-many-to-many-relation-laravel4



            User::find($id)->subjects()->updateExistingPivot($subjectId, ['status' => 'enrolled']);



            Family Fortunes: Saving and Updating Laravel Relations

            Although I’ve been using Laravel heavily for over two years, I still find things there that I didn’t really know about.

            Today I took some time to really understand all the different ways of saving and associating related models. I had misunderstood the docs at first, but after some searching and playing with test code I’ve come to a better understanding.

            One-To-Many and Belongs-To Relationships

            To illustrate I’ll use an example Parent and a Child class. The docs used PostCommentUser and Roles which threw me off, so I hope that my naming convention is clearer.

            class Parent extends Model
            {
              public function children()
              {
                return $this->hasMany(App\Child::class);
              }
            }
            
            class Child extends Model
            {
              public function parent()
              {
                return $this->belongsTo(App\Parent::class);
              }
            }
            

            I always use plurals for hasMany() relationships as a reminder to myself how it goes.

            The first example is saving a Child object to an existing Parent:

            // Find an existing Parent record
            $parent = App\Parent::find(2);
            
            // Creates a new Child instance 
            $child = new App\Child(['name' => 'mei']);
            
            // effectively sets $child->parent_id to 2 then saves the instance to the DB
            $parent->children()->save($child);
            

            If called repeatedly, the above code will continue to save Child records with the same Parent.

            So far so good. We now want to do the reverse of the above.

            Let’s say we want a new parent for our child (programmatic adoption?):

            // a new Parent object
            $parent = App\Parent::find(31);
            
            // updates $child->parent_id to 31
            $child->parent()->associate($parent);
            $child->save();
            

            Our Child had parent_id of 2. Above, associate set the parent_id property of $child to 31, then $child->save() persists the change to the database.

            Of course, it’s possible to for an object to have more than one parent relation (for example, a Comment can belong to a Post and an Author). As the association is only confirmed when save() is called, we can simply call associate with another parent object.

            $parent = App\Parent::find(31);
            // a new parent! 
            $anotherParent = new App\AnotherParent(['name' => 'steve']);
            
            // associate with our parent, as above
            $child->parent()->associate($parent);
            
            // set the second relationship 
            $child->anotherParent()->associate($anotherParent);
            $child->save();
            

            Assuming that foreign keys are set, calling $child->save() before setting the second relationship would throw an error.

            Now that the relationship is set, it’s also possible to sever it.

            // sever the parent-child relationship
            $child->parent()->dissociate();
            $child->save()
            

            In this example $child->parent_id is set to null. The save() method must be called to persist the change to the database.

            Again, if foreign keys have been set an error would be thrown here. This code would only work if the foreign key (parent_id) is nullable.

            Many-To-Many Relationships

            Let’s move on from Parent and Child.

            To illustrate the Many-To_-Many relationship we’ll use Post and Tag. These two are connected with the belongsToMany() method.

            class Post extends Model
            {
              public function tags()
              {
                return $this->belongsToMany(AppTag::class);
              }
            }
            
            class Tag extends Model
            {
              public function posts()
              {
                return $this->belongsToMany(AppPost::class);
              }
            }
            

            Post can have zero or more tags, and a Tag can have zero or more posts (in theory anyway, an unused tag is mostly pointless).

            Such a relationship allows us to get all tags attached to a Post, and conversely get all posts attached to a Tag.

            Unlike one to many relations, both records must be persisted before the following to work. The pivot table must have both ids.

            In this example, a tags are added to a post.

            $post = App\Post::find(3);
            
            $tag = App\Tag::find(47);
            
            $post->tags()->attach($tag->id);
            

            The first argument of attach() is an id, but an Eloquent model can also be passed:

            $post->tags()->attach($tag);
            

            What happens above is that the pivot table gets a new record, with post_id set to 3 and tag_id to 47.

            If your pivot table has extra fields, these can be set by passing an array as the second argument. The parent (in this case Post) model’s timestamp can be touched by passing in true as a third argument:

            $post->tags()->attach($tag, ['expires' => date('Y-m-d H:i:s')], true);
            

            To sever the relationship, detach() is used.

            A single id or an array can be passed to detach(), or called without any arguments which detaches all of the parent’s relations.

            // detaches $tag from $post
            $post->tags()->detach($tag->id);
            
            // detaches tags with the following ids from $post
            $post->tags()->detach([41, 52, 54]);
            
            $post->tags()->detach();
            

            The first and second methods deletes rows corresponding to the $post with the given tag ids. The last method deletes all rows from the pivot table with that $post’s id.

            Finally, the sync() method is provided to make life just a bit easier for us.

            Let’s assume we’ve edited the post, and have remove one tag and attached another.

            $post = App\Post::find(4);
            
            // $post has tags with ids: 32, 45, 47
            
            // tag 32 is no longer required, and we need to add tag with id 86
            
            // so instead of this:
            $post->tags()->detach(32);
            $post->tags()->attach(86);
            
            // we do this
            $post->tags()->sync([45, 47, 86]);
            

            Here the new tags [45, 47, 86] will be persisted, and tag 32 will be deleted from the pivot table. This also removes any logic for finding out which tags already exist for the post which are not meant to be removed.

            Conclusion

            Having spent time playing with this I have a much better appreciation of how it works.

            Again, Laravel and Eloquent provide an elegant interface to the database, and lets us write readable code.

            I’ve picked up a few points here myself, and I’ll be taking a look back at a few of my own projects to see where I can improve them.

            来自  http://meigwilym.com/family-fortunes-saving-and-updating-laravel-relations/


            Many to many relationship with extra field


            I am providing an example

            there are two tables paper and question in paper table {id, name} in question table {id, name, score} in pivot paper_question table {id, paper_id, question_id, question_score}

            how to define this belongsToMany relationship in model with "withPivot" or something else. how to use sync method to parse question id and score together?

            othmanus replied 3 years ago Solution

            I thinks its already clear in the documentation, but here are the headlines

            	
            // Paper.php model
            public function questions()
            {
            	return $this->belongsToMany('App\Question')->withPivot('question_score');
            }
            
            // Question.php model
            public function papers()
            {
            	return $this->belongsToMany('App\Paper')->withPivot('question_score');
            }
            
            // anywhere in controllers
            
            // update paper's questions
            $paper->questions()->sync([
            	$question_1_id => ['question_score' => 'your_value'],
            	$question_2_id => ['question_score' => 'your_value'],
            ]); // assuming that: $paper is valid instance, $question_1_id & $question_2_id are valid ids
            
            // update question's papers
            $question->papers()->sync([
            	$paper_1_id => ['question_score' => 'your_value'],
            	$paper_2_id => ['question_score' => 'your_value'],
            ]);
            

            Hope it helps.

            alvi1987 replied 3 years ago

            just assume I have an array where array keys are question_id and array values are question_scores how can I parse it in sync method for updating the pivot table.

            Firtzberg replied 3 years ago
            // question with id 1 has a question_score 100.
            $data = array(1 => 100);
            $syncData = array();
            foreach($data as $id => $score){
            $syncData[$id] = array('question_score' => $score);
            }
            $paper->questions()->sync($syncData);
            

            Sign in to participate in this thread!

            来自  https://laravel.io/forum/04-12-2015-many-to-many-relationship-with-extra-field

            Welcome to the incredibly popular Easy Laravel 5 companion blog. To celebrate the new edition's release (updated for Laravel 5.5!) use the discount code easteregg to receive 20% off the book or book/video package! » Buy the book

            Did you know this material has been completely updated for Laravel 5.5 in the latest edition of Easy Laravel 5? Check out the new companion project.

            You'll use the many-to-many relation when the need arises to relate a record in one table to one or several records in another table, and vice versa. Consider some future version of TODOParrot that allowed users to classify lists using one or more categories, such as "leisure", "exercise", "work", "vacation", and "cooking". A list titled "San Juan Vacation" might be associated with several categories such as "leisure" and "vacation", and the "leisure" category would likely be associated with more than one list, meaning a list can be associated with many categories, and a category can be associated with many lists. In this detailed post you'll learn all about the many-to-many relation.

            See the below diagram for an illustrative example of this relation.

            In this post (adapted from my bestselling book, Easy Laravel 5) you'll learn how to create the intermediary table used to manage the relation (known as a pivot table), define the relation within the respective models, and manage the relation data.

            Creating the Pivot Table

            Many-to-many relations require an intermediary table to manage the relation. The simplest implementation of the intermediary table, known as a pivot table, would consist of just two columns for storing the foreign keys pointing to each related pair of records. The pivot table name should be formed by concatenating the two related model names together with an underscore separating the names. Further, the names should appear in alphabetical order. Therefore if we were creating a many-to-many relationship between the Todolist and Category models, the pivot table name would be category_todolist. Of course, the Category model and corresponding categories table also needs to exist, so let's begin by generating the model:

            $ php artisan make:model Category --migration

            You'll find the newly generated model inside app/Category.php:

            use Illuminate\Database\Eloquent\Model;
            
            class Category extends Model {
            
            }

            Next, modify the newly created migration file's up() method to look like this:

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

            Finally, run Artisan's migrate command to create the table:

            $ php artisan migrate

            With the Category model and corresponding categories table created, let's next create the category_todolist table:

            $ php artisan make:migration create_category_todolist_table \
              --create=category_todolist
            Created Migration: 2016_04_05_011409_create_category_todolist_table

            Next, open up the newly created migration (database/migrations/) and modify the up method to look like this:

            public function up()
            {
              Schema::create('category_todolist', function(Blueprint $table)
              {
                  $table->integer('category_id')->unsigned()->nullable();
                  $table->foreign('category_id')->references('id')
                        ->on('categories')->onDelete('cascade');
            
                  $table->integer('todolist_id')->unsigned()->nullable();
                  $table->foreign('todolist_id')->references('id')
                        ->on('todolists')->onDelete('cascade');
            
                  $table->timestamps();
              });
            }

            This syntax is no different than that used for the earlier tasks table migration. The only real difference is that we're referencing two foreign keys rather than one.

            After saving the changes run Artisan's migrate command to create the table:

            $ php artisan migrate

            Defining the Many-to-Many Relation

            With the tables in place it's time to define the many-to-many relation within the respective models. Open the Todolist model and add the following method to the class:

            public function categories()
            {
                return $this->belongsToMany('App\Category')
                  ->withTimestamps();
            }

            Notice I've chained the withTimestamps method to the return statement. This instructs Laravel to additionally update the category_todolist timestamps when saving a new record. If you choose to omit the created_at and updated_at timestamps from this pivot table (done by removing the call to $table->timestamps from the migration), you can omit the withTimestamps method).

            Save the changes and then open the Category model, adding the following method to the class:

            public function todolists()
            {
                return $this->belongsToMany('App\Todolist')
                  ->withTimestamps();
            }

            After saving these changes you're ready to begin using the relation!

            Associating Records Using the Many-to-Many Relation

            You can associate records using the many-to-many relation in the same way as was demonstrated for one-to-many relations; just traverse the relation and use the save method, as demonstrated here:

            $tl = Todolist::find(1);
            
            $category = new Category(['name' => 'Vacation']);
            
            $tl->categories()->save($category);

            In order for this particular example to work you'll need to make sure name has been added to the Categorymodel's fillable property.

            After executing this code you'll see the new category has been created and the association between this newly created category and the list has been made:

            mysql> select * from categories;
            +----+----------+---------------------+---------------------+
            | id | name     | created_at          | updated_at          |
            +----+----------+---------------------+---------------------+
            |  1 | Vacation | 2016-04-04 20:44:11 | 2016-04-04 20:44:11 |
            +----+----------+---------------------+---------------------+
            
            mysql> select * from category_todolist;
            +-------------+-------------+------------+------------+
            | category_id | todolist_id | created_at | updated_at |
            +-------------+-------------+------------+------------+
            |           1 |           1 | ...        | ...        |
            +-------------+-------------+------------+------------+

            The above example involves the creation of a new category. You can easily associate an existing category with a list using similar syntax:

            $list = Todolist::find(2);
            
            $category = Category::find(1);
            
            $list->categories()->save($category);

            You can alternatively use the attach and detach methods to associate and disassociate related records. For instance to both associate and immediately persist a new relationship between a list and category, you can either pass in the Category object or its primary key into attach. Both variations are demonstrated here:

            $list = Todolist::find(2);
            
            $category = Category::find(1)
            
            // In this example we're passing in a Category object
            $list->categories()->attach($category);
            
            // The number 5 is the primary key of another category
            $list->categories()->attach(5);

            You can also pass an array of IDs into attach:

            $list->categories()->attach([3,4]);

            To disassociate a category from a list, you can use detach, passing along either the Category object, an object's primary key, or an array of primary keys:

            // Pass the Category object into the detach method
            $list->categories()->detach(Category::find(3));
            
            // Pass a category's ID
            $list->categories()->detach(3);
            
            // Pass along an array of category IDs
            $list->categories()->detach([3,4]);

            Determining if a Relation Already Exists

            Laravel will not prevent you from duplicating an association, meaning the following code will result in a list being associated with the same category twice:

            $list = Todolist::find(2);
            
            $category = Category::find(1)
            
            $list->categories()->save($category);
            $list->categories()->save($category);

            If you have a look at the database you'll see that the Todolist record associated with the primary key 2 has been twice related to the Category record associated with the primary key 1, which is surely not the desired behavior:

            mysql> select * from category_todolist;
            +-------------+-------------+------------+------------+
            | category_id | todolist_id | created_at | updated_at |
            +-------------+-------------+------------+------------+
            |           1 |           2 | ...        | ...        |
            |           1 |           2 | ...        | ...        | 
            +-------------+-------------+------------+------------+

            You can avoid this by first determining whether the relation already exists using the contains method:

            $list = Todolist::find(2);
            
            $category = Category::find(1)
            
            if ($list->categories->contains($category))
            {
            
              return Redirect::route('lists.show', [$list->id])
                ->with('message', 'Category could not be assigned. Duplicate entry!');
            
            } else {
            
              $list->categories()->save($category);
            
              return Redirect::route('lists.show', [$list->id])
                ->with('message', 'The category has been assigned!');
            
            }

            Saving Multiple Relations Simultaneously

            You can use the saveMany method to save multiple relations at the same time:

            $list = Todolist::find(1);
            
            $categories = [
              new Category(['name' => 'Vacation']),
              new Category(['name' => 'Tropical']),
              new Category(['name' => 'Leisure']),
            ];
            
            $list->categories()->saveMany($categories);

            Traversing the Many-to-Many Relation

            You'll traverse a many-to-many relation in the same fashion as described for the one-to-many relation; just iterate over the collection:

            $list = Todolist::find(2);
            
            ...
            
            @if ($list->categories->count() > 0)
            
              <ul>
            
              @foreach($list->categories as $category)
            
                <li>{{ $category->name }}</li>
            
              @endforeach
            
              </ul>
            
            @endif

            Because the relation is defined on each side, you're not limited to traversing a list's categories! You can also traverse a category's lists:

            $category = Category::find(2);
            
            ...
            
            @if ($category->todolists()->count() > 0)
            
              <ul>
            
              @foreach($category->todolists as $list)
            
                <li>{{ $list->name }}</li>
            
              @endforeach
            
              </ul>
            
            @endif

            Synchronizing Many-to-Many Relations

            Suppose you provide users with a multiple selection box that allows users to easily associate a list with one or more categories. Because the user can both select and deselect categories, you must take care to ensure that not only are the selected categories associated with the list, but also that any deselected categories are disassociated with the list. This task is a tad more daunting than it may at first seem. Fortunately, Laravel offers a method named sync which you can use to synchronize an array of primary keys with those already found in the database. For instance, suppose categories associated with the IDs 71252, and 77 were passed into the action where you'd like to synchronize the list and categories. You can pass the IDs into syncas an array like this:

            $categories = [7, 12, 52, 77];
            
            $tl = Todolist::find(2);
            
            $tl->categories()->sync($categories);

            Once executed, the Todolist record identified by the primary key 2 will be associated only with the categories identified by the primary keys 71252, and 77, even if prior to execution the Todolist record was additionally associated with other categories.

            Managing Additional Many-to-Many Attributes

            Thus far the many-to-many examples presented in this chapter have been concerned with a join table consisting of two foreign keys and optionally the created_at and updated_at timestamps. But what if you wanted to manage additional attributes within this table, such as some additional description pertaining to the list/category relation?

            Believe it or not adding other attributes is as simple as including them in the table schema. For instance let's create a migration that adds a column named description to the category_todolist table created earlier in this section:

            $ php artisan make:migration add_description_to_category_todolist_table
            Created Migration: 2016_04_05_012822_add_description_to_category_todolist_table

            Next, open up the newly generated migration file and modify the up() and down() methods to look like this:

            public function up()
            {
              Schema::table('category_todolist', function($table)
              {
                  $table->string('description');
              });
            }
            
            public function down()
            {
              Schema::table('category_todolist', function($table)
              {
                  $table->dropColumn('description');
              });
            }

            Save the changes and After generating the migration be sure to migrate the change into the database:

            $ php artisan migrate
            Created Migration: 2016_04_05_012822_add_descript...

            Finally, you'll need to modify the Todolist categories relation to identify the additional pivot column using the withPivot() method:

              public function categories()
              {
                  return $this->belongsToMany('App\Category')
                    ->withPivot('description')
                    ->withTimestamps();
              }

            With the additional column and relationship tweak in place all you'll need to do is adjust the syntax used to relate categories with the list. You'll pass along the category's ID along with the description key and desired value, as demonstrated here:

            $list = Todolist::find(2);
            $list->categories()->attach(
              [3 => ['description' => 'Because San Juan is a tropical island']]
            );

            If you later wished to update an attribute associated with an existing record, you can use the updateExistingPivot method, passing along the category's foreign key along with an array containing the attribute you'd like to update along with its new value:

            $list->categories()->updateExistingPivot(3, 
              ['description' => 'Sun, beaches and rum!']
            );

            Conclusion

            I congratulate you for making it to the end of this epic post! Chapter 4 of my bestselling book Easy Laravel 5goes into great detail about all of Laravel's supported relations (one-to-one, belongs to, etc.). If you'd like to purchase a copy, use the discount code easteregg when checking out for a 20% discount!


            来自  https://www.easylaravelbook.com/blog/introducing-laravel-many-to-many-relations/


            [Proposal] Using Pivot table values in Belongs To Many Where queries.

            Summary:
            There is currently no way, that I can tell, that you can use Pivot table values to help build a query for a Belongs To Many relationship. This means that you need to return all of the related records, and then loop through them to find the ones you are interested in, based on the pivot table values. When working with a large database, it's going to be quite slow.

            Example of Problem:
            Assume you have two database tables: users and companies, and a pivot table joining them called company_user.

            The company_user table might look something like this:

            +----+------------+---------+-------+
            | id | company_id | user_id | owner |
            +----+------------+---------+-------+
            |  1 |          1 |       1 |     1 |
            |  2 |          1 |       2 |     0 |
            |  3 |          1 |       3 |     0 |
            +----+------------+---------+-------+
            

            In order to retrieve the Company Owner, you currently need to do something like this:

            $owners = array();
            foreach (Company::find(1)->users as $user) {
                if ($user->pivot->owner) {
                    $owners[] = $user;
                }
            }
            return $owners;

            Which, although seems simple, is quite slow when iterating a large database since you don't care about most of the records it is going to return.

            Proposed Solution:
            Implement a Where() function to add a condition based on a pivot table value. That way the database only returns the useful records, making it a lot faster.

            Something like this would work:

            return Company::find(1)->users()->wherePivot('owner', 1)->get();

            It could be implemented by updating Illuminate/Database/Eloquent/Relations/BelongsToMany.php to have functions like these:

            public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
            {
                $column = $this->table.'.'.$column;
                return $this->where($column, $operator, $value, $boolean);
            }
            
            public function orWherePivot($column, $operator = null, $value = null)
            {
                $column = $this->table.'.'.$column;
                return $this->orWhere($column, $operator, $value);
            }

            The core concept is that the pivot table name, retrieved from $this->table, is appended to the column name. After that, it flows back into the default functionality nicely.

            @taylorotwell
            Member

            taylorotwell commented on 2 Nov 2013

            Can't you already just do:

            Company::find(1)->users()->where('company_user.owner', 1)->get();
            
            @valorin
            Contributor

            valorin commented on 2 Nov 2013

            Yes, but that method isn't documented anywhere. So you've gotta either know it works, or know how the where() functions operate to have an idea that it will work... or I'm just really slow :-)

            I thought about doing it that way, but given that Eloquent automatically links class names to tables, including pivot tables, it seemed inconsistent to have to manually specify the pivot table name in this one instance. It makes much more sense, from my point of view, to have Eloquent manage the table name behind the scenes, like everything else.

            Another factor is that you might not want to hard code your pivot table name in multiple places, in case it needs to be changed in the future.

            Anyway, if you don't agree that it's needed then I'll see if I can update the documentation with a line explaining the workaround instead.

            @anlutro
            Contributor

            anlutro commented on 2 Nov 2013

            There is the argument that if you update the pivot table name, you have to update the where statement as well. A wherePivot function might be a nice abstraction even though the functionality is already in place.

            @taylorotwell
            Member

            taylorotwell commented on 26 Nov 2013

            Done.

            @garygreen
            Contributor

            garygreen commented on 21 Feb 2014

            This is very useful though when using this in combination with sync it would seem logical to use this wherePivot to pick out/filter what rows should be synced exactly. At the moment when syncing the where seems to be completely ignored.

            @budhajeewa

            I agree with @garygreen .

            I have a Team model that has a relationship called admins, like this:

            public function admins() {
                return $this->belongsToMany( 'User' )->wherePivot( 'userrole_id', '=', 1 );
            }

            This is the team_user table I have:

            idteam_iduser_iduserrole_id
            ............
            16941
            17942
            18951
            19952
            ............

            When I fetch admins of a team like $team->admins, the User with id 4 is returned fine. But when I do $team->admins()->detach(4), not only the row with id 16 is removed, but also the 17th row. In other words, all records with user_id 4 are removed, regardless of the wherePivot() method.

            I would like to get a table like following:

            idteam_iduser_iduserrole_id
            ............
            17942
            18951
            19952
            ............

            But I get this:

            idteam_iduser_iduserrole_id
            ............
            18951
            19952
            ............

            It's great if this gets fixed. smile

            I am currently using this workaround:

            public function detachAdminById( $adminId ) {
                return DB::table( 'team_user' )
                        ->where( 'user_id', '=', $adminId, 'and' )
                        ->where( 'userrole_id', '=', 1 )
                        ->delete();
                /* TODO:
                  Replace above code with the below, when Laravel supports it.
                  Below code currently detaches all records in 'team_user' table, where
                  'user_id' = $adminId.
                      return $this->admins()->detach($adminId);
                 */
            }
            @manthan787

            I'm also facing the same issue as @budhajeewa
            It would be nice to be able to access the ID of the pivot table, so they can be deleted selectively.

            @Shemahmforash

            I'm also in the same situation as both @budhajeewa and @manthan787. Currently I am using using a workaround similar to the above one, but I think it would be nice if laravel allowed us to access the pivot table primary key, or, even better, be able to detach a many to many relationship according to a certain condition in the extra items of the pivot table.

            @InfiniteDevelopment

            This is exactly the issue I am having..... I have a table for users one for relationships and I am pulling back all of the user objects where the relationship exists but no where can I parse for only the accepted connections which live in the relationships table.... I think wherePivot would be awesome.

            @InfiniteDevelopment

            I added:

            public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
            {
            $column = $this->table.'.'.$column;
            return $this->where($column, $operator, $value, $boolean);
            }

            public function orWherePivot($column, $operator = null, $value = null)
            {
            $column = $this->table.'.'.$column;
            return $this->orWhere($column, $operator, $value);
            }

            To the laravel install and did:

            //count the connections
            $recievedConnectionsCount = $user->aUsersRecievedConnections()->wherePivot('state', '=', 'accepted')->count();
            $sentConnectionsCount = $user->aUsersSentConnections()->wherePivot('state', '=', 'accepted')->count();

            and it gave me the intended result. Maybe someone should do a pull request and get this integrated into the base of laravel so I dont lose it when updating the framework... :)

            @bravist
            return Company::find(1)->users()->wherePivot('owner', 1)->get();

            perferect

            @athamid

            how can I Use andWherePivot? I need check two where on pivot table. @taylorotwell

            @ccastillejo-accent

            ccastillejo-accent commented on 29 Mar  

            I'm also in the same situation and follow @budhajeewa response I make this function:

            use Illuminate\Support\Facades\DB;
            use Illuminate\Database\Capsule\Manager as DBManager;
            
            
            /**
                 * @param string $relation
                 * @param integer $relatedId
                 * @param array $updateData
                 * @param callable|null $builderExtend
                 * @return bool|int
                 */
                public function updatePivot($relation, $relatedId, $updateData, $builderExtend = null)
                {
                    if (!method_exists($this, $relation)) {
                        return false;
                    }
                    $relation = $this->$relation();
                    try {
                        $dbBuilder = DB::table($relation->getTable());
                    } catch (\RuntimeException $e) {
                        $dbBuilder = DBManager::table($relation->getTable());
                    }
                    $dbBuilder->where($relation->getForeignPivotKeyName(), $this->id)
                        ->where($relation->getRelatedPivotKeyName(), $relatedId);
                    if (is_callable($builderExtend)) {
                        $builderExtend($dbBuilder);
                    }
                    return $dbBuilder->update($updateData);
                }

            Put this in some class that extends Model or in trait file

            来自 https://github.com/laravel/framework/issues/2619

            Chapter 7 offers a comprehensive introduction to all Laravel relationship types.

             Buy the book











            How to update Many To Many Relationships pivot table

            PUBLISHED 2 YEARS AGO BY IMRANKHAN

            Hi, I am working in laravel 5, created three tables Models, Features and ModelFeatures table. now on update when multiple features try to update if exist else detach the record from pivot table. I have these models..

            class Model extends Model { public function hasFeature($feature_id) { foreach($this->features as $feature) { if($feature->pivot->feature_id == $feature_id) return true; }

                return false;
            }
            
            public function assignFeature($feature, $options=[])
            {
                return $this->features()->attach($feature, $options);
            }
            
            public function removeFeature($feature)
            {
                return $this->features()->detach($feature);
            }
            

            }

            class Feature extends Model { public function models() { return $this->belongsToMany('App\Models\MobFeature', 'mob_model_features', 'feature_id', 'model_id'); } }

            Now i am trying to update like this, but not working:

            public function updateFeatures($request, $id)
            {
                $is_success = true;
                $message    = '';
            
                // get specific model
                $model = Model::find($id);
                
                // check for mobile feature
                if ($request->features) {
                    foreach ($request->features as $key => $value) {
                        if ( $model->hasFeature($key) ) {
                            // what to do next...
                        }
                        else {
                            $model->assignFeature($key, ['feature_val' => $value]);
                        }
                    }
                }
            

            Update: Thanks, i have update my code now its working. if ($request->features) { $syncFeatures = array(); foreach ($request->features as $key => $value) { if (empty($value)) { continue; }

                        if ( !$model->hasFeature($key) ) {
                            $model->assignFeature($key, ['feature_val' => $value]);
                        }
                        else {
                            $syncFeatures[$key] = array('feature_val' => $value);
                        }
                    }
                    if (!empty($syncFeatures)) {
                        $model->syncFeature($syncFeatures);
                    }
                }
            

            and this in model class: public function syncFeature($syncFeatures) { return $this->features()->sync($syncFeatures); } is this the right way ?

            Best Answer(As Selected By ImranKhan)
            pmall

            You need to use $model->features()->sync([$feature_id => ['pivot_data' => $pivot_value]]);

            pmall
             pmall
            2 years ago(546,345 XP)

            You need to use $model->features()->sync([$feature_id => ['pivot_data' => $pivot_value]]);

            amirhs712

            Tested in laravel 5.6 on a many to many relationship.

            $model->pivot->data = $pivot_data; $model->pivot->update();

            来自 https://laracasts.com/discuss/channels/eloquent/how-to-update-many-to-many-relationships-pivot-table




            普通分类: