skip to Main Content

I have 3 tables users, products, product_users and this logic:

  1. User has many products
  2. Product belongs to User
  3. this relationship stores in product_users by ids.

Why don’t I simply store user_id in products table and avoid this suffering?

Because I want history of product ownership.

Anyway, I have hard time getting products owner (user) data by model relationships. It always return null

This is what I have so far:

Product.php

public function owner()
{
  return $this->belongsTo(User::class, 'product_users', 'product_id', 'user_id'); // probably should get latest entry as current owner
}

User.php

public function products()
{
  return $this->belongsToMany(Product::class, 'product_users', 'user_id', 'product_id');
}

product_users Schema: (simply just stores ids of both products and user nothing more)

Schema::create('product_users', function (Blueprint $table) {
    $table->id();
    $table->foreignId('product_id')->nullable();
    $table->foreignId('user_id')->nullable();
    $table->timestamps();
});
Schema::table('product_users', function (Blueprint $table) {
    $table->foreign('product_id')->references('id')->on('products')->onUpdate('cascade')->onDelete('cascade');
    $table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade')->onDelete('cascade');
});

What did I do wrong here?

2

Answers


  1. You should use hasOneThrough() instead of belongsTo().

    The belongsTo() means you have a foreign id on Product, but since you have the pivot table, your all relation should build through the pivot table.

    Try:

    User

    public function products()
    {
      return $this->belongsToMany(Product::class, 'product_users', 'user_id', 'product_id');
    }
    

    Product

    public function owner()
    {
      return $this->hasOneThrough(User::class, 'product_users', 'product_id', 'user_id'); // probably should get latest entry as current owner
    }
    
    Login or Signup to reply.
  2. belongsTo relationship is a relationship where one of your model has a reference column to another model, this doesn’t support having a pivot table. You cannot just build a model relationship you want then have a different database set-up relationship.

    Your database relationship is a "PLAIN" many-to-many so you should just treat the model relationship as many-to-many.

    wether you like it or not, your table design can support a user with many product and a product with many user, so you need to determine how you handle that case.

    Assume you only want 1 user result for each product even though your table allows you to have many, you can just add additional column in your pivot table to determine the actual "Owner" of that product even though you have many to many relationship.

    You can have your pivot table like below;

    public function up(): void {
        Schema::create('product_users', function (Blueprint $table) {
            $table->foreignId('user_id')->nullable();
            $table->foreignId('product_id')->nullable();
            $table->unsignedTinyInteger('owner')->default(0);
        });
    }
    

    then your User Model

    public function products() {
        return $this->belongsToMany(Product::class, 'product_users');
    }
    

    then your Product Model

    //All users 
    public function users() {
        return $this->belongsToMany(User::class, 'product_users')->withPivot('owner');
    }
    
    // Owner user
    public function owner() {
        return $this->belongsToMany(User::class, 'product_users')->wherePivot('owner', 1)->withPivot('owner');
    }
    

    this way, you have users relationship in your product which query all users belonging to a product

    then an owner relationship also stored in your pivot table but owner column value is 1

    all you have to do is make sure to owner value to 1 in the pivot table for the relationship that actually owns the product

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search