I'm totally aware of query scopes on Eloquent and it's working fine. My problem is a bit different, this time I have one query on the DB Query Builder that contains some conditions that are repeated on other models, so I want to extract and reuse these conditions. This is my original query:
$grants = $this->grants()
->where(function ($query) use ($date) {
$query->whereDate('grant_date', '<=', $date)
->where(function ($query) use ($date) {
$query->whereNull('canceled')
->whereNull('cost');
})
->orWhere(function ($query) use ($date) {
$query->whereDate('canceled', '>', $date);
});
})
->get();
So my idea is to create a scope, let's call ActiveGrants just for the sake of the example:
public function scopeActiveGrants($query, $date)
{
return $query->where(function ($query) use ($date) {
$query->whereDate('grant_date', '<=', $date)
->where(function ($query) use ($date) {
$query->whereNull('canceled')
->whereNull('cost');
})
->orWhere(function ($query) use ($date) {
$query->whereDate('canceled', '>', $date);
});
});
}
In order to reuse the scope like this:
$grants = $this->grants()
->grantsActive()
->get();
Just a note, $this->grants() is a relationship:
public function grants()
{
return $this->hasMany(Grant::class)
}
I know Scopes work fine with Eloquent but I couldn't make work with the DB Builder. Any ideas?
Thanks in advance.
I think that I was over-engineering this code refactoring trying to things on the "Laravel" way. Nothing wrong with that, I love the framework and I like to follow its suggestion if makes sense to me. And scopes are pretty cool in my opinion.
I solved this using a regular function to return the Closure that holds all the conditions that I need:
public function activeGrants($date): Closure
{
return function ($query) use ($date) {
$query->whereDate('grant_date', '<=', $date)
->where(function ($query) use ($date) {
$query->whereNull('cancel')
->whereNull('cost');
})
->orWhere(function ($query) use ($date) {
$query->whereDate('cancel', '>', $date);
});
};
}
And then:
$this->grants()
->where($this->grantsUpTo($date))
->get();
I'm satisfied with this solution, if someone has a better idea please share o/
Related
i am working on a snippet but i have to check another table if that customer data exists return the data if the customer data does not exist on the employment table also return the data based on the former check then ignore the rest but if it does i want to use the condition specified to check and return that data. Here's what i have tried.
Customers::query()
->distinct('id')
->whereHas('quote')
->where(
function ($query) {
$query->whereHas('profile', function ($query) {
$query->where(function ($query) {
$query->whereNull('nin')->WhereNull('bvn');
})->orWhere(function ($query) {
$query->whereNotNull('nin')->whereNotNull('bvn');
});
})->orWhereHas('employment', function ($query) {
$query->where(function ($query) {
$query->whereNull('bank_statement_url')->WhereNull('mono_account_statement_url');
})->orWhere(function ($query) {
$query->whereNotNull('bank_statement_url')->whereNotNull('mono_account_statement_url');
});
});
}
)->paginate(30);
I cleaned up your code a bit as well as changing your orWhereHas() to whereHas():
Customers::query()
->distinct('id')
->whereHas('quote')
->whereHas('profile', function ($query) {
$query->where(function ($query) {
$query->whereNull('nin')->WhereNull('bvn');
})->orWhere(function ($query) {
$query->whereNotNull('nin')->whereNotNull('bvn');
});
})->whereHas('employment', function ($query) {
$query->where(function ($query) {
$query->whereNull('bank_statement_url')
->whereNull('mono_account_statement_url');
})->orWhere(function ($query) {
$query->whereNotNull('bank_statement_url')
->whereNotNull('mono_account_statement_url');
});
}
)->paginate(30);
I am doing a filter using relations here is my controller code:
$userList = User::with(['role' ,'userMetaData','userBet','userComission','userPartnership'])
->where('deleted', '=', '0')
->when($request->parent_id, function ($builder, $parent_id) {
return $builder->where('parent_id', $parent_id);
})
->when($request->role_id, function ($builder, $role_id) {
return $builder->where('role', $role_id);
})
->when($request->to && $request->from , function ($builder) use ($request) {
return $builder->whereBetween('exposure_limit', [$request->from, $request->to]);
})
->when($request->city, function ($builder) use ($request) {
return $builder->whereHas('userMetaData', function($query) use ($request){
$query->where('city', $request->sport);
});
})
->orderBy('created_at', 'DESC')
->get();
In this case(in my code) i am trying to use whereHas() to filter a data from another table(within relations) by eloquent but it's something i am missing:
here in your case, when you use Wherehas to userMetaData it'll filter and gives you only those users who's has city as $request->sport..
In another case, you've used with('userMetaData') which means whatever users you got in filter return all userMetaData.
->when($request->city, function ($builder) use ($request) {
return $builder->whereHas('userMetaData', function($query) use ($request){
$query->where('city', $request->sport);
})->with(['userMetaData' => function($q) use ($request) {
$q->where('city', $request->sport);
}]);
})
Remove userMetaData from User::with(['role','userBet','userComission','userPartnership'])
I'm trying to query a nested relation and getting the count of distant model count, I'm transforming this to a variable but it is not happening:
$companies = Company::where('is_client', '=', 1)
// load count on distant model
->with(['interactionSummaries.interaction' => function ($q) {
$q->withCount(['contactsAssociation' => function ($q) {
$q->whereHas('company', function ($q) {
$q->where('type', 'like', 'Research');
});
}]);
}])
->get()
->transform(function ($company) {
$company->contacts_association_count = $company->interactionSummaries
->pluck('interaction.contacts_association_count')
->collapse()
->sum();
});
When I try to dd($companies) or even return $companies I get null values placed in each array index
But when I do dd($company) inside transform
->transform(function ($company) {
$company->contacts_association_count = $company->interactionSummaries
->pluck('interaction.contacts_association_count')
->collapse()
->sum();
dd($company);
});
I get the single collection which states my query is running properly:
If I remove transform part and simply get the collection:
$companies = Company::where('is_client', '=', 1)
// load count on distant model
->with(['interactionSummaries.interaction' => function ($q) {
$q->withCount(['contactsAssociation' => function ($q) {
$q->whereHas('company', function ($q) {
$q->where('type', 'like', 'Research');
});
}]);
}])
->get()
I'm getting the desired output, But I don't know what it is happening after transform execution. In fact it is not plucking and adding the sum, as I'm getting all 0 in the counts.
I've marked them with red. Help me out in this
I think you are missing a return statement in the transform closure.
You need to put return $company statement in transform.
I am trying to avoid DRY in my query builder, specifically on adding additional method in the chain.
Example, this is initially the query builder that I have:
$products = $app->myShop->realProducts()
->where($query)
->skip($skip)->take($take)
->orderBy($sortKey, $sortOrder)
->get();
Then if user used some filter, I needed to append a method (specifically a whereHas()) to the query builder
$products = $app->myShop->realProducts()
->where($query)
->whereHas('colour', function ($q) use ($find) {
$q->where('colour_slug', $find);
})
->skip($skip)->take($take)
->orderBy($sortKey, $sortOrder)
->get();
I find it "ugly" that to achieve this result, I have to keep repeating those builder query:
if ($user_filtered_this_page == TRUE) {
$products = $app->myShop->realProducts()->where($query)
->whereHas('colour', function ($q) use ($find) {
$q->where('colour_slug', $find);
})
->skip($skip)->take($take)
->orderBy($sortKey, $sortOrder)
->get();
} else {
$products = $app->myShop->realProducts()->where($query)
->skip($skip)->take($take)
->orderBy($sortKey, $sortOrder)
->get();
}
Is there a more clever or elegant way to dynamically and conditionally append the whereHas() method to the chain?
Hope somebody can help. Thank you!
The query doesn't get executed until you call ->get() so you can quite simply build your query, conditionally add your ->whereHas() and then execute it:
$query= $app->myShop->realProducts()
->where($query)
->skip($skip)->take($take)
->orderBy($sortKey, $sortOrder);
if (...) {
$query->whereHas(...);
}
$products = $query->get();
This could be done using Query Builder Conditional Clauses
$products = $app->myShop->realProducts()
->where($query)
->when($user_filtered_this_page, function($query) use($find){
$query->whereHas('colour', function ($q) use ($find) {
$q->where('colour_slug', $find);
})
})
->skip($skip)->take($take)
->orderBy($sortKey, $sortOrder)
->get();
You can write like this:
$products = $app->myShop->realProducts()->where($query)
->whereHas('colour', function ($q) use ($find) {
if ($user_filtered_this_page) {
$q->where('colour_slug', $find);
}
})
->skip($skip)->take($take)
->orderBy($sortKey, $sortOrder)
->get();
Hope it help you ;)
I have this function to get the results from joining relationships. However, the constraints don't seem to be taking effect. If I comment out the constraints, it still it gives the same result set as the uncommented constraints.
public function singleCategory($type, $category)
{
$track = TrackType::with(['tracks.subgenres'], function($query, $category) {
$query->where('name','=', $category);
})->where('name', '=', $type)->get();
dd($track->toArray());
}
Any help would be really appreciated :)
The constraining closure is only passed one parameter: $query. The other parameter you're trying to access ($category) needs to be made available via the use keyword:
public function singleCategory($type, $category)
{
$track = TrackType::with(['tracks.subgenres'], function($query) use ($category) {
$query->where('name', '=', $category);
})->where('name', '=', $type)->get();
dd($track->toArray());
}
Edit
The above will not affect the TrackTypes that are returned, it will only limit the related subgenres that are eager loaded. Based on the comments, it seems as if you are trying to limit the TrackTypes returned to those that contain a certain subgenre. In this case, you need to use the whereHas method:
public function singleCategory($type, $category)
{
$track = TrackType::with('tracks.subgenres')
->whereHas('tracks.subgenres', function($query) use ($category) {
$query->where('name', '=', $category);
})
->where('name', '=', $type)
->get();
dd($track->toArray());
}
Edit 2
It sounds like your target result set is actually the tracks, and not the track types. If so, you probably want to start by querying the tracks, and add in your filter criteria from there (I don't know your exact model and relationship names, so adjust accordingly):
public function singleCategory($type, $category)
{
$track = Track::with(['tracktype', 'subgenres'])
->whereHas('tracktype', function($query) use ($type) {
$query->where('name', '=', $type);
})
->whereHas('subgenres', function($query) use ($category) {
$query->where('name', '=', $category);
})
->get();
dd($track->toArray());
}
If you really are looking to start with the TrackType, then in addition to adding the whereHas() to find the TrackType, you need to add a whereHas() to filter down the Tracks that are eager loaded:
public function singleCategory($type, $category)
{
$track = TrackType::with(['tracks' => function ($query) use ($category) {
$query->whereHas('subgenres', function($query) use ($category) {
$query->where('name', '=', $category);
});
}, 'tracks.subgenres'])
->whereHas('tracks.subgenres', function($query) use ($category) {
$query->where('name', '=', $category);
})
->where('name', '=', $type)
->get();
dd($track->toArray());
}
The first example is much cleaner. In plain English, the first example would sound like "get all tracks of a type and a subgenre". The second example would sound like "get the track type, but only if it has tracks in a certain subgenre; also, only load those tracks within that subgenre".