0

I'm working on a Fantasy sports app. The models I am working with are FantasyPlayer, PlayerGame, TeamGame

FantasyPlayer can have many PlayerGame and can have many TeamGame

public function PlayerGame()
{
    return $this->hasMany('App\Models\PlayerGame','player_id','player_id');
}

public function TeamGame()
{
    return $this->hasMany('App\Models\FantasyData\TeamGame','team','fantasy_player_key');
}

When I load the data I use eager loading currently:

FantasyPlayer::with(['PlayerGame', 'TeamGame'])->take(1)->get();

It is becoming tedious to load both relationships and then which are loaded. Ideally, I want to have the model handle this logic for. So I would be able to do something like this:

FantasyPlayer::with(['FantasyGame'])->take(1)->get();

Then my FantasyGame scope would contain either the PlayerGame or the TeamGame record I need based on an a FantasyPlayer value for the position. Something like this is what I want... but it doesn't work for me:

public function scopeFantasyGame($query)
{
    if($this->position == "DEF"){
      return $this->TeamGame();
    }
    else{
      return $this->PlayerGame();
    }
 }

Does anyone know way I could use eager loading and have the FantasyGame return the correct relationship based on a FantasyPlayer position attribute?:

FantasyPlayer::with(['FantasyGame'])->take(1)->get();
Kenny Horna
  • 13,485
  • 4
  • 44
  • 71
thindery
  • 1,164
  • 4
  • 23
  • 40
  • You've not provided enough information to be sure, but this sounds like it could be a good candidiate for using Laravel [polymorphic relationships](https://laravel.com/docs/5.8/eloquent-relationships#polymorphic-relationships). – fubar Apr 29 '19 at 22:05
  • If you are only returning 1 result is there a need for the eager loading? – ColinMD Apr 29 '19 at 22:08

1 Answers1

4

#1

You can't eager load relationships conditionally based on the result elements, this because the eager loading happens before you retrieve the records, that's why this won't work:

# FantasyPlayer.php

public function scopeFantasyGame($query)
{
    if($this->position == "DEF") // <--- Laravel doens't retrieve the records yet,
    {                            //      so this won't work
      //
    }
    else
    {
      // 
    }
 }

#2

Local Query scopes are used to constraint queries, in your case you want to load relationships with this scope, not its general purpose but sure you could do it:

# FantasyPlayer.php

public function scopeFantasyGame($query)
{
    return $query->with(['PlayerGame', 'TeamGame']);
}

Then use it like this:

# YourController.php

public function myFunction($query)
{
    $fantasyPlayers = FantasyPlayer::fantasyGame()->get();
}

#3

But then, if you want to always eager load the relationships, why use a query scope and not just tell laravel to load your desired relationships by default? You could do specify it in your model (check the Eager Loading By Default of this section of the docs):

# FantasyPlayer.php

protected $with = ['PlayerGame', 'TeamGame'];

Update

If you want to retrieve elements that always have a given relationship, you have two paths. For the first one, you could use a query scope to only load those elements:

# FantasyPlayer.php

public function scopeHasFantasyGame($query)
{
    return $query
              ->has('PlayerGame')
              ->has('TeamGame');
}

Then:

# YourController.php

public function myFunction($query)
{
    $fantasyPlayers = FantasyPlayer::hasFantasyGame()->get();
}

The second option would be to retrieve the elements, then filter the collection based on the existence of the relationship (using the Map() function):

# YourController.php

public function myFunction($query)
{
    $fantasyPlayers = FantasyPlayer::all()
                          ->map(function ($fantasyPlayer) {
                              return $fantasyPlayer->PlayerGame()->exists()
                                     && $fantasyPlayer->TeamGame()->exists();
                          });
}
Kenny Horna
  • 13,485
  • 4
  • 44
  • 71
  • thank you for the explanation - it is clearer now. The default eager loading sounds pretty good and will help in the long run. In that scenario, I still need to do some logic checking to see which relationship has data (PlayerGame or TeamGame). However, my final goal is to be able to call FantasyPlayer->FantasyGame() and always know that FantasyGame contains the record that I need (PlayerGame or TeamGame). It may not be possible with eager loading though. – thindery Apr 30 '19 at 12:46
  • 1
    Well, you have two ways to do that. I'll update my answer. – Kenny Horna Apr 30 '19 at 14:32
  • Thank you again for the great example code and information! This will help me tremendously! – thindery Apr 30 '19 at 15:13
  • @thindery glad to help. – Kenny Horna Apr 30 '19 at 15:16