laravel / ideas

Issues board used for Laravel internals discussions.
938 stars 28 forks source link

Inverse of Eloquent hasManyThrough relationship #1170

Open mbkv opened 6 years ago

mbkv commented 6 years ago

I realized that there was no inverse of the hasManyThrough relationship, even though there's an inverse for everything else.

Consider the following minimalist slack-like messaging system

users:
+----------+--------+
|  column  |  type  |
+----------+--------+
| id       | int    |
| username | string |
+----------+--------+

display_names:
+------------+------+
|   column   | type |
+------------+------+
| id         | int  |
| user_id    | int  |
| channel_id | int  |
+------------+------+

messages:
+-----------------+------+
|     column      | type |
+-----------------+------+
| id              | int  |
| display_name_id | int  |
| message         | text |
+-----------------+------+

Now ignoring the fact this is definitely over-engineered. Doing

public function messages()
{
    return $this->hasManyThrough(Message::class, DisplayName::class);
}

would generate a query similar to

SELECT
    *
FROM
    `messages`
INNER JOIN
    `display_names` ON `display_names`.`id` = `messages`.`display_name_id`
WHERE
    `display_names`.`user_id` = ?

But there's no way to go the reverse. IE: having a message model and getting the user. Like such

SELECT
    *
FROM
    `users`
INNER JOIN
    `display_names` ON `display_names`.`user_id` = `users`.`id`
WHERE
    `display_names`.`id` = ?

Admitedly this is a contrived example, and I'm not too sure if there's any other use case than a poorly engineered database like this

fletch3555 commented 6 years ago

Just a point of pedantry... The inverse of a HasManyThrough is ALSO a HasManyThrough. What you're asking for should be doable with standard queries. Something like:

User::with('displayNames')->where('display_name.id', $id)

(disclaimer: This is untested code. Use at your own risk)

staudenmeir commented 6 years ago

@mbitokhov: You have to swap the models in your relationship:

public function messages()
{
    return $this->hasManyThrough(Message::class, DisplayName::class);
}

The reverse is just the concatenation of two BelongsTo relationships:

$message->displayName->user
mbkv commented 6 years ago

@fletch3555 I thought so too and it didn't really work out. Doing the inverse of a hasManyThrough with a hasManyThrough would require a display_name_id inside the users table. The inverse of a hasMany to hasMany is a belongsTo to belongsTo. You're thinking of belongsToMany being the inverse of a belongsToMany.

@staudenmeir Thanks for the warning!

monaam commented 6 years ago

So you need two relations, belongsToThrough, and belongsToManyThrough x)

bleuscyther commented 6 years ago

Found this : https://github.com/znck/belongs-to-through

quancoder commented 4 years ago

Found this : https://github.com/znck/belongs-to-through

very good

adnedelcu commented 3 years ago

Definitely after 2 years there is no more need for you to solve your problem, but for those that still arrive on this issue, there is also a solution available with Laravel methods. Starting with version 5.8, Laravel has hasOneThrough method (documentation).

In the case of the problem described here, the solution is not very pretty, but it would be something like this:

// Message::class
public function user()
{
    return $this->hasOneThrough(User::class, DisplayName::class, 'id', 'id', 'display_name_id', 'user_id');
}