Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

guyinpv's avatar

hasOneThrough confusion

Simplified three tables: User (id, name, car_id) Car (id, name, maker_id) Maker (id, name)

On the Maker model, I used a hasManyThrough to grab all the users with that make of car, worked just fine. return $this->hasManyThrough(User::class, Car::class, 'maker_id', 'car_id')

But I also want to go the other way. If I have a user, I should be able to read who the maker is since they can only have one car, it's kind of a direct relationship that way. It doesn't work though: return $this->hasOneThrough(Maker::class, Car::class, 'maker_id', '?????')

I'm not sure how the syntax should work in this case, because it's the User that has a car_id column, not the Car model that has a user_id column. I don't know what would go in the method parameters. I've tried a bunch of different arrangements but it doesn't return the model.

So what's the easiest way to have my User model return the maker by traversing through the Car model?

0 likes
6 replies
MarianoMoreyra's avatar

Hi @guyinpv

Laravel doesn't includes the inverse belongsToThrough relationship in this case. So, you'll have to add the belongsTo relationship to each Model and then get the maker by traversing the models in the standard way.

Or, you can try adding a third-party package like this one, that allows you to use a belongsToThrough relationship: https://github.com/staudenmeir/belongs-to-through

It seems to be maintained, although you should always keep in mind that it's not an official package, so you should use it at your own risk!

guyinpv's avatar

Note that the "middle" table Car cannot be changed to a pivot table, having both user_id and maker_id columns, because many users might have this car. That's why each user has the car_id, rather than the Car table having a user_id, which seems to be what the hasOneThrough expects.

The hasOneThrough query looks something like this:

"select * from makers inner join cars on cars.maker_id = maker.id where cars.user_id is 1000"

The trick here is where it looks for cars.user_id, which doesn't exist as a column.

It seems like what has to happen is a second inner join to connect the users table to the cars table, like this perhaps:

"select * from makers inner join cars on cars.maker_id = maker.id inner join users on users.cars_id = cars.id where users.id = 1000"

hasOneThrough obviously doesn't use two inner joins, but I wonder if there is a way to adjust the query to make it work this way?

MarianoMoreyra's avatar

That's right, you don't have to change the Car table to a pivot table, and you don't need to do it that way.

guyinpv's avatar

Ok sure, but I also don't want to get a plugin for this either.

So what is the correct way to build my own relationship or query? I know the default relationships but not how to expand them to do what I need.

Do I do this as a custom attribute instead with a custom query?

MarianoMoreyra's avatar

@guyinpv it will depend on what are you trying to achieve.

Assuming you have the corresponding belongsTo relationship on each Model like this:

User:

    public function car() {
        return $this->belongsTo(Car::class);
    }

Car:

    public function maker() {
        return $this->belongsTo(Maker::class);
    }

You can simply get the maker by doing, for example:

$user = User::with('car.maker')->find($user_id);

$maker = $user->car->maker;

Although I'm not sure if this is what you are asking for...

guyinpv's avatar

Correct, in my case it's:

User: return $this->belongsTo(Car::class);

Car: return $this->belongsTo(Maker::class);

The idea is that a user has one car and thus also one maker, but a maker could belong to many cars, and the same car could belong to many users. Just like a user can own a Honda Accord, but other users might also own Honda Accords. And an Accord is only made by Honda. So the user could only have the maker of Honda as well. I want to grab these in reverse too. If I have Honda, I want all the users. If I have Accord, I want all the users too.

I am perfectly able to get all the users who own Hondas:

Maker: return $this->hasManyThrough(User::class, Car::class, 'maker_id', 'car:id');

So in your example, in my User model if I want to directly grab their maker, this is what I ended up having to do yesterday.

User:

public function maker()
{
  if ($this->car) {
    return $this->car->maker->first();
  } else {
    return null;
  }
}

I'm using the car() method on the user model to first check if they even have a car at all, and only then use the maker() method on the car() model to pull the data. If I don't do it this way and the user doesn't have a car, I get null object errors all over the place.

In the view, I want to maintain a consistent way of accessing the data. For example when I want to grab the name of the car, it's pretty simple: {{optional($user->car)->name}}

So likewise I want to have a consistent way to access the maker too: {{optional($user->maker)->name}}

But this proves difficult. Given the maker() function I just posted, I have to write it like this, using the extra parenthesis around the maker function: {{optional($user->maker())->name}}

I don't want to have to use parenthesis on maker while not having to use it on car. It just leads to confusion.

Please or to participate in this conversation.