神刀安全网

Introducing Laravel Many-To-Many Relations

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.

Introducing Laravel Many-To-Many Relations

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 Category model’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></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></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 7 , 12 , 52 , and 77 were passed into the action where you’d like to synchronize the list and categories. You can pass the IDs into sync as 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 7 , 12 , 52 , 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 bookEasy Laravel 5 goes into great detail about all of Laravel’s supported relations (one-to-one, belongs to, etc.). If you’d like topurchase a copy, use the discount code easteregg when checking out for a 20% discount!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Introducing Laravel Many-To-Many Relations

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮