phpMick's avatar
Level 15

Collection of related models.

I have struggled with this a few time and am sure there must be a Laravel way of doing this.

I have a collection of tokens which are assigned to a user. The tokens table has a user_id in it and $token->user gives me a User model (there is a belongsTo relationship).

I now want to get a collection of all the users, how should I do this? I currently pluck all the user_ids to an array like this:

$userIds = $tokens->pluck('user_id')->toArray();
$users = User::whereIn('id',$userIds)->get();

Surely there is a better way?

0 likes
14 replies
automica's avatar

@phpmick

if your token table has a user_id in it, then are you only applying to a single user? that would be a Token belongsTo User / User hasOne Token.

it sounds like relationship should be Token belongsToMany User

and then you could get

$users = $tokens->users();
phpMick's avatar
Level 15

Sorry, that was wrong, I have now updated my original post.

A token is assigned to a user. It's one to many.

Does it read OK now?

tykus's avatar

If you want all of the users related to a collection of tokens in a flat collection, then your current approach is just fine - it is efficient.

If you were to use a relationship, each Token instance in the collection will have its own users attribute which will be a Collection of User instances; this would require to be flattened in PHP.

phpMick's avatar
Level 15

@tykus are you sure about this: "each Token instance in the collection will have its own users attribute"

$token->user currently returns the user Model.

What I want, is to somehow do $tokens->users

Sinnbeck's avatar

Why not flip it?

$users = User::has('token')->get();
tykus's avatar

@phpmick my reply was based on the original OP. The advice remains to make a new query with the user_id plucked from each Token instance - it is the same query Laravel would run for eager-loading without the PHP overhead of assigning the resulting User instances to each Token instance.

phpMick's avatar
Level 15

I was simplifying things (well trying to). This is the actual code:

$userIds = PassportToken::whereJsonContains('scopes','manage')->get()->pluck('user_id')->toArray();
$users = User::whereIn('id',$userIds)->get();

So It is a bit like this:

token 1  ------ > manage scope ------ > user 1
token 2  ------ > manage scope  ------ >user 2
token 3  ------ >  manage scope ------ >user 3
token 4  ------ > other scope ------ >user 1
token 5  ------ > manage scope, other scope ------ >user 4

I want to get all of the users, with the manage scope.

MichalOravec's avatar
Level 75

This

$userIds = PassportToken::whereJsonContains('scopes', 'manage')->pluck('user_id');

$users = User::whereIn('id', $userIds)->get();

is same as

$users = User::whereHas('tokens', function ($query) { // if the relationship name is tokens
    $query->whereJsonContains('scopes', 'manage');
})->get();

or if exist collections of $tokens

$users = User::whereIn('id', $tokens->pluck('user_id'))->get();
1 like
Sinnbeck's avatar

So this (Damn you are fast Michal :D)

$users = User::whereHas('tokens', function($query) {
    $query->whereJsonContains('scopes','manage');
})->get();
1 like
automica's avatar

@michaloravec can you tell me what days you aren't planning on logging in to laracasts so I have a chance to reply :P

phpMick's avatar
Level 15

Yeah so this:

$users = User::whereHas('tokens', function($query) {
    $query->whereJsonContains('scopes','manage');
})->get();

is exactly what I wanted. I think it looks much neater?

Are we agreed that @michaloravec gets the best answer?

Thank's for your help everyone, much appreciated.

automica's avatar

@michaloravec

@automica In fact, I haven't focused very much on Laracasts for the last three months.

blimey. I dread to see what you're like when you are then. $onFire ++;

1 like

Please or to participate in this conversation.