Looking for some advice on creating an eloquent query.
I have 3 tables
Projects
Tasks
UserTasks
A project has tasks, and in usertasks we save the task and user id so we know it has been completed by the user. Now I need to query all projects that have been completed by the user, so projects that have tasks where all tasks for that project have been completed.
I can't seem to wrap my head around it, I am trying to create a scope for this.
public function scopeisCompleted($query)
{
$query = $query->whereHas('userTasks', function ($query) {
$query->where('user_id', '=', Auth::user()->id);
})->get();
return $query;
}
I can't seem to get my head around how to make sure all tasks for that project are completed by the user.
Any advice on moving forward?
I think you'd need something like this:
public function scopeisCompleted($query)
{
$query = $query->whereHas('userTasks', function ($query) {
$query->where('user_id', '=', Auth::user()->id)
->whereHas('task', function ($query) {
$query->where('completed', 1);
});
})->get();
return $query;
}
So as long as the task relationship is setup for the UserTask model and the column is completed, if not change it to the correct one.
I just read your question in more detail, if you need to check that all tasks are completed then that would be a bit more complicated, the query would need to find how many tasks there are and if that number matches the total completed tasks.
Update
I think I understand a bit more now on what you're trying to do:
public function scopeisCompleted($query)
{
$query = $query->with('tasks', 'userTasks')->whereHas('userTasks', function ($query) {
$query->where('user_id', '=', Auth::user()->id);
})->get();
return $query;
}
as mentioned in the comment, I'm not sure what model this scope is being used on so this might still be incorrect, but here I've added the ->with('tasks') so that once the query results have been returned, the tasks for the project can be compared to the user completed tasks for the project, although it should still be possible to do this in the query rather than in PHP.
if (count($results->tasks)) == count($results->userTasks)) {
// complete
}
I will need to think about it later, I might have the wrong syntax for what you're trying to do and the with might need to be something like ->with('tasks.userTasks').
Related
I am working within a controller in a Laravel application. I am returning a table to the view. The table is based on my PlanSubmission model. I am receiving parameters through a GET request and using those parameters to return a filtered set of rows to my view.
The first part of my controller looks like this and is working fine:
public function index()
{
//Used for filter. The request is received in the URL
if (request()->has('status')) {
$plans = PlanSubmission::where('status', request('status'))->paginate(25)->appends('status', request('status'));
}
elseif (request('employer_name')) {
$plans = PlanSubmission::where('employer_name', request('employer_name'))->paginate(25)->appends('employer_name', request('employer_name'));
}
I have run into a problem because now I need to use a model relationship in the controller. I am receiving 'advisor_name' from the request. The 'advisor_id" column is the foreign key on the PlanSubmission model. The 'advisor_name' column exists in the Advisor model. I have a function on my PlanSubmission model that looks like this:
public function advisor()
{
return $this->belongsTo(Advisor::class);
}
Initially, I thought there was a way I could do this easily with something like:
$plans = PlanSubmission::where(advisor->name, request('advisor_name'))->paginate(25)->appends('advisor_name', request('advisor_name'));
Of course, this will not work because I cannot enter a relationship into the first parameter in the Where Clause.
I do not know where to go from here. My other thought is to return all the advisors first from the Advisor model like this:
$advisors = Advisor::where('name', request('advisor_name'));
Then, I imagine I would have to somehow loop through that and get the id (primary key) for each of the objects in $advisors and somehow get that into the PlanSubmission where clause. I'm totally lost.
Like Victor mentions in his answer you can use whereHas like so:
PlanSubmission::whereHas('advisor', function ($query) {
$query->where('name', request('advisor_name'));
});
You didn't asked this directly, but I noticed that you use conditionals to make different queries. Eloquent provides a few way to make this a bit nicer to deal with.
The first which is kind of obvious is that that whatever method you call a builder (query) is returned that you can just add on to. It could be there were some common restrictions in your two cases:
public function index()
{
$query = PlanSubmission::where('something', 42);
if (request()->has('status')) {
$query = $query->where('status', request('status'));
} elseif (..) {
...
}
return $query->paginate(25);
}
Another way to do conditional queries in Laravel is using when. E.g. for status:
$query = $query->when(request->has('status'), function ($query) {
// note that you don't have to return the query
$query->where('status', request('status'));
});
// or PlanSubmission::>when(..)
In your example you cannot both filter by status AND advisor_name, but lets assume that would be okay, then you can combine everything like so:
public function index()
{
return PlanSubmission::query()
//->where('something', 42)
->when(request->has('status'), function ($query) {
$query->where('status', request('status'));
})
->when(request->has('advisor_name'), function ($query) {
$query->whereHas('advisor', function ($query) {
$query->where('name', request('advisor_name'));
});
})->paginate(25);
}
This approach may seem verbose for simple queries and then it is fine to use if conditions, but for complex queries when can be useful. Also the idea of "building up a query" also works nice in those situation. You can pass the query builder around and continuously build it up.
You can use whereHas for that
docs
I've 2 models, CoreChallenge and Challenge. Below are relations between table.
I want to fetch Challenges which has core_challenges active. I tried to do putting global scope in CoreChallenge model, but when I'm getting null in relationship when Corechallenge is inactive.
I've done it this way
$challenges = Challenge::with('core_challenge')->whereHas('core_challenge', function($q){
$q->where('status', '=', 'active');
})->get();
I want to do it using global scopes
Global scope on CoreChallenge gives me null, but I want that it's parent (Challenge) should not load even, like in whereHas. Is there any way?
I have stumbled to the same approach, but when the table got bigger (core_challenges_table in your scenario), whereHas ended up being very slow (around 1min response time).
So I used a solution like this:
$ids = CoreChallenge::where('status', 'active')->pluck('id');
$challenges = Challenge::with('core_challenges')
->whereIn('core_challenge_id', $ids)
->get();
With this approach, my query reduced to 600~ms from 1min.
Which can be translated to Model scopes
class Challenge {
public function scopeActive($query) {
$activeIds = CoreChallenge::where('status', 'active')->pluck('id');
return $query->whereIn('core_challenge_id', $ids);
}
}
Challenge::with('core_challenges')->active()->get();
I'm building a Laravel page on which I want to show a list of lessons. Which lessons should be on the page is filtered by three criteria (of which all should be true):
The lesson is active, ie "where('active', true)". Simple enough.
The lesson is part of a track that the user has chosen. Models are set up with belongsToMany() (it is a many-to-many relationship), so I can get these lessons by a simple $track->lessons.
This is where it gets tricky. Some lessons should only be visible to users with certain titles (ie there is a many to many between titles and lessons). I can get the lessons with the correct title requirement using Auth::user()->title->lessons.
Question is how I get all this together. The best I've come up with this far is the following:
$title = Auth::user()->title;
$lessons = Lesson::where('active', true)
->whereIn('id', $track->lessons->pluck('id'))
->where(function ($query) use($title) {
$query->whereIn('id', $title->lessons->pluck('id'))->orWhere('limited_by_title', false);
})
->get();
...which is crap ugly, clearly suboptimal and (for some reason I really don't understand) also won't work (I don't get the lessons my title entitles me to in my list). Been struggling for quite some hours now, I get the feeling that I'm overcomplicating, first plucking id's and then using them in a whereIn() can't possibly be a good way of doing this.
So I can easily enough get a collection of lessons in the track, and I can get a collection of lessons belonging to the title, but how do I get all objects that exist in both those collections?
Using whereHas() is the answer to your concerns about plucking IDs. Instead of running additional queries to retrieve IDs, whereHas() will attach the constraint to the original query as a subquery on the related tables.
Breaking the query down to its parts:
1: Answered
2: Assuming the inverse of $track->lessons is $lesson->tracks, and $track is coming from code you didn't include:
$lessons = Lesson::whereHas('tracks', function ($query) use ($track) {
$query->where('id', $track->id);
})
3: Assuming the inverse of $title->lessons is $lesson->titles:
$lessons = Lesson::where(function ($query) use ($title) {
$query->whereHas('titles', function ($query) use ($title) {
$query->where('id', $title->id);
})
->orWhere('limited_by_title', false);
})
Combined back into one:
$track = ???;
$title = Auth::user()->title;
$lessons = Lesson::where('active', true)
->whereHas('tracks', function ($query) use ($track) {
$query->where('id', $track->id);
})
->where(function ($query) use ($title) {
$query->whereHas('titles', function ($query) use ($title) {
$query->where('id', $title->id);
})
->orWhere('limited_by_title', false);
})
->get();
If this still doesn't give the results you were expecting, you can examine the full query being run by replacing get() with toSql(). Sometimes working from the ORM as a starting point instead of the SQL can lead you down the wrong path. For even more detail to debug and understand the queries being run, you can enable query logging: https://laravel.com/docs/5.7/database#listening-for-query-events
intead of where use whereHas on "titles" relationship
$title = Auth::user()->title;
$lessons = Lesson::where('active', true)
->whereIn('id', $track->lessons->pluck('id'))
->whereHas('titles',function ($query) use($title) {
$query->whereIn('id', $title->pluck('id'))
->orWhere('limited_by_title', false);
})->get();
First of all complicated, really complicated. Your table structure needs serious modification to make it easier.
However, considering you don't want to go down that road, you could do it simpler by using join
Assuming you have following table structure:
users
titles (has user_id foreign key)
lessons (has title_id foreign key)
tracks (has lesson_id foreign key)
$trackName = $request->input('track_name');
$title = Auth::user()->title;
$lessons = Lesson::join('tracks', 'lessons.id', '=', 'tracks.lesson_id')
->join('titles', 'lessons.title_id', '=', 'titles.id')
->where('lessons.active', true)
->where('tracks.track_name', $trackName)
->where(function ($query) use($title) {
$query->where('titles.id', $title->id)->orWhere('lessons.limited_by_title', false);
});
dd($lessons);
That is of course if your users table and titles have one to one relationship otherwise pluck all title_ids and use whereIn instead of where for titles.id query.
I hope you have enough understanding of laravel framework to understand and implement this solution.
Sorry, I don't have enough time to proofread or give more details.
Good luck!
Good luck if you need pagination after that :p I doubt simple ->paginate() will work :D
I hope it helps
Im trying to make a query using whereHas with eloquent. The query is like this:
$projects = Project::whereHas('investments', function($q) {
$q->where('status','=','paid');
})
->with('investments')
->get();
Im using Laravel 5.2 using a Postgres driver.
The Project model is:
public function investments()
{
return $this->hasMany('App\Investment');
}
The investments model has:
public function project() {
return $this->belongsTo('App\Project');
}
The projects table has fields id,fields...
The investments table has the fields id,project_id,status,created_at
My issue is that the query runs and returns a collection of the projects which have at least one investment, however the where clause inside the whereHas is ignored, because the resulting collection includes investments with status values different than paid.
Does anyone has any idea of what is going on?
I believe this is what you need
$projects = Project::whereHas('investments', function($q) {
$q->where('status','=','paid');
})->with(['investments' => function($q) {
$q->where('status','=','paid');
}])->get();
whereHas wil check all projects that have paid investments, with will eagerload all those investments.
You're confusing whereHas and with.
The with method will let you load the relationship only if the query returns true.
The whereHas method will let you get only the models which have the relationship which returns true to the query.
So you need to only use with and not mix with with whereHas:
$projects = Project::with(['investments' =>
function($query){ $query->where('status','=','paid'); }])
->get();
Try like this:
$projects = Project::with('investments')->whereHas('investments', function($q) {
$q->where('status','like','paid'); //strings are compared with wildcards.
})
->get();
Change the order. Use with() before the whereHas(). I had a similar problem few weeks ago. Btw, is the only real difference between the problem and the functional example that you made.
I have 2 tables, Places and Users. Places can have many Users.
// in Place.php
public function users()
{
return $this->hasMany('User');
}
//in User.php
public function place()
{
return $this->belongsTo('Place');
}
and am trying to get only the Places that have at least one active and enabled User associated through a static method Place::locationsWithPeople().
public static function locationsWithPeople()
{
return Place::with(array('users' => function($query)
{
$query->where('enabled', '=', 1)->where('active', '=', 1);
}))->get();
}
This yields an HTTP 500. Removing the two wheres does not help.
This works, but of course it does not contain the two wheres:
return Place::has('users')->get();
Anyone can help? It seems totally analogous to the example in Laravel's documentation.
This works for me.
$place = new Place;
$array = $place->with(array('users' => function($query)
{
$query->where('enabled', 1)->where('active', 1);
}))->get();
var_dump($array->toArray());
Maybe because you are trying to use non-static methods statically?
It seems that the problem was caused by excessive resource consumption, leading the page to HTTP500 over resource exhaustion. Fixed by rewriting it through join statements.
Can you try this?
return Place::with(array('users' => function($query)
{
$query->where('enabled', '=', 1)->where('active', '=', 1);
}))->hasWith('users')->get();
Method hasWithis a scope that written by me. You can get scopeHasWith() from http://paste.laravel.com/13jb. This will work same as has() method but it will call eagerload closure before attach WHERE to query builder.
I think this can resolve your problem that used resource too much and you can WHERE them from eagerload closure. I had same problem as you that I want to query with relation but it send all records that match WHEREIN too and cause memory problem.