How to use custom query scopes in an Eloquent subselect query? - laravel

This works by manually getting the data as an array then repassing it:
public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)
{
return $query->where(function($query)use($value,$tenant)
{
$user_id_list = $tenant->getWhiteListedUsersGroup()
->users()
->select('users.id')
->lists('id')
->all()
;
$query->{ $value ? 'whereIn' : 'whereNotIn' }('users.id',$user_id_list);
});
}
But I want this to work (comment // indicates the only difference):
public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)
{
return $query->where(function($query)use($value,$tenant)
{
$user_id_list = $tenant->getWhiteListedUsersGroup()
->users()
->select('users.id')
//->lists('id')
//->all()
;
$user_id_list = $tenant->getWhiteListedUsersGroup()->users()->select('users.id');//->lists('id')->all();
$query->{ $value ? 'whereIn' : 'whereNotIn' }('users.id',$user_id_list);
});
}
I want to be able to create a "real" subselect without having to have duplicate copies of custom query scopes and relationship queries just for each scope. $tenant->getWhiteListedUsersGroup()->users() is a many-to-many relationship
Here is an example of what has to be done to get a real subselect:
public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)
{
return $query->where(function($query)use($value,$tenant)
{
$query->{ $value ? 'whereIn' : 'whereNotIn' }('users.id',function($query)
{
$query->from('groups_memberships')
// recreating an existing relationship function
->join('groups','groups.id','group_memberships.group_id')
->select('users.id')
// recreating an already existing query scope
->whereNull('deleted_at')
;
});
});
}
This question will most likely apply to both Laravel 4.0+ and 5.0+
This question is NOT answered by How to do this in Laravel, subquery where in
Restructing code so that the query starts from intended sub query will not work as soon as I need a second non-trivial subselect.
The inclusion/exclusion of ->getQuery() has not made a difference.
I have to choose between a fake subselect or non-DRY custom query scopes.
It seems that the main issue is that the subselect engine is forcing me to use a pre-existing $query object that can't be initialized from an existing relationship.
The recreation of soft deletes (whereNull('deleted_at')) is a trivial example, but I might have to recreate a queryscope that could be relatively complicated already.

Is this whats your going after?
$value; //true or false
$tenant->whereHas('user', function($query) use ($value){
$query->whereHas('groupMembership', function($query) use ($value){
$query->whereHas('group', function($query) use ($value){
if($value){ $query->onlyTrashed(); )
});
})
})
This assumes the group relation includes a withTrashed() call on the relation

Related

Query Builder filter for multi level deep relationship in Laravel

I have a selection of plots which each belong to a development by a hasManyThrough relationship through housetypes. I want to filter these by development on their overview page. Plots has a housetype_id column and housetypes has a development_id column.
public function plots()
{
return $this->hasManyThrough(Plot::class, Housetype::class);
}
When I use my filter it returns the developments ID number as $development, I then need this to only show plots which are linked to that development.
I have looked into using whereHas or Join methods but have been unable to figure this out. Current filter scope is below. Thanks
public function scopeFilterDevelopment($query)
{
$development = request()->input('filter_development');
if ($development == "") {
return;
}
if(!empty($development)){
$query->where('development_id', $development);
}
}
If I can understand it right you wish to assert a condition on other Model, HasMany will load all the objects to the related model once the query is completed. Eloquent then binds the related model objects to each.
Try joins from Laravel instead. I feel this is what you exactly want: https://laravel.com/docs/5.8/queries#joins
I would use whereHas to filter the relationship:
YourModel::whereHas('plots', function($query) {
$query->filterDevelopment();
})->get();
I would also edit the query scope not to rely on the request global function, but instead pass the development of value as a parameter.
you have make a leftjon and then use when, you dont have to use
if(!empty($development)){
$query->where('development_id', $development);
}
this any more, you can use
->when($development=="" ? false : true, function($query) use ($development){
return $query->where('development_id', $development);
})
this is a full example
$queryBuilder = DB::table('facturas')->
leftJoin('clientes','clientes.id','=','facturas.clientes_id')->
select('facturas.estados_id as estado','facturas.numero as
numero',DB::raw('concat(clientes.nombre," ",clientes.apellido) as cliente'))->
when($estados===null ? false: true,function($query) use ($estados){
return $query->whereIn('facturas.estados_id', $estados);
})
It was a whereHas that solved this in the end! (another developer at work walked me through this)
Relationship -
public function housetype()
{
return $this->belongsTo(Housetype::class);
}
Function -
public function scopeFilterDevelopment($query)
{
if (request()->input('filter_development') == "") {
return;
}else{
$query->whereHas('housetype', function($housetype){
$housetype->where('development_id', request()->input('filter_development'));
});
}
}
This then returns any plot where its housetype has a matching development_id for the filter_development from the request.
Thanks for everyone's input

Does "where" works with mutattors in eloquent?

I have a mutattor in an eloquent model that generates a "status" atribute.
public function getStatusAttribute(){
if(){
return "enabled";
}
else
{
return "disabled";
}
}
Can I use?
$query = Anuncio::query();
$query->where('status', "enabled" );
return $query->get();
I seems that I cannot. I getting "status" column not defined. How can I get around this problem?
No doesn't works it works on model level
you can use after query from database in collection result
No, when you are doing a query you are asking the database, therefor there needs to be a column status.
There is a way, retrieve x elements from the database and use the Laravel Collection method where(). This is not optimal if you have many elements in teh database, it will retrieve all of them.
Anuncio::all()->where('status', 'enabled')->all();

Laravel 5 hasMany relation returning incorrect relations

I have a Yard model and Treatment model, I am using the following hasMany relationship to return treatments that are currently active:
public function activeTreatments() {
return $this->hasMany('App\Models\Treatment')
->where(function ($q) {
$q->where('expires_at','>=', Carbon::now('Pacific/Auckland'))
->where('completed_at','>', Carbon::now('Pacific/Auckland'));
})
->orWhere('completed',false);
}
For some reason when I add the ->orWhere('completed',false) the query returns all treatments not just the treatments associated with the specific yard. What am I doing wrong here?
It's hard to say exactly what is going on without inspecting the SQL being generated.
Wherever you are using this code, you could chain a toSql() on the end to see what the query looks like (use this where you would use a get()). Or you could enable the query log to see what is being queried.
Given the symptoms, it is likely that the orWhere() is negating a condition used to filter the models.
Try nest the orWhere() inside the current where() statement:
public function activeTreatments() {
return $this->hasMany('App\Models\Treatment')
->where(function ($q) {
$q->where(function($q), {
$q->where('expires_at','>=', Carbon::now('Pacific/Auckland'))
->where('completed_at','>', Carbon::now('Pacific/Auckland'));
})->orWhere('completed',false);
});
}

Laravel get result from query in side query by Eloquent in one object

I have two tables:
main_presentations
so here i have "id" and "isEnabled";
child_presentations
And here i have "id" , "isEnabled" and "idParent";
I want to select in one object this is my code:
public function MainSlider(MainPresentation $MainPresentations, ChildPresentation $ChildPresentations)
{
$MainPresentations = MainPresentation::where('isEnabled', true)->get();
foreach ($MainPresentations as $MainPresentation) {
$AnArray[] = ChildPresentation::where([
['idParent', $MainPresentation['id']],
['isEnabled', true]
])->get();
}
return $AnArray;
}
but this is the result:
enter image description here
What you are doing is executing a query per result, which can be ineffective when it starts getting bigger.
You can:
Use querybuilder
As it follows, you just build a query starting on ChildPresentation, set a relation to MainPresentation table by id and get the collection
public function MainSlider()
{
$childPresentations = ChildPresentation::join('main_presentations','main_presentations.id','child_presentations.idParent')
->where('child_presentations.isEnabled', true)->where('main_presentations.isEnabled', true)->get();
return $childPresentations;
}
If you want all the MainPresentations with their respective ChildPresentations, only the enables ones.
You can take advantage of Laravel relationships and eager loading.
https://laravel.com/docs/5.6/eloquent-relationships
First, set the relationships in your MainPresentation model
In MainPresentation.php
public function childPresentation {
return $this->hasMany('App\ChildPresentation', 'idParent', 'id');
}
Your MainSlider function would be:
(Btw, no idea why you're receiving two arguments if you're overriding them but doesn't matter)
public function MainSlider() {
$mainPresentations = MainPresentation::with(['childPresentations' => function ($advancedWith) {
child_presentation.isEnabled is true
$advancedWith->where('isEnabled', true);
}])
->where('isEnabled', true)->get()->toArray();
return $mainPresentations;
}
This will return an array of MainPresentations that contain an array of child_presentations, with all their childs.
This translates to two queries:
Select * from main_presentations where isEnabled = true;
Select * from child_presentations where isEnabled= true and id in (in the first query);
Laravel then does background work to create the structure you desire when you write ->toArray()
Note: If you have a $visible array in your MainPresentation model, be sure to add: 'childPresentation' to it, otherwise the toArray will not agregage the childs to the parent.
Second note: I advise following some standards whenever you're writing code, usually functions are named camelCase and variables are camelCase.

How to fetch two related objects in Laravel (Eloquent) with one SQL query

I am trying to get two related objects in Laravel using eager loading as per documentation.
https://laravel.com/docs/5.4/eloquent-relationships#eager-loading
My models are:
class Lead extends Model {
public function session() {
return $this->hasOne('App\LeadSession');
}
}
class LeadSession extends Model {
public function lead() {
return $this->belongsTo('App\Lead');
}
}
I want to get both objects with one SQL query. Basically I want to execute:
select * from lead_sessions as s
inner join lead as l
on l.id = s.lead_id
where s.token = '$token';
and then be able to access both the LeadSession and Lead objects. Here is the php code I am trying:
$lead = Lead::with(['session' => function ($q) use ($token) {
$q->where('token','=',$token);
}])->firstOrFail();
print($lead->session->id);
I have also tried:
$lead = Lead::whereHas('session', function($q) use ($token) {
$q->where('token','=',$token);
})->firstOrFail();
print($lead->session->id);
and
$session = LeadSession::with('lead')->where('token',$token)->firstOrFail();
print($session->lead->id);
In all three cases I get two queries executed, one for the leads table, and another for the lead_sessions table.
Is such a thing possible in Eloquent? In my view it should be a standard ORM operation, but for some reason I am struggling a whole day with it.
I don't want to use the Query Builder because I want to use the Eloquent objects and their functions afterwards.
I am coming from Python and Django and I want to replicate the behavior of select_related function in Django.
Try this and see if it makes more than one query
$session = LeadSession::join('leads', 'leads.id', '=', 'lead_sessions.lead_id')
->where('token',$token)
->firstOrFail();
I hope it only runs a single query. I didnt test this. Not sure if you have to add a select() to pick the columns. But yeah, try this first.
Updates
Just adding how to use both session and lead data. Try a select and specify the data you need. The reason being that if both tables have similar columns like 'id', one of them will be overwritten. So you have to alias your select like
$session = LeadSession::join('leads', 'leads.id', '=', 'lead_sessions.lead_id')
->where('token',$token)
->select(
'lead_sessions.*',
'leads.id as lead_id',
'leads.name',
'leads.more_stuff'
)
->firstOrFail();
Now all this data belongs to $session variable. For testing you were doing
print($lead->session->id);
//becomes
print($session->lead_id); //we aliased this in the query

Resources