Laravel Relation for different User types

Published 1 week ago by notflip

I have a platform where I have Users that are a Shop, they can place Orders, and I have Users that are Suppliers, they can view the placed Orders.

Now I'm running into some logic issue's, for example in my User class I woud like for the Supplier to be able to do this

Auth::user()->orders

But the User that is a shop should also be able to do this

Auth::user()->orders

I illustrated a bad example on how to do this here:

class User extends Authenticatable
{
  public function orders()
  {
      return $this->hasMany(Order::class, 'created_by');
  }

  public function alsoHasOrders()
  {
      return $this->hasMany(Order::class, 'fulfilled_by');
  }

}

There should be a better way to do this right?

thoasty

Id suggest you better seperate the users and authentication logic to a model for a ShopUser and a SupplierUser.

tisuchi
tisuchi
1 week ago (147,815 XP)

May be you can try with Polymorphic relationships. Imagine that, each order has been placed by either shop or Supplier.

Check More: https://laravel.com/docs/5.4/eloquent-relationships#polymorphic-relations

notflip

Thanks! @tisuchi I was thinking of that approach, But can't translate the example to my situation.. Would the User have the userable_id and userable_type for example?

ohffs
ohffs
1 week ago (223,530 XP)

The easier way to do it might be to check and return a different relation :

  public function orders()
  {
      if ($this->is_shop) {
          return $this->hasMany(Order::class, 'created_by');
      }
      return $this->hasMany(Order::class, 'fulfilled_by');
  }
thoasty

How about:

class User extends Authenticatable
{
  public function orders_placed()
  {
      return $this->hasMany(Order::class, 'created_by');
  }

  public function orders_received()
  {
      return $this->hasMany(Order::class, 'fulfilled_by');
  }

}

Related to your table structure polymorphism wont help you.

Solution by @ohffs breaks eloquent features.

notflip

@thoasty Thanks for your answer! Why does the if break eloquent features?

thoasty

Did not dig into it but might conflict with eager loading. And even if not it is still a bad example of code architecture. It uses one method for two different results.

So, either use two methods or two classes. Depending on your needs.

mushti

The solution given by @ohffs won't break anything. Just replace $this->is_shop with the property you use to differentiate between a shop and a supplier.

tisuchi
tisuchi
1 week ago (147,815 XP)

To make it simpler, I support @ohffs comments. Just make it if else, may be-

The easier way to do it might be to check and return a different relation :

public function orders() { if ($this->is_shop) { return $this->hasMany(Order::class, 'created_by'); } return $this->hasMany(Order::class, 'fulfilled_by'); }

thoasty

lol.

ohffs
ohffs
1 week ago (223,530 XP)

My answer could give unexpected results using with() clauses - but as the original question wanted to use it directly as a relation it's fine I think. But something to be aware of down the line. If you're going to be doing reporting or something that uses the with() then you'd probably want to break it out to something like :

// the 99% use-case
public function orders()
{
  if ($this->is_shop) {
    return $this->shopOrders();
  }
  return $this->supplierOrders();
}

public function shopOrders()
{
  return $this->hasMany(Order::class, 'created_by');
}

public function supplierOrders()
{
  return $this->hasMany(Order::class, 'fulfilled_by');
}

// then
foreach ($user->orders as ....

// or
$whatever = User::shops()->with('shopOrders')->....;
J5Dev
J5Dev
1 week ago (22,160 XP)

Just as an additional top this, one thing to consider is what if you want to allow users to be both supplier and shop?

What you would be better off doing is actually extracting the concept of a user as a person, away from the concept of what a user can do.

So, for example you may have the following Class'

  • User (The actual person logging in, and only information related to authentication.)
  • Shopper (Someone purchasing or who can purchase)
  • Supplier (An entity/company that sells things)
  • Order (A base class that holds logic for an order)
  • Purchase extends Order (Holds logic specific to being placed by a Shopper)
  • Sale extends Order (Holds logic specific to being serviced by a Supplier)

Then simply use relationships to manage the application flow, referencing Sale or Purchase respectively.

So...

User would simply house data and logic used for authentication, nothing more. A User could then register but without any constraints on what they can/cant do.

Upon making a first purchase, a Shopper record is created, that is associated with a user, but with additional info needed to be able to service their purchase (delivery address/s, real name etc.), both by the Supplier and the application. At this point a Purchase ( but actually Order) is associated with the Shopper.

Lets say that User then wants to sell something, they list an item, at which time a Supplier record is created (and subsequent items etc). If a Shopper then buys their item, the Order is created (by the user creating a Purchase), but it is managed as a Sale from the perspective of the Supplier.

Not only does this give a lot more freedom with regards to scaling the application, but it better represents the contextual business language commonly used within eCommerce.

All in all, much better, and all it requires is a couple of relationships, so much simpler!

Plus as outlined above, this removes any clash you may encounter with eager loading etc, as effectively a user logs in, and they are associated with being a Shopper and a Supplier, both of which can bea eager loaded at the same time, as can any Purchases and Sales... Simply eager load 'user.shopper.purchases' and 'user.supplier.sales' :D

Hope that helps :)

Sign In or create a forum account to participate in this discussion.