Laravel, sort result on field from relation table? - laravel

I have a list with gamers and another table with game stats.
My list code is:
$gamers = Gamer::with(['lastGameStat' => function($query) {
$query->orderBy('total_points', 'DESC');
}])->paginate(20);
relation:
public function lastGameStat() {
return $this->hasOne(GameStat::class, 'gamer_id', 'id')->orderBy('created_at', 'DESC');
}
in relation table I have field: total_points and with this code I thought it's possible to sort list of gamers by total_points $query->orderBy('total_points', 'DESC');
It doesn't work, can somebody give me an advice here how can I sort the result on a field from relation table?

I guess you'll need either another relation or custom scopes to fetch various game stats of a gamer.
Second relation
Gamer.php (your model)
class Gamer
{
public function bestGameStat()
{
return $this
->hasOne(GameStat::class)
->orderBy('total_points', 'DESC');
}
}
Custom scopes
Gamer.php
class Gamer
{
public function gameStat()
{
return $this->hasOne(GameStat::class);
}
}
GameStat.php
use Illuminate\Database\Eloquent\Builder;
class GameStat
{
public function scopeBest(Builder $query)
{
return $query->orderBy('total_points', 'DESC');
}
}
In your controller:
$gamersWithTheirLatestGameStatistic = Gamer::with(['gameStat' => function($query) {
$query->latest();
}])->paginate(20);
$gamersWithTheirBestGameStatistic = Gamer::with(['gameStat' => function($query) {
$query->best();
}])->paginate(20);
Be aware as this is untested code and might not work.

Related

How to get data from Laravel relation

I have 2 models: publictxt and Pulicetxtrecive
publictxt model:
class Publictxt extends Model
{
protected $fillable=['sender','reciver','description','useridseen'];
public function Publictxtrecives()
{
return $this->hasMany(Pulicetxtrecive::class,'publictxt_id', 'id');
}
}
Pulicetxtrecive model:
protected $fillable=['publictxt_id','user_id','seen'];
public function publictxts()
{
return $this->belongsTo(Publictxt::class);
}
I want to get the values ​​from the Publictxt that are available in the Pulicetxtrecive.
When a record is stored in the Publictxt, users are registered in the Pulicetxtrecive after viewing.
$pulictxtcount=Publictxt::where('reciver',Auth::user()->shift)->orwhere('reciver',1)->with('Publictxtrecives')->whereHas('Publictxtrecives',function($q){$q->where('seen', '=', 0);})->count();
this code doesn't work.
There are some conflicts in your database structure.
You said when a user sees a letter the Publictxtrecives will be created.
That means if a Publictxt has a Publictxtrecives that definitely has been seen .
But there is a seen column in Publictxtrecives table.
You should pick one.
But anyway as this structure:
$pulictxtcount=Publictxt::where(
function($query){
$query->where('reciver',Auth::user()
->shift)->orwhere('reciver',1);
})
->Where(function($query)
{
$query->whereHas('Publictxtrecives',
function($q){$q->where('seen',1);
}
)->orWhereDoesntHave('Publictxtrecives');
})
->with('Publictxtrecives');
$pulictxtcount = Publictxt::with([
'Publictxtrecives' => function($query){
$query->where('seen', 0);
}
])
->where('reciver', Auth::user()->shift)
->orwhere('reciver', 1)
->get();
I solved this problem:
$pulictxtcount = Publictxt::where(function($q){$q->where('reciver',Auth::user()->shift)->orwhere('reciver',1);})->whereDoesntHave('Publictxtrecives', function($q){$q->where('user_id', Auth::user()->id);
})->count();

Where clause in polymorphic relationship

I have the tables
threads
- id
replies
- id
- repliable_id
- repliable_type
I want to add another column to the Thread, which is the id of the most recent reply.
Thread::where('id',1)->withRecentReply()->get()
And the following query scope
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->whereHasMorph('repliable', ['App\Thread'], function ($q) {
$q->where('repliable_id', '=', 'threads.id');
})->latest('created_at')
->take(1),
]);
}
I have also tried
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->where('repliable_id', '=', 'threads.id')
->latest('created_at')
->take(1),
]);
}
But in both cases the
recent_reply_id => null
If instead of threads.id i enter an integer, it works and the recent_reply_id is not null
For example
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->whereHasMorph('repliable', ['App\Thread'], function ($q) {
$q->where('repliable_id', '=', 1);
})->latest('created_at')
->take(1),
]);
}
My question is
Is there a way to be able to fetch the recent_reply_id using the respective threads.id ?
I would suggest using appends instead of query scopes so in your Thread model we will add
protected $appends = ['recent_reply_id'];
.....
public function replies () {
// you need to add here your relation with replies table
}
public function getRecentReplyIdAttribute () {
return $this->replies()->latest('id')->first()->id;
}
now wherever you query the threads table you will have access to recent_reply_id like
$thread = Thread::where('id',1)->withRecentReply()->get();
$thread->recent_reply_id;

Eloquent `with()` with filtering based on relation

I have this tables.
And this model relations, this relations works fine.
class Item extends Model
{
public function translations()
{
return $this->hasMany(ItemTranslations::class);
}
}
class ItemTranslation extends Model
{
public function language()
{
return $this->belongsTo(Language::class);
}
}
I need to return a list of items with the translations, but only the translations related to a specific language.
I can't have this query working, im getting all translations of each item, not only the one filtered with this query. The language related to the translation is not needed on the result.
$query = Item::query();
$query->with('translations')->when('language',function($query) use ($ISOlanguage) {
return $query->where('languages.ISO_code', '=', $ISOlanguage);
});
return $query->paginate();
Any idea who i can have this working? Thanks!
So what you want to do is constraining eager loading
Item::with(["translations" => function ($query) use ($ISOlanguage) {
$query->where('language.ISO_code', $ISOlanguage);
}])->get();
https://laravel.com/docs/5.8/eloquent-relationships#constraining-eager-loads
I finally have it working
Item::with(['translations' => function($query) use ($ISOlanguage) {
$query->whereHas('language', function($query) use ($ISOlanguage) {
$query->where('ISO_code', '=', $ISOlanguage);
});
}])->get();
Thanks #julian-s for your help!

Get all championships that are teams in Eloquent

I have a tournament, a tournament can have many >
public function championships()
{
return $this->hasMany(Championship::class);
}
and a Championship hasOne Category. In Category, I have the isTeam attribute.
Now I need a function that get me all the championships that have the isTeam = 1 in Category table.
public function teamChampionships()
{
}
Of course, I have defined : $tournament->championships, $championship->category
In my controller, I get all of them:
$tournament = Tournament::with('championship.category')->find($tournament->id);
Any idea???
Try
$tournament = Tournament::with(['championships' => function ($query) {
$query->whereHas('category', function($subquery) {
$subquery->where('isTeam', '=', 1);
});
}])->get();
If the above doesn't work, try a different approach. Define isTeam() scope in Category model
public function scopeIsTeam($query) {
return $query->where('isTeam', 1);
}
Then you can use it like this
$tournament = Tournament::with('championships.categoryIsTeam')
->find($tournament->id);
Even better, create another scope in Championship that loads only teams
public function categoryTeam() {
return $this->hasOne(Category::class)->isTeam();
}
Sorry for too much information. One of those should do the job.

Order by relationship deep

This is my models structure:
class Family extends Eloquent
{
public function members()
{
return $this->hasMany('App\Person');
}
}
class Person extends Eloquent
{
public function family()
{
return $this->belongsTo('App\Family');
}
public function school()
{
return $this->belongsTo('App\School');
}
}
class School extends Eloquent
{
public function students()
{
return $this->hasMany('App\Person');
}
}
So, a Person belongs to both Family and School.
I fetch a Family:
$family = Family::find(1);
And list the members:
foreach ($family->members AS $member) {
$member->name;
foreach ($member->school AS $school) {
$school->name;
}
}
How can I order the schools by name in ASC order?
EDIT
How to make this list ordered:
foreach ($family->members AS $member) {
$member->school->name;
}
EDIT 2
Very sorry. I've got mixed up.
What I'm trying to achieve is to sort the members of the family by the name of the school that they're attending.
A Person can attend only one school.
Try
$family = Family::with(['members.school'=>function($q){
$q->orderBy('name', 'ASC');
}])->find(1);
The with function will eagerload your records preventing the N + 1 query problem and also make your application run faster.
foreach ($family->members AS $member) {
$member->name;
foreach ($member->school AS $school) {
$school->name;
}
}
hope it helps
I think you need to eager load the relationship then order the relationship:
$family = Family::with(['members.school' => function($query) {
$query->orderBy('name', 'asc');
}])->find(1);
Edit
In response to your comment:
What if every person belongs to only one school, and I want to list
members ordered by the school name, without the second loop? Will
update question with the code in a second
I'm pretty sure you can go ahead and use the same query above with the eager loading as the school() relation on Person is belongsTo so it will return one eloquent record not many. You'd only need another for loop if it was returning an eloquent collection. So you should be able to run the loop like so:
foreach ($family->members AS $member) {
$member->name;
$member->school->name;
}
Unless I'm missing something?
I'll just point out as well that if you always want them sorted by name, you can define a relation on your Person object for school and order it like this:
public function schoolOrderedByName() // you could just call it school()
{
return $this->belongsTo('App\School')->orderBy('name', 'asc');
}
Then you don't have to write the constraint every time you use the relation:
$family = Family::with(members.schoolOrderedByName')->find(1);
Before the second foreach, add this statement
$schools = $member->school->orderBy('name', 'ASC')->get();
Like this
foreach ($family->members AS $member) {
$member->name;
$schools = $member->school->orderBy('name', 'ASC')->get();
foreach ($schools AS $school) {
$school->name;
}
}

Resources