Order by relationship deep - laravel

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;
}
}

Related

Laravel Eloquent how to get all parents

I have relation like this:
DB relation
I have a code in my model that retrieves me just one parent:
public function AllParents()
{
return $this->belongsToMany($this, 'parent', 'product_id', 'parent_id')
->select('parent', 'name');
}
I get it in my controller like this:
private function product(Product $product)
{
return $product->Product()
->with('AllParents')
->get();
}
Finally I need data like this:
Product1/Product_2/Product_3
I think I need a loop, but how to do it in Eloquent?
Just change relationship. You have mentioned pivot table name wrong one.
public function AllParents()
{
return $this->belongsToMany(Product::class, 'Product_parent', 'product_id', 'parent_id') ->select('parent', 'name');
}
and then you can access
\App\Models\Product::with('AllParents')->get()
In your Product Model. You have define relationship like this.
public function allParents()
{
return $this->belongsToMany(Product::class, 'product_parents', 'product_id', 'parent_id')->select('name');
}
And, In Controller you can get all the parents with eager loading.
Product::with('allParents')->find($productId);
And, In View you can use foreach loop to iterate every parent object.
#foreach ($product->allParents as $parentProduct)
{{ $parentProduct->name }}
#endforeach
I did like this:
I modified my controller
private function product(Product $product)
{
$allParents = [];
$parent = null;
$parent->$product->Product()->with('AllParents')->first()->id;
while ($parent != null) {
array_push($allParents, Product::all->find($parent));
$parent = Product::all()->find($parent) - first();
}
}

How to get a total amount of products in children categories of single parent

I have a Category model in which has children categories which has products... getting the total amount of products along with the children is easy. But what about getting the parent categories total amount which is the sum of all children categories of this single parent?
Here is the App\Category model:
public function products()
{
return $this->hasMany(Product::class, 'category_id');
}
public function parent()
{
return $this->hasOne(Category::class, 'id', 'parent_id');
}
public function children()
{
return $this->hasMany(Category::class, 'parent_id', 'id');
}
public function scopeTotalShirts($query)
{
return $query->where('slug', 'shirts')->first()->children->each->products()->count();
}
A shirt would have child categories such as T-Shirt, Long Sleeve, Graphic, etc.
I want to get the total of those children categories so it all adds up in the Shirts (parent) category.
Is there some elegant way to do this besides querying up the children categories and getting the count?
Thank you!
Based on your answer you can remove the extra queries in the loop, at the least:
$children = $category->children()->withCount('products')->get();
return $children->sum('products_count');
Or you can do the actual query from the inverse direction and remove the need for all of this:
return Product::whereHas('child.category', function ($q) {
$q->where('slug', 'shirts');
})->count();
Though, I don't know how you setup these relationships in the inverse.
Based on the relationships you have you probably need to do more querying here as I would suppose a Product could belong to a parent or child category:
return Product::whereHas('category', function ($q) {
$q->where('slug', 'shirts')
->orWhereHas('parent', fn ($q) => $q->where('slug', 'shirts'));
})->count();
The Product model should have a category relationship (belongsTo). The parent relationship on Category should be a belongsTo not hasOne.
Here is the solution I came up with:
public static function getTotalShirts()
{
$category = Category::where('slug', 'shirts')->first();
$children = $category->children()->get();
$count = 0;
foreach ($children as $child) {
$count += $child->products()->count();
}
return $count;
}
I was wondering if there was some neater way to do it? And is having public static functions safe?

Laravel, sort result on field from relation table?

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.

Laravel - select with relationship with one query

I have a Person model. Each person may have zero or more cars:
class Person extends Model
{
public function cars()
{
return $this->hasMany('App\Car');
}
}
I wish to select and display all persons who have a ford with one running query. So i tried this:
$persons = Person::whereHas('cars', function ($query) {
$query->where('mark', 'ford');
})->get();
foreach ($persons as $person) {
foreach($person->cars()->get() as $car) {
print $person->name . " has a " . $car->mark . $car->model
}
}
The $persons is gotten with one query, but inside the foreach loop $person->cars()->get() creates a new query for each person. How can i avoid this and get the needed car data with the first query?
You have to add the mark filter to whereHas() and with():
$persons = Person::whereHas('cars', function ($query) {
$query->where('mark', 'ford');
})->with(['cars' => function($query) {
$query->where('mark', 'ford');
}])->get();
The issue is in cars()
Use the below given snippet
foreach ($persons as $person) {
foreach($person->cars as $car)
{
print $person->name . " has a " . $car->mark . $car->model
}
}
When u do cars() it refers to model which execute another query. But when u use cars it only refers to collection which is already loaded.
Hope this helps
it is worth examining : laravel Eager Loading
When used in this manner will come in a query relational data.
Example:
Person::with('cars')->get();
This code response:
Person data with Person's Car data

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.

Resources