Laravel OrderBy Nested Collection - php

I'm using a Roles package (similar to entrust). I'm trying to sort my User::all() query on roles.id or roles.name
The following is all working
User::with('roles');
This returns a Collection, with a Roles relation that also is a collection.. Like this:
I'm trying to get all users, but ordered by their role ID.
I tried the following without success
maybe because 'roles' returns a collection? And not the first role?
return App\User::with(['roles' => function($query) {
$query->orderBy('roles.id', 'asc');
}])->get();
And this
return App\User::with('roles')->orderBy('roles.id','DESC')->get();
None of them are working. I'm stuck! Can someone point me in the right direction please?

You can take the help of joins like this:
App\User::join('roles', 'users.role_id', '=', 'roles.id')
->orderBy('roles.id', 'desc')
->get();
Hope this helps!

You can make accessor which contains role id or name that you want to sort by.
Assume that the accessor name is roleCode. Then App\User::all()->sortBy('roleCode') will work.

Here's the dirty trick using collections. There might be a better way to achieve this(using Paginator class, I guess). This solution is definitely a disaster for huge tables.
$roles = Role::with('users')->orderBy('id', 'DESC')->get();
$sortedByRoleId = collect();
$roles->each(function ($role) use($sorted) {
$sortedByRoleId->push($role->users);
});
$sortedByRoleId = $sortedByRoleId->flatten()->keyBy('id');

You can sort your relations by using the query builder:
notice the difference with your own example: I don't set roles.id but just id
$users = App\User::with(['roles' => function ($query) {
$query->orderBy('id', 'desc');
}])->get();
See the Official Laravel Docs on Constraining Eager Loading

f you want to order the result based on nested relation column, you must use a chain of joins:
$values = User::query()->leftJoin('model_has_roles', function ($join)
{
$join>on('model_has_roles.model_id', '=', 'users.id')
->where('model_has_roles.model_type', '=', 'app\Models\User');})
->leftJoin('roles', 'roles.id', '=', 'model_has_roles.role_id')
->orderBy('roles.id')->get();
please note that if you want to order by multiple columns you could add 'orderBy' clause as much as you want:
->orderBy('roles.name', 'DESC')->orderby('teams.roles', 'ASC') //... ext
check my answer here:
https://stackoverflow.com/a/61194625/10573560

Related

Search many level Eloquent Collection

If I have a collection with many levels, are there any tricks to find out what I need to put in my where() to be able to search by it? This is what I have so far, but I get an empty array from it. $contacts->where('contacts.tags.id', $tag->id) I've also tried contacts.tags.tags.id This is what I get if I run dd($contacts)
You may be looking for Eloquent - Eager Loading With Constraints.
$id = 'Your ID you are searching for';
$contacts= App\Models\Contact::with(['tags' => function ($query) {
$query->where('id', '=', $id');
}])->get();
show me all the contacts where tags = id. if it's only one you may switch ->get() for ->first() to return the object instead of a collection (array)

Laravel - Using a nested relationship with the query builder

I am working on an API but its starting to get a bit slow now that the data is increasing. I am moving some of the queries so that they use the DB query builder.
I have my last one which has a nested query:
$artists = Artist::with('performances', 'performances.stage')->get();
I have got so far:
$artists = \DB::table('artists')
->leftJoin('performances', 'artists.id', '=', 'performances.artist_id')
->get();
But now need to do the second relationship which in the Performance model is:
public function stage()
{
return $this->hasOne('App\Models\Stage', 'id', 'stage_id');
}
Any help on how I do this?
yes you can use eloquent relationship with query builder like this
$artists = Artist::join('performances', 'artists.id', '=', 'performances.artist_id')
->all();
foreach($artists as $artist){
$data = $artist->stage()->first();
}
It is very well covered in the official documentation, please, refer to this section of Documentation
I think that you want to achieve something like this:
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
And also, please, read carefully this section

Eloquent - where has pivot users orWhere has none

I'm trying to write a function that will get all "buckets" that are assigned to the auth'd user and/or buckets that have NO USERS assigned.
Relations and such, work as they should. Unless I'm missing something?
How can I get all buckets user is assigned too - and also include buckets where no users (including the auth user) are assigned.
Buckets user is assigned to
Buckets where NO users have been assigned. i.e. pivot table contains no rows for bucket, etc.
My issue very likely stems from the orWhere query...
$buckets = Team::currentTeam()->buckets()->with('user')->whereHas('user', function($query) {
$query->where('user_id', Auth::user()->id)
->orWhere('user_id', function() {
$query->count();
}, '<', 0);
})->get();
Didn't tested this but I think this should work. You're looking to remove that orWhere query and add orHas('user', '=', 0).
$buckets = Team::currentTeam()->buckets()->with('user')->whereHas('user', function($query) {
$query->where('user_id', Auth::user()->id);
})->orHas('user', '=', 0)->get();
Another possible solution you might consider is using a left join.
Heads-up: this might not be accurate as I don't know your db schema.
Team::currentTeam()
->buckets()
->leftJoin('users', 'users.bucket_id', '=', 'buckets.id')
->where(function($query) {
$query->where('users.id', $user_id)
->orWhereNull('user.id');
});
Please also check this blog post
AND-OR-AND + brackets with Eloquent
https://laraveldaily.com/and-or-and-brackets-with-eloquent/

using eloquent model property in subquery of with function

My question may be confuse because I've started learning Laravel.
Can I use eloquent model property in subquery of with() function?
I have classes, students, and grades tables. I wish to get
$classes = [{id:1, name:'maths', students:[{id:2, name: john,
grade:B},{id:1, name: Mac, grade:C}]}, {id:2, name:'physics',
students:[{id:2, name: john, grade:null},{id:1, name: Mac,
grade:null}]}]
for example:
$classes = Class::with(['students' => function($query){
->leftJoin('grades', 'grades.student_id', '=', 'students.id')
}])->get()->toJSON();
This query doesn't select correct grades of related classes. I wish to use class::id = grades.class_id in join condition.
I use leftJoin because some class may not have released its grade.
You need to do it this way:
$classes = Class::with(['students' => function($query){
->leftJoin('grades', function($join) {
$join->on('grades.student_id', '=', 'students.id');
$join->on('grades.class_id', '=', 'class.id');
})
}])->get()->toJSON();
Finally I got solutions, even though, not best solution but it worked. Thank #Marcin Nabialek and #lukasgeiter. It is just simply to through pivot table class_student.
$classes = Class::with(['students' => function($query){
$query->leftJoin('grades', function($join) {
$join->on('grades.student_id', '=', 'students.id');
$join->on('students.id', '=', 'class_student.student_id');
$join->on('class_student.class_id', '=', 'grades.class_id');
})
}])->get()->toJSON();
It took too much time bcoz passing through many join tables.
Hoping to get better solutions.

Complex joins in Eloquent

I have a table of itineraries. An itinerary belongs to a customer and has multiple days. A package is assigned to each of these days. I want to be able to produce a manifest showing which customers are allocated to a package and on which days.
I'm struggling with Eloquent, because you can't do queries beyond a one-to-Many relationship
What i want to do is this:
return $this->package->where('PackageID, $id)->itineraryDay->itinerary->customer->select('CustomerID', 'Date')
But can only really achieve it using the query builder:
return DB::connection($this->connection)
->table('t_package as PA')
->join('t_itinerary_day_map as IDM', 'IDM.PackageID', '=', 'PA.PackageID')
->join('t_itinerary_day as ID', 'IDM.ItineraryDayID', '=', 'ID.ItineraryDayID')
->join('t_itinerary as IT', 'IT.ItineraryID', '=', 'ID.ItineraryID')
->join('t_customer as CC', 'CC.ItineraryID', '=', 'IT.ItineraryID')
->where('PA.PackageID', $id)
->select('CC.CustomerID', 'ID.Date')
->distinct()
->get();
I really want to use Eloquent as I hate hardcoding table names and i've already created relationships for these models, but can't see any way around it
I believe you could do something like this to find customers that have a package with the given ID:
$packageId = 42;
$customers = $customer->whereHas('packages', function($q) use($packageId){
return $q->where('package_id', $packageId);
})->get();
How would that work for what you want?
I'll have to make few assumptions on your relationship but it seems doable.
If one ItineraryDay belongs to one Itinerary. And one Itinerary belongs to one Customer. And one ItineraryDay may have more than one Package.
$packageID = 111;
$itineraryDays = ItineraryDay::with('itinerary.customer')
->whereHas('package', function($q) use($packageID) {
$q->where('PackageID', $packageID);
})
->get();
foreach($itineraryDays as $itineraryDay) {
var_dump($itineraryDay);
var_dump($itineraryDay->itinerary->customer);
}
I'm not sure if i get your relationship method naming correct, but hopefully this works.

Categories