Laravel — P38: Many to Many Relationship (CMP)

Laravel Relationships Unlocked: Using belongsToMany, attach, and detach

The many-to-many relationship is slightly more complicated than the relationships that we’ve looked at so far. For our example, we have a PersonalCar and that PersonalCar has images. If we looked at the inverse of it, the Image can have one or more PersonalCars. How do you store the relationship? Does PersonalCar need to have an image_id (foreign id) inside it and does Image also need a personal_car_id inside it? This is where the concept of pivot tables come in. It’s a table that stores the foreign id of each table.

The Pivot Table Migration

We have already looked at the pivot table migration when we were creating our database structure, but what does it actually mean?

https://medium.com/geekculture/laravel-p35-database-setup-cmp-e9ab2aba05ae

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('image_personal_car', function (Blueprint $table) {
            $table->foreignId('personal_car_id');
            $table->foreignId('image_id');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('image_personal_car');
    }
};

Our pivot tables are named uniquely: they merge the names of the two tables that we’re trying to link. In this case, images and personal_cars. Each table’s plural form is reduced to the singular form, the tables are sorted in alphabetical order, and are linked with underscores. Singular forms of these two tables are image and personal_car. They’re already sorted alphabetically since image comes before personal_car (i before p). We simply need to link them with underscores: image_personal_car. And your pivot table is created with the personal_car_id and image_id foreign id fields inside it.

Relationship Setup (Many to Many)

The many-to-many relationship is defined with the belongsToMany method. Let’s create an images method inside of our PersonalCar mo

<?php

namespace App\Models;

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

class PersonalCar extends Model
{
    use HasFactory;

    protected $fillable = [
        'year', 'is_manual', 'exterior_color', 'purchase_amount', 'current_value', 'sales_amount', 'date_purchased', 'date_sold'
    ];

    public function brand()
    {
        return $this->belongsTo(PersonalCarBrand::class, 'personal_car_brand_id', 'id');
    }

    public function model()
    {
        return $this->belongsTo(PersonalCarModel::class, 'personal_car_model_id', 'id');
    }

    public function images()
    {
        return $this->belongsToMany(Image::class);
    }
}
Pretty straightforward. You simply add the belongsToMany(Image::class) code to your images method. Now what? Well, let’s try to retrieve it.
> PersonalCar::with('images')->first();

[!] Aliasing 'PersonalCar' to 'App\Models\PersonalCar' for this Tinker session.
= App\Models\PersonalCar {#4718
    id: 1,
    year: "2022",
    personal_car_brand_id: 6,
    personal_car_model_id: 6,
    is_manual: 1,
    exterior_color: "itaque",
    purchase_amount: 21846038,
    current_value: 635120796,
    sales_amount: 0,
    date_purchased: "2014-01-18",
    date_sold: null,
    created_at: "2023-01-20 20:37:11",
    updated_at: "2023-01-20 20:37:11",
    images: Illuminate\Database\Eloquent\Collection {#4716
      all: [],
    },
  }

By running the PersonalCar::with(‘images’)->first() in tinker, we can see that images is being pulled, but there are no images attached. That’s to be expected. We never populated our images table nor our image_personal_car pivot table. How do we do that?

attach()

To add an entry into our image_personal_car pivot table, we’ll need to have values in both our images table and our personal_cars table. Once they’re both in there, we’ll use the attach() method to link the two.

Let’s create a new Image in tinker.

> Image::create(['url' => '2007-chevy-corvette.jpg', 'alt' => '2007 Chevy Corvette']);

[!] Aliasing 'Image' to 'App\Models\Image' for this Tinker session.
= App\Models\Image {#4728
    url: "2007-chevy-corvette.jpg",
    alt: "2007 Chevy Corvette",
    updated_at: "2023-01-22 13:15:11",
    created_at: "2023-01-22 13:15:11",
    id: 1,
  }

The image is now inserted into the images table. We can verify that by extracting all of the images in the images table.

> Image::get();

= Illuminate\Database\Eloquent\Collection {#4723
    all: [
      App\Models\Image {#3760
        id: 1,
        url: "2007-chevy-corvette.jpg",
        alt: "2007 Chevy Corvette",
        created_at: "2023-01-22 13:15:11",
        updated_at: "2023-01-22 13:15:11",
      },
    ],
  }

We’ll need to now get and assign the PersonalCar to a variable and then the image that we just created to a variable of its own.

> $car = PersonalCar::first();

= App\Models\PersonalCar {#3768
    id: 1,
    year: "2022",
    personal_car_brand_id: 6,
    personal_car_model_id: 6,
    is_manual: 1,
    exterior_color: "itaque",
    purchase_amount: 21846038,
    current_value: 635120796,
    sales_amount: 0,
    date_purchased: "2014-01-18",
    date_sold: null,
    created_at: "2023-01-20 20:37:11",
    updated_at: "2023-01-20 20:37:11",
  }

The $car variable contains the first record from our personal_cars table.

> $image = Image::first();

= App\Models\Image {#4716
    id: 1,
    url: "2007-chevy-corvette.jpg",
    alt: "2007 Chevy Corvette",
    created_at: "2023-01-22 13:15:11",
    updated_at: "2023-01-22 13:15:11",
  }

The $image variable contains the first record from our images table.

Time to link them together. Let’s use attach(). But where? This will need to be done through our relationship that we have defined inside of our PersonalCar model, specifically the images() relationship.

> $car->images()->attach($image);
= null

Make sure that you use images() with the parentheses since you need to eloquent relationship, not the result. Since we’re starting off in the $car we need to attach($image). Let’s see if our $car has the images relationship associated with it.

> $car->images;

= Illuminate\Database\Eloquent\Collection {#4730
    all: [
      App\Models\Image {#4733
        id: 1,
        url: "2007-chevy-corvette.jpg",
        alt: "2007 Chevy Corvette",
        created_at: "2023-01-22 13:15:11",
        updated_at: "2023-01-22 13:15:11",
        pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4101
          personal_car_id: 1,
          image_id: 1,
        },
      },
    ],
  }

Lets try using our with() method and see if it returns the images associated with the PersonalCar.


> PersonalCar::with('images')->first();

= App\Models\PersonalCar {#4736
    id: 1,
    year: "2022",
    personal_car_brand_id: 6,
    personal_car_model_id: 6,
    is_manual: 1,
    exterior_color: "itaque",
    purchase_amount: 21846038,
    current_value: 635120796,
    sales_amount: 0,
    date_purchased: "2014-01-18",
    date_sold: null,
    created_at: "2023-01-20 20:37:11",
    updated_at: "2023-01-20 20:37:11",
    images: Illuminate\Database\Eloquent\Collection {#4743
      all: [
        App\Models\Image {#4746
          id: 1,
          url: "2007-chevy-corvette.jpg",
          alt: "2007 Chevy Corvette",
          created_at: "2023-01-22 13:15:11",
          updated_at: "2023-01-22 13:15:11",
          pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4744
            personal_car_id: 1,
            image_id: 1,
          },
        },
      ],
    },
  }

> 

It does. Success. Not too bad of a concept when you break it down piece by piece. Can we do the same with our Image model? Of course.

<?php

namespace App\Models;

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

class Image extends Model
{
    use HasFactory;

    protected $fillable = [
        'url', 'alt',
    ];

    public function cars()
    {
        return $this->belongsToMany(PersonalCar::class);
    }
}

Let’s try creating a new image and associating it through our Image model. Make sure to restart tinker.

> $image_two = Image::create(['url' => '2003-chevy-corvette.jpg', 'alt' => '2003 Chevy Corvette']);

= App\Models\Image {#4748
    url: "2003-chevy-corvette.jpg",
    alt: "2003 Chevy Corvette",
    updated_at: "2023-01-22 13:29:50",
    created_at: "2023-01-22 13:29:50",
    id: 2,
  }
> $car = PersonalCar::first();

[!] Aliasing 'PersonalCar' to 'App\Models\PersonalCar' for this Tinker session.
= App\Models\PersonalCar {#4719
    id: 1,
    year: "2022",
    personal_car_brand_id: 6,
    personal_car_model_id: 6,
    is_manual: 1,
    exterior_color: "itaque",
    purchase_amount: 21846038,
    current_value: 635120796,
    sales_amount: 0,
    date_purchased: "2014-01-18",
    date_sold: null,
    created_at: "2023-01-20 20:37:11",
    updated_at: "2023-01-20 20:37:11",
  }
Since our relationship now exists in the Image model, we can use it to attach $car as well. It really doesn’t matter which way you go.
> $image_two->cars()->attach($car);
= null

Now to test whether we have two images associated with our first car.

> $car->images;

= Illuminate\Database\Eloquent\Collection {#4503
    all: [
      App\Models\Image {#3760
        id: 1,
        url: "2007-chevy-corvette.jpg",
        alt: "2007 Chevy Corvette",
        created_at: "2023-01-22 13:15:11",
        updated_at: "2023-01-22 13:15:11",
        pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4717
          personal_car_id: 1,
          image_id: 1,
        },
      },
      App\Models\Image {#3776
        id: 3,
        url: "2003-chevy-corvette.jpg",
        alt: "2003 Chevy Corvette",
        created_at: "2023-01-22 13:30:08",
        updated_at: "2023-01-22 13:30:08",
        pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4504
          personal_car_id: 1,
          image_id: 3,
        },
      },
    ],
  }

There they are.

detach()

What if we wanted to remove the relationship? In other words, we no longer wanted to have an Image linked to a PersonalCar. We need to use the detach() method. Let’s create a third image, attach it, and then detach it.

> $image_three = Image::create(['url' => 'test.jpg', 'alt' => 'Test Image']);

= App\Models\Image {#4734
    url: "test.jpg",
    alt: "Test Image",
    updated_at: "2023-01-22 13:36:52",
    created_at: "2023-01-22 13:36:52",
    id: 4,
  }
> $car = PersonalCar::first();

= App\Models\PersonalCar {#4725
    id: 1,
    year: "2022",
    personal_car_brand_id: 6,
    personal_car_model_id: 6,
    is_manual: 1,
    exterior_color: "itaque",
    purchase_amount: 21846038,
    current_value: 635120796,
    sales_amount: 0,
    date_purchased: "2014-01-18",
    date_sold: null,
    created_at: "2023-01-20 20:37:11",
    updated_at: "2023-01-20 20:37:11",
  }
> $car->images()->attach($image_three);
= null
We now have three images attached to our $car.
> PersonalCar::with('images')->first();

= App\Models\PersonalCar {#4728
    id: 1,
    year: "2022",
    personal_car_brand_id: 6,
    personal_car_model_id: 6,
    is_manual: 1,
    exterior_color: "itaque",
    purchase_amount: 21846038,
    current_value: 635120796,
    sales_amount: 0,
    date_purchased: "2014-01-18",
    date_sold: null,
    created_at: "2023-01-20 20:37:11",
    updated_at: "2023-01-20 20:37:11",
    images: Illuminate\Database\Eloquent\Collection {#4740
      all: [
        App\Models\Image {#4745
          id: 1,
          url: "2007-chevy-corvette.jpg",
          alt: "2007 Chevy Corvette",
          created_at: "2023-01-22 13:15:11",
          updated_at: "2023-01-22 13:15:11",
          pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4743
            personal_car_id: 1,
            image_id: 1,
          },
        },
        App\Models\Image {#4746
          id: 3,
          url: "2003-chevy-corvette.jpg",
          alt: "2003 Chevy Corvette",
          created_at: "2023-01-22 13:30:08",
          updated_at: "2023-01-22 13:30:08",
          pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4742
            personal_car_id: 1,
            image_id: 3,
          },
        },
        App\Models\Image {#4748
          id: 4,
          url: "test.jpg",
          alt: "Test Image",
          created_at: "2023-01-22 13:36:52",
          updated_at: "2023-01-22 13:36:52",
          pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4741
            personal_car_id: 1,
            image_id: 4,
          },
        },
      ],
    },
  }

Before we delete it, lets see what our pivot table looks like in mysql.

mysql> select * from image_personal_car;

+-----------------+----------+
| personal_car_id | image_id |
+-----------------+----------+
|               1 |        1 |
|               1 |        3 |
|               1 |        4 |
+-----------------+----------+
3 rows in set (0.00 sec)

mysql>

Pretty cool. I want to delete the last relationship 1 and 4. This is the one that has test.jpg as our image.

First, we need to pull up that image.

> $image = Image::find(4);

= App\Models\Image {#4729
    id: 4,
    url: "test.jpg",
    alt: "Test Image",
    created_at: "2023-01-22 13:36:52",
    updated_at: "2023-01-22 13:36:52",
  }
Next, we need to pull up our PersonalCar with the id of 1.
> $car = PersonalCar::find(1);

= App\Models\PersonalCar {#4745
    id: 1,
    year: "2022",
    personal_car_brand_id: 6,
    personal_car_model_id: 6,
    is_manual: 1,
    exterior_color: "itaque",
    purchase_amount: 21846038,
    current_value: 635120796,
    sales_amount: 0,
    date_purchased: "2014-01-18",
    date_sold: null,
    created_at: "2023-01-20 20:37:11",
    updated_at: "2023-01-20 20:37:11",
  }

> 

Now to detach. We can detach from Image or from PersonalCar. In other words, both of these are valid:

$car->images()->detach($image);
$image->cars()->detach($car);

I’ll execute the first one.

> $car->images()->detach($image);
= 1
We can now look at the images associated with our PersonalCar and that image will be removed.
> $car->images;

= Illuminate\Database\Eloquent\Collection {#4740
    all: [
      App\Models\Image {#4749
        id: 1,
        url: "2007-chevy-corvette.jpg",
        alt: "2007 Chevy Corvette",
        created_at: "2023-01-22 13:15:11",
        updated_at: "2023-01-22 13:15:11",
        pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4748
          personal_car_id: 1,
          image_id: 1,
        },
      },
      App\Models\Image {#4751
        id: 3,
        url: "2003-chevy-corvette.jpg",
        alt: "2003 Chevy Corvette",
        created_at: "2023-01-22 13:30:08",
        updated_at: "2023-01-22 13:30:08",
        pivot: Illuminate\Database\Eloquent\Relations\Pivot {#4739
          personal_car_id: 1,
          image_id: 3,
        },
      },
    ],
  }

I hope that this article has helped you demystify many-to-many relationships. It seems like this topic is sometimes difficult for developers to grasp, which is why I dedicated an entire article on it. We’ll come back to this concept, and many others revolving relationships, when we do a more thorough deep dive later.

Laravel Series

Continue your Laravel Learning.

Laravel — P37: Models $fillable, belongsTo and hasMany (CMP)

Mastering Models in Laravel: $fillable, belongsTo, and hasMany

Laravel – P37: CMP – Models $Fillable,Belongsto and Hasmany

Explore Laravel models with a focus on $fillable, belongsTo, and hasMany. Understand how to efficiently manage relationships and data handling in your projects.

Laravel — P38: Many to Many Relationship (CMP)

Laravel Relationships Unlocked: Using belongsToMany, attach, and detach

Laravel – P38: CMP – Many To Many Relationship belongsToMany, attach, detach

Learn how to manage many-to-many relationships in Laravel using belongsToMany, attach, and detach. Simplify complex data links in your applications.

Laravel — P39: Routes and Controllers High Level Setup (CMP)

Streamlining Laravel: Routes and Controllers High-Level Overview.

Laravel – P39: CMP – Routes And Controllers High-Level Setup

Discover how to efficiently set up routes and controllers in Laravel. A high-level guide to streamline your application’s foundational structure.

Leave a Reply