Laravel get all related models in one collection/array - php

Laravel 4
Is there any easy way to get all related models in one collection (only with Eloquent)?
For example, I want to get all of the students, that is related to many classes:
First, I need to get a collection with the relations:
$classes = Class::with('students')->whereIn('code', ['A', 'B'])->get();
Then I need to go through the entire collection to merge the students:
$allStudents = new Collection;
foreach ($classes as $class) {
$allStudents = $allStudents->merge($class->students);
}
Or if I need only one key, for example, id of the student, I'm doing this:
$allStudentsIds = [];
foreach ($classes->fetch('students') as $studentsArray) {
$allStudentsIds = array_merge(
$allStudentsIds, array_pluck($studentsArray, 'id')
);
}
Is there a method to get only related models? Or somehow I can make it through a request to the Student model?

Query based on students and join with classes?
Student::select('student.*')
->join('class_student', 'class_student.student_id', '=', 'student.id')
->join('class', 'class.id', '=', 'class_student.class_id')
->whereIn('class.id', $classes)
->get();
Something like that.
I assumed it is a many to many relationship and class_student is the association table

Related

How to detach on many element with Laravel Eloquant

I have a relationship many to many with the table Screen and Media with a pivot table Media_Screen.
I want to remove the data with the screens' id in $id_screens from the pivot table.
I did like this :
$id_screens = [4,5];
$screens = App\Screen::whereIn('id', $id_screens)->get();
foreach($screens as $screen)
{
$screen->medias()->detach();
}
It works but I'm wondering if there is not a better way to do? I tried something like this but it didn't work :
$id_screens = [4,5];
$screens = App\Screen::whereIn('id', $id_screens)->get();
$screens->medias()->detach();
You can mass detach the relationship by querying on pivot table. It'll reduces number of queries you do in the loop. In your case, you can use below code for example.
$screens_id = App\Screen::whereIn('id', $id_screens)
->select('id')
->get()
->pluck('id');
\DB::table('Media_Screen')->whereIn('id_screen', $screens_id)->delete();
You may try "high order messages" as documented here:
$id_screens = [4,5];
$screens = App\Screen::find($id_screens);
$screens->each->medias()->detach();

Load relationships before foreach loop

I have a User model that does have many Networks. Each Network does have many Lists.
I have this code in the User model that is really slow:
foreach ($this->networks as $network) {
if ($myCondition) {
foreach ($network->lists()->get()->lists('id')->toArray() as $newId) {
$ids[] = $newId;
}
}
}
I wonder if there is a way to load all lists of all networks before the 2 foreach loops.
This may speed it up, although I'm not certain:
$this->networks()->with('lists')->get()->pluck('lists')->flatten()->pluck('id')
Also if it isn't a many to many relationship you could consider putting user_id as a column in your 'lists' table if speed is really important
This is lazy eager loading:
$this->networks->load('lists');
That loaded the lists relationship for all the networks.
If you just want this list of ids and don't need the actual records returned you can do that as well:
$ids = Lists::whereHas('network', function ($query) {
$query->whereIn('id', $this->networks->pluck('id'));
})->pluck('id');
If you want because Lists belongsTo Network you can go around the relationship:
$ids = Lists::whereIn('network_id', $this->networks->pluck('id'))
->pluck('id');
Just keep code in User model. In controller, you can do:
$User->load('networks', function($q){
$q->with('lists');
});
There in:
'networks' and 'lists' is your relationship name.
Further:
You can lazy load all your relation ship, in child by child.
$User->load('networks', function($q){
$q->with('lists', function($q1){
$q1->with('child_relationship_of_lists');
});
});

Laravel Where In on Where Has

Is there any way to return a parent model with a relationship but only return some of the relationship rows by using a where in?
That may be quite confusing, let me explain.
At the moment I have 2 models, Buildings and rooms, 1 building can have many rooms.
I want to be able to pass in an array of room ids, and return the sites and only the rooms that are in the array.
Heres what I have at the moment
if($request->input('ids') && !is_null($request->input('ids'))){
$ids = explode(',',$request->input('ids'));
//Exploded ids looks like this "2,4,11,55,56"
$buildings = Buildings::join('rooms')->whereIn('rooms.id',$ids)->get();
} else {
$buildings = Buildings::whereHas('rooms')->get();
}
At the moment this will return all buildings that have a room which id is in the ids array and all of its rooms, which ends up returning a building with 200+ rooms. I need it to return the building and ONLY the rooms that have an id in that array.
Is this possible?
I know I can do it the inverse way and get all rooms as the parent then get the buildings, but I need buildings to be the parent as i'm running a foreach like this with the results
foreach($buildings as $key => $building){
<h1>{{$building->name}}</h1>
foreach($building->rooms as $k => $room){
<p>{{$room->name}}</p>
}
}
Incase thats still confusing, the real world scenario is that i'm generating a PDF of rooms. The rooms can be selected by ticking a checkbox next to the room in a room list. I then need to be able to pass the array of room ids, and get all buildings that contain one of the rooms. Then get all of the rooms for each building where the room id is in the array.
First you need to know, whereHas only filter your parent result but not the eager loading relation. So you need to apply that filter in eager load too. Like this
$ids = explode(',',$request->input('ids'));
$buildings = Buildings::with(['rooms' => function($q) use ($ids) {
$q->whereIn('id', $ids);
}])->whereHas('rooms', function($q) use ($ids) {
$q->whereIn('id', $ids);
})->get();
Here whereHas filter buildings and using with filter rooms.
$ids = explode(',',$request->input('ids'));
$building_ids = Room::whereIn('id',$ids)->pluck('building_id');
$buildings_with_specific_rooms = Building::join('rooms', 'buildings.id', '=', 'rooms.building_id')->select('buildings.name', 'rooms.name')->whereIn('buildings.id', $building_ids)->whereIn('rooms.id', $ids)->get();
hope this helps you.
you can do this with the following code:
$ids = explode(',',$request->input('ids'));
$buildings = Buildings::whereHas('rooms', function($q) use ($ids) {
$q->whereIn('id', $ids);
})->get();
Hope this helps.
You can eager load the child relation and get the buildings out of the rooms collection:
$buildings = Room::with('building')
->with('building.room')
->whereIn('id', $ids)
->get()
->pluck('building');
For this to work you need to have the relation declared in both Building and Room models.

How to Merge Two Eloquent Collections?

I have a questions table and a tags table. I want to fetch all questions from tags of a given question. So, for example, I may have the tags "Travel," "Trains" and "Culture" attached to a given question. I want to be able to fetch all questions for those three tags. The tricky, so it seems, is that questions and tags relationship is a many-to-many defined in Eloquent as belongsToMany.
I thought about trying to merge the questions Collections as below:
foreach ($question->tags as $tag) {
if (!isset($related)) {
$related = $tag->questions;
} else {
$related->merge($tag->questions);
}
}
It doesn't seem to work though. Doesn't seem to merge anything. Am I attempting this correctly? Also, is there perhaps a better way to fetch a row of rows in a many-to-many relationship in Eloquent?
The merge method returns the merged collection, it doesn't mutate the original collection, thus you need to do the following
$original = new Collection(['foo']);
$latest = new Collection(['bar']);
$merged = $original->merge($latest); // Contains foo and bar.
Applying the example to your code
$related = new Collection();
foreach ($question->tags as $tag)
{
$related = $related->merge($tag->questions);
}
The merge() method on the Collection does not modify the collection on which it was called. It returns a new collection with the new data merged in. You would need:
$related = $related->merge($tag->questions);
However, I think you're tackling the problem from the wrong angle.
Since you're looking for questions that meet a certain criteria, it would probably be easier to query in that manner. The has() and whereHas() methods are used to generate a query based on the existence of a related record.
If you were just looking for questions that have any tag, you would use the has() method. Since you're looking for questions with a specific tag, you would use the whereHas() to add the condition.
So, if you want all the questions that have at least one tag with either 'Travel', 'Trains', or 'Culture', your query would look like:
$questions = Question::whereHas('tags', function($q) {
$q->whereIn('name', ['Travel', 'Trains', 'Culture']);
})->get();
If you wanted all questions that had all three of those tags, your query would look like:
$questions = Question::whereHas('tags', function($q) {
$q->where('name', 'Travel');
})->whereHas('tags', function($q) {
$q->where('name', 'Trains');
})->whereHas('tags', function($q) {
$q->where('name', 'Culture');
})->get();
$users = User::all();
$associates = Associate::all();
$userAndAssociate = $users->merge($associates);
Merge two different eloquent collections into one and some objects happen to have the same id, one will overwrite the other. Use push() method instead or rethink your approach to the problem to avoid that.
Refer to web
Creating a new base collection for each eloquent collection the merge works for me.
$foo = collect(Foo::all());
$bar = collect(Bar::all());
$merged = $foo->merge($bar);
In this case don't have conflits by its primary keys.
I have faced some issue by using merge. So I used concat. You can used it like below.
$users = User::all();
$associates = Associate::all();
$userAndAssociate = $users->concat($associates);
All do not work for me on eloquent collections, laravel eloquent collections use the key from the items I think which causes merging issues, you need to get the first collection back as an array, put that into a fresh collection and then push the others into the new collection;
public function getFixturesAttribute()
{
$fixtures = collect( $this->homeFixtures->all() );
$this->awayFixtures->each( function( $fixture ) use ( $fixtures ) {
$fixtures->push( $fixture );
});
return $fixtures;
}
I'm sorry about that, but since PHP 7.4 you're available to do like this (better use merge).
$foo = Foo::all();
$bar = Bar::all();
/** $foo will contain $foo + $bar */
$foo->push(...$bar);
I would like to add that, i found that the concat method does not seem to override based on ID, while the merge method does. concat seems to work for me, while merge caused issues.

Select from two tables Laravel 5

I use Laravels many to many relations. I have 2 tables projects and groups and pivot table project_group
Now I can do something like this:
$groups = \App\Project::findOrFail(Auth::user() -> id)->groups()->where('adminid','=',Auth::user()->id)->get();
It will return just Groups...Like this:
Design
SEO
But I need to return like this:
Design(Project2,Project3)
SEO(Porject1)
So for each loop I need to get group and all project linked to that group.
Here is my relation into Project modul:
public function groups(){
return $this ->belongsToMany('App\Group','project_group')->withPivot('admin_id');
}
You can define the inverse of the relationship in your Group model.
public function projects(){
return $this->belongsToMany('App\Project','project_group')->withPivot('admin_id');
}
And then, use it in your eloquent query.
$groups = Group::with('projects')->get();
You'll be able to loop through your groups and in each group get all projects.
foreach($groups as $group) {
foreach($group->projects as $project){
//your project
}
}
I don't really understand the usage of Auth::user() -> id but if you only want project where the current user is admin you can use a condition in the with function.
$groups = Group::with(['projects' => function($query) {
$query->where('adminid', Auth::user()->id)
}])->get();

Categories