afrayedknot's avatar

Eager load relationship for authenticated user

So if I have a relationship on my User model:

    public function properties()
    {
        return $this->hasMany('\App\Property');
    }

Is it possible to eager load the properties on the authenticated user - so when I do

\Auth::user()->properties->count()

It is not another call to the database?

0 likes
21 replies
pmall's avatar

Interesting question im curious if someone has an answer.

Anyway for this situation two queries will be issued eager loading or not. Eager loading is made to resolve the N + 1 query problem not for loading all the model data in one query.

afrayedknot's avatar

But if I do \Auth::user()->properties->count() in 3-4 different places throughout my app/view, will that be 3-4 additional queries? Or just 1?

alexplekhanov's avatar

Protected property $with in Illuminate\Database\Eloquent\Model can be used. I am sure it will work for you. The only issue of this approach is that 'properties' will be eager loaded with all other User models.

3 likes
t0ne's avatar

what is the right thing to do here? if don't want to execute a query when i call "\Auth::user()->properties->count()" in the view, where should i load 'properties' to the AuthUser Model?

simplenotezy's avatar

I'm curious as well. I eager load some relationships on the authed user on every page load, so could be interesting how we could reduce the queries, so that the relationship is called when the user is Authed.

grantholle's avatar

I think it gets cached. If you use Laravel Debugbar you get a queries tab, which is super handy for catching pages that you forget to use eager loading. You can see how many and what queries get ran.

My template:

  {{ auth()->user()->roles->count() }}
  {{ auth()->user()->roles->count() }}
  {{ auth()->user()->roles->count() }}
  {{ auth()->user()->roles->count() }}

It runs 2 queries, one to get the authenticated user, the other to get the relationship. Any subsequent call to the relationship is cached. I'm using an employees table and model for my auth model:

select * from `employees` where `employees`.`id` = '1' limit 1
select `roles`.*, `employee_role`.`employee_id` as `pivot_employee_id`, `employee_role`.`role_id` as `pivot_role_id` from `roles` inner join `employee_role` on `roles`.`id` = `employee_role`.`role_id` where `employee_role`.`employee_id` = '1'
1 like
shincoder's avatar

The auth manager is a singleton + the user is cached (see line Guard.php line: 130) + relations are also not loaded every time (see: Model.php line: 2674).

So, I don't think you need eager loading, since the database hit will only occur for the first time.

As @Cocoon said, the count() method is called on the resulting collection not the relationship.

guillaume.caggia's avatar

You can do this in your controller to eager load your properties relation on the user :

$user = Auth::user();
$user->load('properties');

And it works ! You can pass $user variable after to your view.

Borisu's avatar

If I'm not mistaken there is a method withCount() that you can directly call on your relationship like this:

public function properties()
    {
        return $this->hasMany('\App\Property')->withCount('properties');
    }

You might have to experiment to find the exact correct usage for this.

EDIT:

From the Let's build a forum series:

  1. Use a global scope to always eager load a relationship, you can do this in your model
protected static function boot() {
  parent::boot();
  static::addGlobalScope('propertiesCount', function($builder) {
    $builder->withCount('properties');
  }
}
  1. Use the $with property:
protected $with = ['properties'];

Hope this helps.

1 like
ArthurYdalgo's avatar

I know it's been a long time since this was asked, but he's what I did: (this is a Inertia+React scenario, by the way)

# app/Http/Middleware/HandleInertiaRequests.php
public function share(Request $request): array
{
        $user = $request->user();
        $user?->load(['roles']);
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $user,
            ],
            'ziggy' => function () use ($request) {
                return array_merge((new Ziggy)->toArray(), [
                    'location' => $request->url(),
                ]);
            },
            // ...
        ]);
}
caption223's avatar

I know this post is pretty old, but I hope my answer may help other people. I'm using Laravel 10 for creating a REST API and this is what I did to eager load a relationship for authenticated user:

AuthController:

public function me()
{
    $user = User::with('applicantProfile')->find(Auth::id());
    return new AuthUserResource($user);
}

User model:

public function applicantProfile(): HasOne
{
    return $this->hasOne(ApplicantProfile::class);
}

AuthUserResource:

public function toArray(Request $request): array
{
    return [
        'user_profile' => new ApplicantProfileResource($this->whenLoaded('applicantProfile')),
    ];
}

My schema on applicant_profiles table:

$table->foreignIdFor(User::class);

krisi_gjika's avatar

@caption223 now you make 3 queries instead of 2 :(

What's wrong with auth()->user()->with('applicantProfile')?

Snapey's avatar

@caption223 This is pathetic

$user = User::with('applicantProfile')->find(Auth::id());

Use the authenticated user to find the authenticated user?

Just do

Auth::user()->load('applicantProfile');

but even this is unnecessary since there is no need to eager load something when there is only one model.

1 like
caption223's avatar

@Snapey Ohh sorry, I didn't mention that I have a separate model for applicant profile that is related to the authenticated user. I've tried doing that in the beginning but intelephense said the method 'load()' is undefined and the same thing when I tried using 'with()' . I don't want to get stuck so I quickly tried a different approach and that's what I came up with. I'm glad you pointed this out, using this is much better and actually works:

Auth::user()->load('applicantProfile');

I should have tried running it first, my bad. I'm still an entry level and I'm working on with this solo project. It's a bummer when there's no one else to talk to about this. I'm glad I posted this, thanks for the correction.

Please or to participate in this conversation.