jesse_orange_newable's avatar

Understanding the hasManyThrough relation

Hi everyone, I'm trying to understand a relation I haven't paid much attention to, until recently.

In my setup I have 3 models: Team, User and Article with relations between User and Article, andTeam and User


/**
 * A user belongs to a team
 *
 * @return void
 */
public function team()
{
    return $this->belongsTo(Team::class, 'department', 'name');
}


/**
 * Get all of the articles associated with this user
 */
public function articles()
{
    return $this->hasMany(Article::class, 'user_username', 'username');
}

I've also added the inverse of the given relations, so at the moment I can do

  • $team->users
  • $user->team
  • $article->author
  • $user->articles

I've been trying to get the articles written per department so this was my thought process:

  • Get the users in the department
  • Get the articles from the user

I feel like this is a perfect place to use a hasManyThrough so I tried this in the Team model:


/**
 * Get all of the articles for this department by going through users who authored articles
 * hasManyThrough: [farthest model]|[intermediate model]
 */
public function articles()
{
    return $this->hasManyThrough(
        'App\Article',
        'App\User',
        'department', // Foreign key on users table...
        'user_username', // Foreign key on articles table...
        'id', // Local key on teams table...
        'username' // Local key on users table...
    );
}

However, this returns null.

Essentially this query:


SELECT * FROM articles
LEFT JOIN users ON articles.user_username = users.username
WHERE user_username IS NOT NULL
AND users.department = 'Digital'

Or something similar to this?

"select * fromarticleswhere exists (select * fromuserswherearticles.user_username=users.usernameanddepartment= ?)"

0 likes
9 replies
Nakov's avatar

@jesse_orange_newable did you get the query by debugging or this is what you expect?

You can check where do you go wrong like this:

dd(Team::first()->articles()->toSql()); 

And make sure that the SQL is the one that you expect.

jesse_orange_newable's avatar

I never realized you could convert any Eloquent chain to SQL...

It output the following:

"select * from `articles` inner join `users` on `users`.`username` = `articles`.`user_username` where `users`.`deleted_at` is null and `users`.`department` = ?"
Nakov's avatar

@jesse_orange_newable it looks like the query is correct, so now the question is do you have data that belongs to that department ? Meaning users but also those users to have articles, otherwise it will return null because it is inner join not left join.

jesse_orange_newable's avatar

On my local machine this:

select * from `articles` inner join `users` on `users`.`username` = `articles`.`user_username` where `users`.`deleted_at` is null and `users`.`department` = 'Digital'

Returns as expected, which is very peculiar indeed.

jesse_orange_newable's avatar

Exactly, however the only difference is that I was testing in Tinker like so:


$team = Team::where('name', 'Digital')->get();

$team->articles;

Surely this would yield the same result?

Nakov's avatar

Hm, not sure. Because ->get() returns a collection, so I don't know how don't you get an error, saying articles does not belong to the Collection instance or similar.

You can test in tinker 100%, but try this instead:


$team = Team::where('name', 'Digital')->first();

$team->articles;

->first() returns an instance of the team :)

or the one liner I showed in my previous reply.

1 like
Nakov's avatar

@jesse_orange_newable Laravel is awesome just so you know :D

There is always something to get you debug easier. You can also install Laravel debugbar which also shows the queries in the browser. Or use Telescope. Many options, huge community :)

1 like

Please or to participate in this conversation.