I’m working on a project using Laravel v9.19 and PHP 8.0.
Here is a part of that project’s database which is relevant to the question:
- One user can have many orders, but each order has only one user (one-to-many relationship).
- One order can have only one item, but one item may be present in many orders (many-to-one relationship).
Here is a User class:
class User extends Authenticatable
{
// ...
/**
* Get orders of this user.
*
* @return IlluminateDatabaseEloquentRelationsHasMany
*/
public function orders(): HasMany
{
return $this->hasMany(Order::class, 'client_id');
}
}
Here is an Order class:
class Order extends Model
{
// ...
/**
* Get the client of this order.
*
* @return IlluminateDatabaseEloquentRelationsBelongsTo
*/
public function client(): BelongsTo
{
return $this->belongsTo(User::class, 'client_id');
}
/**
* Get the ordered item.
*
* @return IlluminateDatabaseEloquentRelationsBelongsTo
*/
public function item(): BelongsTo
{
return $this->belongsTo(Item::class, 'item_id');
}
}
Here is an Item class:
class Downloadable extends Model
{
// ...
/**
* Get the orders having this item.
*
* @return IlluminateDatabaseEloquentRelationsHasMany
*/
public function orders(): HasMany
{
return $this->hasMany(Order::class, 'item_id');
}
}
I would like to utilize the Eloquent Relationships in order to make it possible to:
- Retrieve all items ordered by a specific user.
- Retrieve all users that ordered a specific item.
I will need to add a new method to the User model:
/**
* Get items ordered by this user.
*
* @return ???
*/
public function items()
{
// What to do here?
}
I will need to add a new method to the Item model:
/**
* Get users that ordered this item.
*
* @return ???
*/
public function users()
{
// What to do here?
}
Since the default HasManyThrough relationship requires a different table structure, and I cannot modify the existing table structure, it’s not applicable here.
As one of possible solutions I’m considering to use staudenmeir/eloquent-has-many-deep, creating a new intermediate table named order_item
to map orders to items, and use the following code, for example, for the items method:
/**
* Get items ordered by this user.
*
* @return ???
*/
public function items()
{
return $this->hasManyDeep(
Item::class,
[Order::class, 'order_item']
);
}
If there exists a Laravel solution without external libraries, or you know something more efficient, please share that to me. I’m eager to know the best practice for that case.
2
The relationship you have defined is a belongsToMany
relationship, but if you’re set on using hasManyThrough
, purely to retrieve the other set you could use:
class User extends Authenticatable
{
// ...
/**
* Get all items ordered by the user.
*
* @return IlluminateDatabaseEloquentRelationsHasManyThrough
*/
public function items(): HasManyThrough
{
return $this->hasManyThrough(Item::class, Order::class, 'client_id', 'id', 'id', 'item_id');
}
}
class Item extends Model
{
// ...
/**
* Get all users who ordered this item.
*
* @return IlluminateDatabaseEloquentRelationsHasManyThrough
*/
public function users(): HasManyThrough
{
return $this->hasManyThrough(User::class, Order::class, 'item_id', 'id', 'id', 'client_id');
}
}
However if you are look to create Orders
through the relationship you should use the belongsToMany