In short - I would like to define global scope that would filter related instances by the value of the field in pivot table. I have defined the next relationship:
class Client extends Model {
function promotions():BelongsToMany{
return $this->belongsToMany(Promotion::class)
->using(ClientPromotionPivot::class);
}
}
class Promotion extends Model {
// ...
}
class ClientPromotionPivotextends Pivot {
protected $casts = [
'active' => 'boolean'
];
}
My idea is that by default, everywhare where I access $client->promotions - only active ones would appear:
$active_promotions = $client->promotions;
BUT, sometimes I need to access them all:
$all_promotions = $client->promotions()
->withoutGlobalScope('active_promotions') // or something similar
->get();
Also I have many more similar relationships in many models where the Pivot table has the field active to be filtered on. The problem is it seems that global scopes doesn't know anything about the pivot table.
P.S. : Please do not offer creating two separated relations - like all_promotions and active_promotions - I would like to avoid this. Also I don't want to write something similar to $client->promotions()->wherePivot('active') everywhere I access promotions.
To me it looks like a missing feature in Eloquent/Laravel. I found this Issue published recently, but there is no solution to it: https://github.com/laravel/framework/issues/48617
What I tried:
- Defining two different relationships (This is my current solution, but I don't like it too much):
function all_promotions():BelongsToMany{ return $this->belongsToMany(Promotion::class) ->using(ClientPromotionPivot::class); } function active_promotions():BelongsToMany{ return $this->all_promotions->wherePivot('active'); }
- Defining conditional relationship:
function promotions($active=true):BelongsToMany{ $relationship = $this->belongsToMany(Promotion::class) ->using(ClientPromotionPivot::class); if ($active) $relationship->wherePivot('active'); return $relationship; }
- Creating a global scope and passing it the relationship object:
And then using it like so:class ActivePivot implements Scope { public $belongs_to_many_query; // we need to pass the relationship to constructor to be able to use **qualifyPivotColumn** method public function __construct(BelongsToMany $belongs_to_many_query) { $this->belongs_to_many_query = $belongs_to_many_query; } public function apply(Builder $builder, Model $model) { return $builder->where( $this->belongs_to_many_query->qualifyPivotColumn('active'), '=', 'true' ); } }
And this way I can efectively do both:class Client extends Model { function promotions():BelongsToMany{ $relationship = $this->belongsToMany(Promotion::class) ->using(ClientPromotionPivot::class); // Here I'm passing the relationship to the constructor of the global scope $relationship->withGlobalScope('active_pivot', new ActivePivot($relationship)); return $relationship; } }
$active_promotions = $client->promotions; // and $all_promotions = $client->promotions()->withoutGlobalScope('active_pivot');
All three solutions work... but look ugly and not very flexible (imagine if i'd like to apply multiple global scopes/filters). Intuitively it seems to me that there should be a better approach to it.