Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ignaaaam's avatar

Transform one to many relationship into many to many

Hello, I finished the Laravel 8 from scratch course, and there's something I would like to do but I dont know if it's better to start it from zero and delete the Category entirely. My idea is that now there's a relation one to many between Post and Category, what I want is to convert that into a many to many relationship so one post can have more than 1 category, instead of just 1 category can belong to multiple posts. The problem is that since all its made (components, migrations, etc...) with the idea of just having 1 post maybe its better to delete all Category and start it from scratch.

Category Model

<?php

namespace App\Models;

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

/**
 * App\Models\Category
 *
 */

class Category extends Model
{
    use HasFactory;

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

Post Model

<?php

namespace App\Models;

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

/**
 * App\Models\Post
 */
class Post extends Model
{
    use HasFactory;

    protected $with = ['category','author'];


    public function scopeFilter($query, array $filters)
    {
        $query->when($filters['search'] ?? false, fn($query, $search) =>
            $query->where(fn($query) =>
                        $query->where('title', 'like', '%' . $search . '%')
                        ->orWhere('body', 'like', '%' . $search . '%')
                    )
            );
        $query->when($filters['category'] ?? false, fn($query, $category) =>
            $query->whereHas('category', fn ($query) =>
                $query->where('slug', $category)
            )
        );

        $query->when($filters['author'] ?? false, fn($query, $author) =>
            $query->whereHas('author', fn ($query) =>
                $query->where('username', $author)
            )
        );
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    public function author()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}

What I need for sure is changing the relationship in models (maybe in the Post it should be)

return $this->hasMany(Category::class)->withPivot('category_id'); 

and in the Category model maybe should be

return $this->belongsToMany(Post::class)
                    ->withPivot('post_id'); 

And also I would need the pivot table that I think it could be like this:

class CreatePostsCategoriesPivotTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts_categories', function (Blueprint $table) {
            $table->unsignedBigInteger('post_id')->index();
            $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
            $table->unsignedBigInteger('category_id')->index();
            $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
            $table->primary(['category_id', 'post_id']);
        });
    }

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

I would appreciate if someone can tell me if im wrong or not, or what should I just do next, because I also populated the pivot table (with random numbers of maximum posts & categories). Also at the time of serving the project I get errors with the scopeFilter (searching function) because I guess i'll need to update that to match the new categories relationship.

If someone can help me would appreciate it, if you need any other file i'll share it, but its basically the same as Laravel 8 from scratch course final files.

Thanks to everyone & Jeffrey i'm learning a lot here.

0 likes
10 replies
sr57's avatar
sr57
Best Answer
Level 39

maybe its better to delete all Category and start it from scratch.

Can be done but not the (ideal) solution if you have lot of data.

I did not review your code but I think you get the idea that I summarize here :

-1- backup of your db (at least tables concerned) - did not see this point :-)

-2- create the intermediate (pivot) table

-3- populate this table with the 'hasMany relations'

-4- adapt your models (even if you did an error you'll not loose data, and in case of ... you have your backup)

-5- adapt your foreign keys as needed

1 like
ignaaaam's avatar

@sr57 Since its a project for learning and all the data I have its mostly generated via seeders or by me while testing post methods I wouldnt mind losing the data, but I'll try what you summarized.

Also one thing, when I seed the data, I create 25 posts and 25 categories are created aswell, right now every post is attached to every category in an autoincrement way, should I change that aswell I think? And to populate pivot table is it ok to do it this way? : This is the DatabaseSeeder function to populate pivot table.

foreach(range(1,25) as $index){
            DB::table('posts_categories')->insert([
                'post_id' => rand(1, 25 ),
                'category_id' => rand(1,25)
            ]);
        }

Thanks for your answer!

ignaaaam's avatar

@MichalOravec Thanks will do, had it this way before but it sounded weird to me, but yeah I'll change it.

sr57's avatar

@ignaaaam

populate is only one shot, I never use seed, or php for that, it's easier (for me) to use sql directly in the db.

INSERT INTO post_category (post_id,category_id) SELECT post_id,id FROM categorys
1 like
ignaaaam's avatar

Still without knowing how to solve this, does the relation in Post should be return $this->hasMany(Category::class, 'category_post'); or it should be return $this->belongsToMany(Category::class, 'category_post'); in both models? At first I thought it should be hasMany and belongs to many in category model but I've seen on some tutorials & Laravel documentation that say that it has to be belongsToMany on both. But my problem is to know how to adapt views and the search filter to this new relationship.

sr57's avatar

or it should be return $this->belongsToMany(Category::class, 'category_post');

Yes, the relation is not hasMany and is TOTALLY symmetric

https://laravel.com/docs/9.x/eloquent-relationships#many-to-many-defining-the-inverse-of-the-relationship

But my problem is to know how to adapt views and the search filter to this new relationship.

It's another subject*, ... I should begin from scratch, you have to think differently (new models / new functionalities)

*probably time to close this one.

1 like
ignaaaam's avatar

@sr57 Yeah thanks for the help, will open another thread to see if there's an easy way to adapt all components from the one to many to many to many new relationship.

Thanks!

sr57's avatar

@ignaaaam You probably don't know how to close a subject, just chose an answer from the thread as best. Do this for all your past threads.

ignaaaam's avatar

@sr57 Thanks! Didn't know that, tought some admin should close it.

Please or to participate in this conversation.