Sort collection by relationship value - laravel

I want to sort a laravel collection by an attribute of an nested relationship.
So I query all projects (only where the project has tasks related to the current user), and then I want to sort the projects by deadline date of the task relationship.
Current code:
Project.php
public function tasks()
{
return $this->hasMany('App\Models\ProjectTask');
}
Task.php
public function project()
{
return $this->belongsTo('App\Models\Project');
}
UserController
$projects = Project->whereHas('tasks', function($query){
$query->where('user_id', Auth::user()->id);
})->get()->sortBy(function($project){
return $project->tasks()->orderby('deadline')->first();
});
I don't know if im even in the right direction?
Any advice is appreciated!

A nice clean way of doing this is with the . operator
$projects = Project::all()->load('tasks')->sortBy('tasks.deadline');

I think you need to use something like join() and then sort by whatever you need.
For exapmle:
Project::join('tasks', 'tasks.project_id', '=', 'projects.id')
->select('projects.*', DB::raw("MAX(tasks.deadline) as deadline_date"))
->groupBy('tasks.project_id')
->orderBy('deadline_date')
->get()
Update
Project::join('tasks', function ($join) {
$join->on('tasks.project_id', '=', 'projects.id')
->where('tasks.user_id', Auth::user()->id)
->whereNull('tasks.completed');
})
->select('projects.*', DB::raw("MAX(tasks.deadline) as deadline_date"))
->groupBy('tasks.project_id')
->orderBy('deadline_date')
->get()
Update2
Add with in your query as:
->with(['tasks' => function ($q) {
$q->where('user_id', Auth::user()->id)
->whereNull('completed');
})

Try This,
$tasks = Task::with('project')
->where('user_id',Auth::user()->id)
->orderBy('deadline')
->get();
After that you can access project properties like below
$tasks->first()->project()->xxx

Related

Laravel eloquent search query for two foreign tables

i have a question about the code above , i want to search with criteria from 2 extra tables
I want 'charge' from charges table , 'name' from user table and also 'name' from customer table
all has binds the above query runs but the doesnt fetch data from customers->name any idea how to make it work?
public function scopeSearch($query, $val){
return $query->has('customer')
->whereHas('user', function($query) use ($val) {
$query->where('tasks','like','%'.$val.'%')
->Orwhere('name','like','%'.$val.'%');
})
->with('user')
->with('customer');
}
You can follow the documentation about adding stuff to your relation query (whereHas).
So, you should have this:
public function scopeSearch($query, $val){
return $query->with(['user', 'customer'])
->whereHas('user', function($query) use ($val) {
$query->where('tasks','like','%'.$val.'%')
->Orwhere('name','like','%'.$val.'%');
})
->whereHas('customer', function($query) use ($val) {
$query->where('name','like','%'.$val.'%');
});
}
See that you had only used whereHas for users but not customers...
Finally worked! after some research i found that the problem was my using of or statement
the code above works for me:
public function scopeSearch($query, $val){
return $query->whereHas('user', function($query) use ($val) {
$query->where('tasks','like','%'.$val.'%')
->orWhere('name','like','%'.$val.'%');
})
->orWhereHas('customer', function($query) use ($val) {
$query->where('name', 'LIKE', '%'.$val.'%');
})
->with('user', 'customer');
}

Add Parameter to Laravel Eloquent relationship in "with" function

I would like to add a parameter to my Eloquent relationship in the with:: static method.
I have a Dealer Model that has those relationships
public function events()
{
return $this->hasMany('App\Event', 'dealer_id', 'dealer_id');
}
public function recentEvents($interval = 14)
{
return $this->events()
->whereDate('datestart', '>=', Carbon::now('America/Toronto')->subDays($interval))
->whereDate('dateend', '>=', Carbon::now('America/Toronto'));
}
Everything works fine if I call it like this $dealer->recentEvents(20) but in my DealerController
I would like to do something in the line of this
Dealer::with("recentEvents")
->where('dealer_flag', 1)
->orderBy('dealer_storename', 'ASC')
->get();
Where i would pass a paremeter to "recentEvents".
I've tried putting a closure function to with like this
Dealer::with(["recentEvents" => function ($query) use ($interval){
//The problem here is that I would need to put the recent-events code like this
$query->whereDate('cal_datestart', '>=', Carbon::now('America/Toronto')->subDays($interval))
->whereDate('cal_dateend', '>=', Carbon::now('America/Toronto'));
}])
->where('dealer_flag', 1)
->orderBy('dealer_storename', 'ASC')
->get();
But it would defeat the purpose of having a recentEvents relationship.
Is it possible to just pass a parameter ?
I've tried using Dynamic Scope from Laravel doc but I figured it was not what I was looking for.
i think u should use like this
Dealer::with(["events",function($q) use($interval){
$q->whereDate('datestart', '>=', Carbon::now('America/Toronto')->subDays($interval))
->whereDate('dateend', '>=', Carbon::now('America/Toronto'));
}])
->where('dealer_flag', 1)
->orderBy('dealer_storename', 'ASC')
->get();

Joining 2 scopes laravel

I've been trying to solve this for quite a while now. I want to join these two scopes from my Match Model:
public function scopeMainMatches($query)
{
return $query->where('type', 'main');
}
public function scopeDotaMatches($query)
{
return $query->join('leagues', function ($join) {
$join->on('matches.league_id', '=', 'leagues.id')
->select('matches.*')
->where('leagues.type', '=', 'dota2')
->where('matches.type', '=', 'main');
});
}
so basically, when I put in into join eloquent relationship it will be the same like this:
$query = DB::table('matches')
->join('leagues', 'leagues.id', '=', 'matches.league_id')
->select('matches.*')
->where('leagues.type', '=', 'dota2')
->get();
it works fine during the terminal check. but I need to connect 2 scopes for the Controller which looks like this:
$_matches = \App\Match::mainMatches()
->get()
->load('teamA', 'teamB')
->sortByDesc('schedule');
so when I try to connect mainMatches and dotaMatches, it doesn't show up on the matches. although when i run php artisan tinker, it returns the correct output, but it won't show up on the matches table.
$_matches = \App\Match::mainMatches()
->dotaMatches()
->get()
->load('teamA', 'teamB')
->sortByDesc('schedule');
any Ideas how to work on this? TYIA!
I've managed to join two tables in just one scope here is the code:
public function scopeMainMatches($query) {
return $query->join('leagues','leagues.id','=','matches.league_id')->select('matches.*')->where('matches.type', 'main');
}

Laravel Eloquent - Having trouble querying relationship with ownership

I want to get all categories and within those categories, only items that belong to the logged in user. If there are none, the category should still be there.
This is what I tried. Still getting items from other users.
$categories = Category::parents()
->with(['lineItems' => function ($query) use($id) {
$query->where('user_id', $id);
}])->get();
Haven't been able to find anything that works. (using laravel 5.7)
Relationships
Category
public function lineItems()
{
return $this->hasMany(LineItem::class);
}
LineItems
public function category()
{
return $this->belongsTo(Category::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
I'm not sure where the ::parents() method is coming from (I can't find documentation for it anywhere; is it something you wrote?), but it seems like
$categories = Category::all()
->with(['lineItems' => function ($query) use ($id) {
$query->where('user_id', $id);
}])->get();
might work?
So, Travis's question made me think about my loop. My with was affecting my top level categories which left its children's line items without the where user. There's probably a more elegant method of doing this but this is what ended up working.
This queries the child's line items and the grandchild's line items.
$categories = Category::parents()->ordered()
->with(['children.lineItems' => function($q) use($id) {
$q->where('user_id', '=', $id);
}, 'children.children.lineItems' => function($q) use($id) {
$q->where('user_id', '=', $id);
}])->get();
Try this:
$categories = Category::parents()
->whereHas('lineItems', function ($query) use($id) {
$query->where('user_id', $id);
})->get();

Where NOT in pivot table

In Laravel we can setup relationships like so:
class User {
public function items()
{
return $this->belongsToMany('Item');
}
}
Allowing us to to get all items in a pivot table for a user:
Auth::user()->items();
However what if I want to get the opposite of that. And get all items the user DOES NOT have yet. So NOT in the pivot table.
Is there a simple way to do this?
Looking at the source code of the class Illuminate\Database\Eloquent\Builder, we have two methods in Laravel that does this: whereDoesntHave (opposite of whereHas) and doesntHave (opposite of has)
// SELECT * FROM users WHERE ((SELECT count(*) FROM roles WHERE user.role_id = roles.id AND id = 1) < 1) AND ...
User::whereDoesntHave('Role', function ($query) use($id) {
$query->whereId($id);
})
->get();
this works correctly for me!
For simple "Where not exists relationship", use this:
User::doesntHave('Role')->get();
Sorry, do not understand English. I used the google translator.
For simplicity and symmetry you could create a new method in the User model:
// User model
public function availableItems()
{
$ids = \DB::table('item_user')->where('user_id', '=', $this->id)->lists('user_id');
return \Item::whereNotIn('id', $ids)->get();
}
To use call:
Auth::user()->availableItems();
It's not that simple but usually the most efficient way is to use a subquery.
$items = Item::whereNotIn('id', function ($query) use ($user_id)
{
$query->select('item_id')
->table('item_user')
->where('user_id', '=', $user_id);
})
->get();
If this was something I did often I would add it as a scope method to the Item model.
class Item extends Eloquent {
public function scopeWhereNotRelatedToUser($query, $user_id)
{
$query->whereNotIn('id', function ($query) use ($user_id)
{
$query->select('item_id')
->table('item_user')
->where('user_id', '=', $user_id);
});
}
}
Then use that later like this.
$items = Item::whereNotRelatedToUser($user_id)->get();
How about left join?
Assuming the tables are users, items and item_user find all items not associated with the user 123:
DB::table('items')->leftJoin(
'item_user', function ($join) {
$join->on('items.id', '=', 'item_user.item_id')
->where('item_user.user_id', '=', 123);
})
->whereNull('item_user.item_id')
->get();
this should work for you
$someuser = Auth::user();
$someusers_items = $someuser->related()->lists('item_id');
$all_items = Item::all()->lists('id');
$someuser_doesnt_have_items = array_diff($all_items, $someusers_items);
Ended up writing a scope for this like so:
public function scopeAvail($query)
{
return $query->join('item_user', 'items.id', '<>', 'item_user.item_id')->where('item_user.user_id', Auth::user()->id);
}
And then call:
Items::avail()->get();
Works for now, but a bit messy. Would like to see something with a keyword like not:
Auth::user()->itemsNot();
Basically Eloquent is running the above query anyway, except with a = instead of a <>.
Maybe you can use:
DB::table('users')
->whereExists(function($query)
{
$query->select(DB::raw(1))
->from('orders')
->whereRaw('orders.user_id = users.id');
})
->get();
Source: http://laravel.com/docs/4.2/queries#advanced-wheres
This code brings the items that have no relationship with the user.
$items = $this->item->whereDoesntHave('users')->get();

Resources