Back to Blog

Eloquent BelongsToMany Relationships in Laravel

Julian Beaujardin
Julian Beaujardin August 5th, 2024

In Laravel, Eloquent ORM provides a beautiful, simple ActiveRecord implementation for working with your database. One of its powerful features is managing relationships between models. This article will focus on the BelongsToMany relationship, which is used to define many-to-many relationships.

What is a BelongsToMany Relationship?

A BelongsToMany relationship is a many-to-many relationship. For example, a user can have many roles, and a role can be assigned to many users. This relationship is typically implemented with a pivot table.

Example Scenario

Consider a blog application where posts can have many tags, and tags can be assigned to many posts. This is a classic many-to-many relationship.

Setting Up the Database

First, let's set up our database tables. We'll need three tables: posts, tags, and post_tag (the pivot table).

Migration for Posts Table

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

Migration for Tags Table

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTagsTable extends Migration
{
    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('tags');
    }
}

Migration for Post_Tag Pivot Table

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostTagTable extends Migration
{
    public function up()
    {
        Schema::create('post_tag', function (Blueprint $table) {
            $table->id();
            $table->foreignId('post_id')->constrained()->onDelete('cascade');
            $table->foreignId('tag_id')->constrained()->onDelete('cascade');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('post_tag');
    }
}

Defining the Relationship in Models

Next, we'll define the BelongsToMany relationship in our Post and Tag models.

Post Model

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

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

Tag Model

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    use HasFactory;

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

Working with the BelongsToMany Relationship

Now that our relationship is defined, we can start working with it.

Attaching Tags to a Post

To attach a tag to a post, you can use the attach method.

$post = Post::find(1);
$tag = Tag::find(1);

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

Detaching Tags from a Post

To detach a tag from a post, you can use the detach method.

$post->tags()->detach($tag->id);

Syncing Tags with a Post

The sync method can be used to synchronize tags for a post. This will attach new tags and detach any tags that are not included in the given array.

$post->tags()->sync([1, 2, 3]);

Retrieving Tags for a Post

To retrieve tags for a post, you can use the tags relationship.

$tags = Post::find(1)->tags;

foreach ($tags as $tag) {
    echo $tag->name;
}

Retrieving Posts for a Tag

Similarly, to retrieve posts for a tag, you can use the posts relationship.

$posts = Tag::find(1)->posts;

foreach ($posts as $post) {
    echo $post->title;
}

Using Pivot Table Columns

Sometimes, you might need to store additional information on the pivot table. For instance, you might want to track when a tag was attached to a post.

Adding Columns to Pivot Table

First, add the new column to your pivot table migration:

Schema::create('post_tag', function (Blueprint $table) {
    $table->id();
    $table->foreignId('post_id')->constrained()->onDelete('cascade');
    $table->foreignId('tag_id')->constrained()->onDelete('cascade');
    $table->timestamps();
    $table->timestamp('tagged_at')->nullable(); // New column
});

Accessing Pivot Table Columns

To access the pivot table columns, use the withPivot method on the relationship definition:

class Post extends Model
{
    use HasFactory;

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

You can then access the pivot table columns like so:

$post = Post::find(1);
foreach ($post->tags as $tag) {
    echo $tag->pivot->tagged_at;
}

Updating Pivot Table Columns

To update a pivot table column, use the updateExistingPivot method:

$post->tags()->updateExistingPivot($tag->id, ['tagged_at' => now()]);

Conclusion

Eloquent's BelongsToMany relationship provides a simple yet powerful way to manage many-to-many relationships in your Laravel applications. By understanding and utilizing these relationships, you can create more robust and maintainable code. This article covered the basics, but there's much more you can do with Eloquent relationships. Explore the official documentation for more advanced usage and techniques.