Laravel Many to Many Relationships with CRUD Example

by Solomon Eseme

.

Updated Mon Apr 24 2023

Laravel Many to Many Relationships with CRUD Example

Laravel Many to many relationships is a bit more involved than One to One and One to Many relationships.

Let’s first explain a possible use-case for this relationship — Imagine we were building an e-commerce website that allows multiple vendors to sell a certain set of products through our platform.

In this system, it would be possible to have multiple vendors selling the same product. If we wanted to relate vendors to products, it would be a bit tricky.

We can’t use one to one or one to many relationships because each product is related to several vendors, and each vendor is related to several products.

In cases like this, where we have multiple entities on both sides of the relationship, we use the many to many relationships.

By the way, if you’re STARTING with Laravel, I have created a Complete Laravel Guide just for you.

Going back to our introductory illustration, if we wanted to describe the relationship between products and vendors in plain English, we’d say,

“Each product has many vendors and each vendor has many products”.

To define this relationship, we need to create a third table (in addition to the two tables on both sides of the relationship) in our database.

This third table is called the pivot table. The pivot table would then store all the relationships that exist between both tables.

If we wanted to relate the products table to the vendor’s table in many to many relationships, we’d create a table called product_vendor.

In Laravel, it’s always the combination of the two table names in alphabetical order.

Laravel Many to Many Relationships

Before we dive into the article, subscribe to get access to our free Laravel tips that will improve your productivity.

Defining the Relationship

After setting up our database structure, we’d need to define the relationship in our models then.

We do this using the belongsToMany method like defined in the snippet below:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * The vendors that belong to this product
     */
    public function vendors()
    {
        return $this->belongsToMany(Vendor::class);
    }
}

The belongsToMany method accepts four parameters;

The first is the second eloquent relationship.

The second is the pivot table name (If we wanted to specify a name other than the default convention, we do it there).

The third is the foreign key on the model we’re currently defining this relationship, while the fourth is the foreign key.

In full, this definition would look like this:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * The vendors that belong to this product
     */
    public function vendors()
    {
        return $this->belongsToMany(Vendor::class, 'relationship_table', 'product_id', 'vendor_id');
    }
}

It’s important to note that the third and fourth parameters are the names on the pivot table.

Both keys are actually foreign keys since the relationship would be linked and stored on the pivot table.

Once the relationship is defined, it can be retrieved like usual; since it’s a many to many relationships, it would return a Laravel Collection. 

<?php

  namespace App\Http\Controllers;

  use App\Models\Product;

  class RandomController extends Controller {
    /**
    * Gets vendors
    */
    public function getVendors() {
      $product = Product::find(1);
      $vendors = $product->vendors; // Returns a Laravel Collection
      foreach($vendors as $vendor) {
        // Do what you want with $vendor
      }
    }
  }

Defining the Inverse of Many to Many Relationships

To define the inverse of the other half of many to many relationships, we’ll also use the belongsToMany relationship on the vendor model.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Vendor extends Model
{
    /**
     * The products that belong to this vendor
     */
    public function products()
    {
        return $this->belongsToMany(Product::class);
    }
}

Defining the “inverse” of many to many relationships are done in the same way.

You can also set the same extra parameters to the belongsToMany method as needed, and you can retrieve the relationship in the same way too. 

Take a break and subscribe to get access to our free Laravel tips that will improve your productivity.

Working with the Pivot Table

When we define many to many relationships, Laravel automatically creates a property on the objects by retrieving a pivoting relationship.

This pivot property lets us access the pivot table.

<?php

  namespace App\Http\Controllers;

  use App\Models\Product;

  class RandomController extends Controller {
    /**
    * Gets vendors
    */
    public function getVendors() {
      $product = Product::find(1);
      $vendors = $product->vendors; // Returns a Laravel Collection
      foreach($vendors as $vendor) {
        // Do what you want with $vendor
        $vendor->pivot->updated_at;
      }
    }
  }

This property lets us access other meta-data stored on the pivot table describing that relationship.

By default, the data we can access from the pivot property are the two foreign keys of both models’ relationship (e.g. product_id and vendor_id).

If we have other meta-data on our third table, we’d like to access the pivot property; we’ll need to define them when creating the relationship.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * The vendors that belong to this product
     */
    public function vendors()
    {
        // So Each vendor can define the quantity of the product in his store
        return $this->belongsToMany(Vendor::class)->withPivot('quantity', 'price');
    }
}

If you require the two special columns in our table, created_at and updated_at which will be managed by Eloquent to update whenever a record is created or updated in the pivot table.

We define them with the withTimestamps method.  

Note: That when using this method, the table managing the records must have the created_at and updated_at columns defined on it.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * The vendors that belong to this product
     */
    public function vendors()
    {
        // So Each vendor can define the quantity of the product in his store
        return $this->belongsToMany(Vendor::class)->withTimestamps()
    }
}

Using dynamic methods, we can also constrain our queries with the pivot table.

For instance, we may want to return only vendors where the product quantity is greater than zero on the pivot table.

We could do something like:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * The vendors that belong to this product
     */
    public function vendors()
    {
        // So Each vendor can define the quantity of the product in his store
        return $this->belongsToMany(Vendor::class)->withTimestamps()
    }
    
    public function vendorsWithProducts() {
      return $this->vendors()->wherePivot('quantity', '>', 0);
    }
}

Finally, we may want to create an eloquent model for our pivot table. To do that, we first need to specify the model in our belongsToMany declaration:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * The vendors that belong to this product
     */
    public function vendors()
    {
        // So Each vendor can define the quantity of the product in his store
        return $this->belongsToMany(Vendor::class)->using(ProductVendor::class);
    }
}

After doing that, we can then define the pivot model itself:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\Pivot;

class ProductVendor extends Pivot
{
    //
}

If the table for your pivot model uses incrementing integers as a primary key, then you need to add the following attribute to your model definition, like so:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\Pivot;

class ProductVendor extends Pivot
{
    /**
      * Indicates if the IDs are auto-incrementing.
      *
      * @var bool
      */
    public $incrementing = true;
}

What’s Next?

You can get an in-depth article on “Laravel One to One Polymorphic relationships with example” that explain everything from creating the relationship, the inverse method of creating it, and how to retrieve the data.

Conclusion 

In this article, we had a detailed look at Laravel many to many relationships and an application implementing the relationship to solve a problem.

While Many to Many relationships are one of the least used relationships in real-world applications compared to one-to-one and one-to-many.

Understanding it makes it easy to apply to build such applications when the need arises make handling such cases straight-forward.  

Feel free to check out our other articles, where we similarly go in-depth to explain other Laravel relationships here.

Feel free to let us know if you have any questions, and we’d love to help.

Whenever you're ready

There are 3 ways we can help you become a great backend engineer:

The MB Platform

Join 1000+ backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.

The MB Academy

The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

Join Backend Weekly

If you like post like this, you will absolutely enjoy our exclusive weekly newsletter, Sharing exclusive backend engineering resources to help you become a great Backend Engineer.

Backend Tips, Every week

Backend Tips, Every week