Query Builder filter for multi level deep relationship in Laravel - 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

Related

Eloquent - apply value from relationship to where in scope

So I have this need to check if a customer needs to be called. Customers has to be called at intervals depending on a value days_between_calls in a BelongsTo model called SubscriberType. I got it to work but I don't like it, maybe there is a cleaner way.
So I have a model Subscription with relations :
public function subscriberType()
{
return $this->belongsTo(SubscriberType::class);
}
public function calls()
{
return $this->hasMany(Call::class);
}
and a (very simplified) scope :
public function scopeNeedsCall(Builder $query) {
$query->join('subscriber_types', 'subscriber_types.id', '=', 'subscriptions.subscriber_type_id')
->whereDoesntHave('calls', function(Builder $query) {
$query->whereRaw('calls.created_at > DATE_SUB(NOW(), INTERVAL days_between_calls DAY)');
});
}
Is there any cleaner way to use this days_between_calls field's value without manually joining its table and without writing raw sql?
Thanks ahead.
So it looks like there is not much that can be improved, and I do need a rawsql part here. I improved it a little anyway using https://laravel.com/docs/9.x/eloquent-relationships#has-one-of-many but that's about it.

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);
});
}

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

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

Laravel Has One Relation changing the identifier value

I'm not sure this is a real relation. I will try to explain the best way I can.
So first of all, I have three models :
Appartement,
AppartementPrice
The AppartementPrice depends on :
- appartement_id
I would like the AppartementPrice to be retrieve like that :
If there is a specific price for the appartement, then retrieve it, If not retrieve the price for all appartement which is stored in the database with an appartement_id = 0.
So basically what I would like is to do something like that :
public function price()
{
if(isset($this->hasOne('AppartementPrice')->price) // Check that relation exists
return $this->hasOne('AppartementPrice');
else
return $this->hasOne('AppartementPrice')->where('appartement_id', '0');
}
But this is not working.
It does not retrive me the default price.
I guess anyway this is not a best practice ?
I first tried to get the informations like that :
//Check if appartment has a specific price or retrieve default
if($priceAppartement = AppartementPrice::getPriceByCompanyAppartement($this->id))
return $priceAppartement;
else
return AppartementPrice::getDefaultPrice();
But I had this error :
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
when doing :
echo $app->price->price;
How can I check that a relation exists ? And is there a way to do as I describe ?
Thank you
You can't replace relation like this, as what you intend is not logical - you want to retrieve relation that doesn't exist.
Instead you can do this:
public function getPriceAttribute()
{
return ($this->priceRelation) ?: $this->priceDefault();
}
public function priceDefault()
{
// edit: let's cache this one so you don't call the query everytime
// you want the price
return AppartmentPrice::remember(5)->find(0);
}
public function priceRelation()
{
return $this->hasOne('AppartementPrice');
}
Then you achieve what you wanted:
$app->price; // returns AppartmentPrice object related or default one
HOWEVER mind that you won't be able to work on the relation like normally:
$price = new AppartmentPrice([...]);
$app->price()->save($price); // will not work, instead use:
$app->priceRelation()->save($price);
First of all something really important in Laravel 4.
When you do not use parentheses when querying relationship it means you want to retreive a Collention of your Model.
You have to use parentheses if you want to continue your query.
Ex:
// for getting prices collection (if not hasOne). (look like AppartementPrice)
$appartment->price;
// for getting the query which will ask the DB to get all
//price attached to this appartment, and then you can continue querying
$priceQuery = $appartment->price();
// Or you can chain your query
$appartment->price()->where('price', '>', 0)->get() // or first() or count();
Secondly, your question.
//Appartement Model
// This function is needed to keep querying the DB
public function price()
{
return $this->hasOne('AppartementPrice')
}
// This one is for getting the appartment price, like you want to
public function getAppartmentPrice()
{
$price_object = $this->price;
if (!$price_object) // Appartment does not have any price {
return AppartementPrice->where('appartement_id', '=', 0)->get();
}
return $price_object;
}

Filtering eager-loaded data in Laravel 4

I have the following setup:
Clubs offer Activities, which are of a particular Type, so 3 models with relationships:
Club:
function activities()
{
return $this->hasMany('Activity');
}
Activity:
function club()
{
return $this->belongsTo('Club');
}
function activityType()
{
return $this->hasMany('ActivityType');
}
ActivityType:
function activities()
{
return $this->belongsToMany('Activity');
}
So for example Club Foo might have a single Activity called 'Triathlon' and that Activity has ActivityTypes 'Swimming', 'Running', and 'Cycling'.
This is all fair enough but I need to show a list of ActivityTypes on the Club page - basically just a list. So I need to get the ActivityTypes of all the related Activities.
I can do that like so from a controller method that receives an instance of the Club model:
$data = $this->club->with(array('activities', 'activities.activityTypes'))->find($club->id)
That gets me an object with all the related Activities along with the ActivityTypes related to them. Also fair enough. But I need to apply some more filtering. An Activity might not be in the right status (it could be in the DB as a draft entry or expired), so I need to be able to only get the ActivityTypes of the Activities that are live and in the future.
At this point I'm lost... does anybody have any suggestions for handling this use case?
Thanks
To filter, you can use where() as in the fluent DB queries:
$data = Club::with(array('activities' => function($query)
{
$query->where('activity_start', '>', DB::raw('current_time'));
}))->activityType()->get();
The example which served as inspiration for this is in the laravel docs, check the end of this section: http://laravel.com/docs/eloquent#eager-loading
(the code's not tested, and I've taken some liberties with the property names! :) )
I think if you first constraint your relationship of activities, the activity types related to them will be automatically constrained as well.
So what I would do is
function activities()
{
return $this->belongsToMany('Activity')->where('status', '=', 'active');
}
and then your
$data = $this->club->with(array('activities', 'activities.activityTypes'))->find($club->id)`
query will be working as you would expect.

Resources