Search code examples
phplaraveleloquenteager-loading

Laravel user model conditional eager loading (with)


I am working on a hybrid app build with Laravel and Vue. I have a use case where not all users have certain relations. For example a client can have a Domain and Multiple Business Units.

Currently i have set it up like this:

<?php

namespace App\Models;

use Laravel\Sanctum\HasApiTokens;
use Spatie\MediaLibrary\HasMedia;
use Illuminate\Notifications\Notifiable;
use Lab404\Impersonate\Models\Impersonate;
use Spatie\MediaLibrary\InteractsWithMedia;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements HasMedia
{
  use Traits\BaseModelTrait;
  use Traits\ActiveTrait;

  use InteractsWithMedia;
  use Impersonate;
  use HasApiTokens;
  use Notifiable;
  use HasFactory;

  protected $hidden = [
    'password', 'remember_token',
  ];

  protected $fillable = [
    'name', 'email', 'password', 'avatar',
  ];

  protected $casts = [
    'settings' => AsArrayObject::class,
    'is_admin' => 'boolean',
  ];

  protected $with = [
    'domain',
    'BusinessUnits'
  ];

  public function scopeAdmin($query)
  {
    return $query->where('is_admin', true);
  }

  public function scopeEmployee($query)
  {
    return $query->whereNull('domain_id');
  }

  public function scopeClient($query)
  {
    return $query->whereNotNull('domain_id');
  }

  public function BusinessUnits()
  {
    return $this->belongsToMany(BusinessUnit::class, 'users_business_units_pivot');
  }

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

The "problem" with this approach is that for every request 2 queries are executed for each user. I want the relations eager loaded only if the "domain_id" is not null (scopeClient). For normal "models" i can select per page what models should be loaded etc., but for the authenticated user this is not really possible as i know.

I think i am looking for something like this:

  protected $with = [
    (!$this->domain_id) ? 'domain' : null,
    (!$this->domain_id) ? 'BusinessUnits' : null
  ];

This currently generates an error: "Constant expression contains invalid operations."

Any advice and or ideas to tackle this would be appreciated!


Solution

  • You can try using events:

    // this code should be inside your model
    public static function boot()
    {
        parent::boot();
    
        self::retrieved(function($model){
            if($model->domain_id !== null)
            {
                 $model->load('domain', 'BusinessUnits');
            }
        });
    }
    

    and obviously, you have to remove those relations from $with