1

I'm developing an application where a User can be of type Admin, Client, Supplier or Manager. Each type (except Admin) has it's own model with respective data, and related with a user_id. For example:

users
    - id
    - name
    - email
    - password
    - type

clients
    - id
    - user_id
    - segment
    - last_buy

suppliers
    - id
    - user_id
    - product_type
    - corporate_name

managers
    - id
    - user_id
    - managed_area

I want to have, in my User model, a profile attribute that loads user's data from it's other model (Client, Supplier or Manager), based on existing type attribute.

Before, I've used the $appends property and getProfileAttribute() method approach to achieve the result. But now, I'm trying to optimize my application using eager loading. So I'm trying to load the profile this way:

public function profile(){
    if($this->type == "client"){
        return $this->hasOne(Client::class);
    } else if($this->type == "supplier"){
        return $this->hasOne(Supplier::class);
    } else if($this->type == "manager"){
        return $this->hasOne(Manager::class);
    }
    return null;
}

But isn't working. Every query ->with(["profile"]) returns the profile attribute with null value. I've dded the $this->type and noticed is always returning null too. I don't understood why, but this is the cause why it can't conditionally check User's type.

So how can I achieve the expected result?

RBFraphael
  • 367
  • 1
  • 3
  • 13
  • 1
    What you are trying to do is close to being a morph in Laravel, here it is required that id and type is in the same table to make that work, are you able to change the code or? and the reasoning for your code not working is when queries are executed before model load, $this->type will always be null, so your approach is not feasible. – mrhn Mar 20 '23 at 01:11
  • Do you have relationships set correctly on all models? I think not. Show the contents of the Users, Clients, Suppliers, Managers Models. – mrkazmierczak Mar 20 '23 at 02:11

2 Answers2

0

You must specify a foreign key and local key for each hasOne because you are not using 'conventional' column names.

e.g.:

$this->hasOne(Client::class, 'id', 'user_id');
Pippo
  • 2,173
  • 2
  • 3
  • 16
  • OP *is* using conventional names ... ? The parent users table has an `id`, and each of the child tables has a `user_id`. – Don't Panic Mar 20 '23 at 06:12
0

As @mrhn commented above, the reason for your code not working is when queries are executed before model load. You can consider to apply One To One (Polymorphic) in your code: https://laravel.com/docs/10.x/eloquent-relationships#one-to-one-polymorphic-relations

Your tables would be:

users
    - id
    - name
    - email
    - password
    - profile_type
    - profile_id

clients
    - id
    - segment
    - last_buy

suppliers
    - id
    - product_type
    - corporate_name

managers
    - id
    - managed_area

Your migration:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->string('password');
    $table->morphs('profile');
    $table->timestamps();
});

Your relationship in user model:

/**
 * Get the user's profile;.
 */
public function profile()
{
    return $this->morphTo();
}
Khang Tran
  • 2,015
  • 3
  • 7
  • 10