I have 2 Laravel models, Classes and Subjects. Each Class belongs to a Subject.
I'd like to do a query, which sorts and returns the classes depending on whether their subject's ID is within a given array of IDs.
E.g. I can use the code below successfully to get the list of classes, which have the relevant subject IDs.
However, I can't figure out how to get ALL classes (i.e. no filter), and just sort so that the classes which have the relevant subjects are first, followed by other classes.
One approach is to create 2 different Collections, and ->merge them, but that then gets tricky when trying to paginate.
Thanks for your help!
Classes::whereHas('subject',function($q) use ($subject_list) {
$q->whereIn('id', $subject_list);
})->get();
You could perform a leftJoin to get all classes (i.e. no filter), and just sort so that the classes which have the relevant subjects are first
Classes::leftJoin('subjects', function ($join) use ($subject_list) {
$join->on('classes.subject_id', '=', 'subjects.id')
->whereIn('subjects.id', $subject_list);
})
->orderBy('classes.subject_id')
->get();
whereHas() will not help you here. You can use sortByDesc():
$classes = Classes::all();
$classes->sortByDesc(function($i) use($subject_list) {
return in_array($i->subject_id, $subject_list);
});
Or you could execute two queries and merge results:
$classes = Classes::whereIn('subject_id', $subject_list)->get();
$classesNotIn = Classes::whereNotIn('subject_id', $subject_list)->get();
$classes->merge($classesNotIn);
I've tested both approaches and they work, but you'll need to create Paginator manually in both cases.
Related
I'm working on a project where I have the following models and relationships:
The project have many scenarios(one to many) and the scenarios can have multiple OP Tags(many to many).
I display all the projects on an index page and I have a new request to display all the OP Tags that are part of a project. The unique ones(and in reverse order) because multiple scenarios from the same project can have a relationship with the same OP Tag.
So I wrote this method:
$opTags = collect();
$project->scenarios->each(function ($scenario) use ($opTags) {
$scenario->opTags->each(static function ($opTag) use ($opTags) {
$opTags = $opTags->push($opTag);
});
});
$opTags = $opTags->unique('name')->reverse();
But I was thinking that maybe there is a more efficient method? Thank you!
What you are currently using is in fact inefficient as it will create an n + 1 query problem, unless the relations are already eager loaded. So adding something like this above what you wrote should solve that.
$project->load('scenarios.opTags,name');
Also you could write this the other way around like so:
$opTags = OpTag::query()
->whereHas('scenarios', function (Builder $query) use ($project) {
$query->whereRelation('project', 'id', $project->id);
})
->select('name')
->distinct()
->get();
I'm trying to figure out how these nestedsets in Laravel works. I've an many to many relation between organizations and departments. An organization can have many departments. An department can have many departments. For this I'm using Nestedsets.
What I'm trying to do, is retrieving all organizations from a user. On this query I'd like to retrieve all departments attached to these organizations. I'd like the structure, so I've a infinite parent -> child relation on my departments, so I'm able to build a structuretree using treant.js.
I'm pretty sure I've everything build correctly in my database, so my first thought were to use with. However it seems like I'm only getting the first children. Here is an example:
$currentUser->organizations()->with(
'departments.children',
'departments.commodities',
'departments.children.commodities',
)->get()
I've to include children.[model] for every nested department. So if I've two levels, I've to add departments.children.children.commodities, and so on. This seems pretty retarded!
I've been trying pretty many different approches to get a proper solution, but the one below is my best solution for now. I just feel like I'm using the nestedset-library wrong.
public function getUserDepartmentTree() {
foreach ( $this->organizations()->get() as $organization ) {
$dep[] = $organization->departments()->get()->toTree();
}
return $dep;
}
So my question is, how should I get all relational data from my departments tree-structured?
For this you'll want to use descendants instead of children as children will only return the direct/first child models whereas descendants will return everything under a certain node.
Since this will add the relationship as descendants instead of children you'll need to tweak it slightly i.e. change the name of the relationship and then use the toTree() method:
$organizations = $currentUser->organizations()
->with('departments.commodities', 'departments.descendants.commodities')
->get()
->map(function ($organization) {
$organization->departments->map(function ($department) {
return $department->setRelation('children', $department->descendants->toTree())->unsetRelation('descendants');
});
return $organization;
});
I am fairly new to laravel and I built a little "similar posts" section. So every post has a tag and I query all the id's from the current tag. And then I find all the posts with thoses id's. Now my problem is that the current post is always included. Is there an easy way to exclude the current id when querying?
I can't seem to find anything in the helper function on the laravel docs site
this is my function:
public function show($id)
{
$project = Project::findOrFail($id);
foreach ($project->tags as $tag){
$theTag = $tag->name;
}
$tag_ids = DB::table('tags')
->where('name', "=", $theTag)
->value('id');
$similarProjects = Tag::find($tag_ids)->projects;
return view('projects.show', ['project' => $project, 'similarProjects' => $similarProjects]);
}
An easy way to solve your issue would be to use the Relationship method directly instead of referring to it by property, which you can add additional filters just like any eloquent transaction.
In other words, you would need to replace this:
Tag::find($tag_ids)->projects
With this:
Tag::find($tag_ids)->projects()->where('id', '!=', $id)->get()
Where $id is the current project's id. The reason behind this is that by using the method projects(), you are referring your model's defined Relationship directly (most probably a BelongsToMany, judging by your code) which can be used as a Query Builder (just as any model instance extending laravel's own Eloquent\Model).
You can find more information about laravel relationships and how the Query Builder works here:
https://laravel.com/docs/5.1/eloquent-relationships
https://laravel.com/docs/5.1/queries
However, the way you are handling it might cause some issues along the way.
From your code i can assume that the relationship between Project and Tag is a many to many relationship, which can cause duplicate results for projects sharing more than 1 tag (just as stated by user Ohgodwhy).
In this type of cases is better to use laravel's whereHas() method, which lets you filter your results based on a condition from your model's relation directly (you can find more info on how it works on the link i provided for eloquent-relationships). You would have to do the following:
// Array containing the current post tags
$tagIds = [...];
// Fetch all Projects that have tags corresponding to the defined array
Project::whereHas('tags', function($query) use ($tagIds) {
$query->whereIn('id', $tagIds);
})->where('id', !=, $postId)->get();
That way you can exclude your current Project while avoiding any duplicates in your result.
I don't think that Tag::find($tag_ids)->projects is a good way to go about this. The reason being is that multiple tags may belong to a project and you will end up getting back tons of project queries that are duplicates, resulting in poor performance.
Instead, you should be finding all projects that are not the existing project. That's easy.
$related_projects = Project::whereNotIn('id', [$project->id])->with('tags')->get();
Also you could improve your code by using Dependency Injection and Route Model Binding to ensure that the Model is provided to you automagically, instead of querying for it yourself.
public function show(Project $project)
Then change your route to something like this (replacing your controller name with whatever your controller is:
Route::get('/projects/{project}', 'ProjectController#show');
Now your $project will always be available within the show function and you only need to include tags (which was performed in the "with" statement above)
This may be a dupe but I've been trawling for some time looking for a proper answer to this and haven't found one yet.
So essentially all I want to do is join two tables and attach a where condition to the entire collection based on a field from the joined table.
So lets say I have two tables:
users:
-id
-name
-email
-password
-etc
user_addresses:
-address_line1
-address_line2
-town
-city
-etc
For the sake of argument (realising this may not be the best example) - lets assume a user can have multiple address entries. Now, laravel/eloquent gives us a nice way of wrapping up conditions on a collection in the form of scopes, so we'll use one of them to define the filter.
So, if I want to get all the users with an address in smallville, I may create a scope and relationships as follows:
Users.php (model)
class users extends Eloquent{
public function addresses(){
return $this->belongsToMany('Address');
}
public function scopeSmallvilleResidents($query){
return $query->join('user_addresses', function($join) {
$join->on('user.id', '=', 'user_addresses.user_id');
})->where('user_addresses.town', '=', 'Smallville');
}
}
This works but its a bit ugly and it messes up my eloquent objects, since I no longer have a nice dynamic attribute containing users addresses, everything is just crammed into the user object.
I have tried various other things to get this to work, for example using a closure on the relationship looked promising:
//this just filters at the point of attaching the relationship so will display all users but only pull in the address where it matches
User::with(array('Addresses' => function($query){
$query->where('town', '=', 'Smallville');
}));
//This doesnt work at all
User::with('Addresses')->where('user_addresses.town', '=', 'Smallville');
So is there an 'Eloquent' way of applying where clauses to relationships in a way that filters the main collection and keeps my eloquent objects in tact? Or have I like so many others been spoiled by the elegant syntax of Eloquent to the point where I'm asking too much?
Note: I am aware that you can usually get round this by defining relationships in the other direction (e.g. accessing the address table first) but this is not always ideal and not what i am asking.
Thanks in advance for any help.
At this point, there is no means by which you can filter primary model based on a constraint in the related models.
That means, you can't get only Users who have user_address.town = 'Smallwille' in one swipe.
Personally I hope that this will get implemented soon because I can see a lot of people asking for it (including myself here).
The current workaround is messy, but it works:
$products = array();
$categories = Category::where('type', 'fruit')->get();
foreach($categories as $category)
{
$products = array_merge($products, $category->products);
}
return $products;
As stated in the question there is a way to filter the adresses first and then use eager loading to load the related users object. As so:
$addressFilter = Addresses::with('Users')->where('town', $keyword)->first();
$users= $addressFilter->users;
of course bind with belongsTo in the model.
///* And in case anyone reading wants to also use pre-filtered Users data you can pass a closure to the 'with'
$usersFilter = Addresses::with(array('Users' => function($query) use ($keyword){
$query->where('somefield', $keyword);
}))->where('town', $keyword)->first();
$myUsers = $usersFilter->users;
Using Laravel 4 I have the following models and relations: Event which hasMany Record which hasMany Item. What I would like to do is something like this
Item::where('events.event_type_id', 2)->paginate(50);
This of cause doesn't work as Eloquent doesn't JOIN the models together when retrieving the records. So how do I go about this without just writing the SQL myself (which I would like to avoid as I want to use pagination).
What you want is eager loading.
It works like this if you want to specify additional constraints:
Item::with(array('events' => function($query) {
return $query->where('event_type_id', 2);
}))->paginate(50);
There is a pull request pending here https://github.com/laravel/framework/pull/1951.
This will allow you to use a constraint on the has() method, something like this:
$results = Foo::has(array('bars' => function($query)
{
$query->where('title', 'LIKE', '%baz%');
}))
->with('bars')
->get();
The idea being you only return Foos that have related Bars that contain the string 'baz' in its title column.
It's also discussed here: https://github.com/laravel/framework/issues/1166. Hopefully it will be merged in soon. Works fine for me when I update my local copy of the Builder class with the updated code in the pull request.