I have the following scope in my Eloquent Model Candidate that let's me fetch a Collection where each item has a relation matching the Collections $fields and $categories.
That is, it lets me get a Collection of Candidates that have all $fields and $categories.
class Candidate extends Model {
public function scopeMatching($query, $fields, $categories ) {
return $query->get()->filter( function ( $candidate ) use ( $categories, $fields ) {
$outcome = false;
foreach ($categories as $category){
$outcome = $candidate->categories->contains( $category );
}
foreach ($fields as $field){
$outcome = $candidate->fields->contains( $field );
}
return $outcome;
});
}
public function user() {
return $this->belongsTo( User::class );
}
public function jobs() {
return $this->belongsToMany(Job::class);
}
public function categories() {
return $this->belongsToMany(Category::class);
}
public function fields() {
return $this->belongsToMany(Field::class);
}
}
Usage example from a test method:
// Pick 2 random categories
$categories = Category::all()->random( 2 );
// Pick 5 random fields.
$fields = Field::all()->random( 5 );
// Pick a random candidate.
$candidate = Candidate::all()->random();
// Attach categories and fields to candidate.
$candidate->categories()->attach( $categories );
$candidate->fields()->attach( $fields );
// Filter candidates to get only matching ones.
$candidates = Candidate::with(['fields','categories'])->matching($fields, $categories);
It's working fine, but I'm wondering if it's the best way to do it. Is there a better, more "eloquent" way to do it without using the two foreach loops?
scopeMatchingdoes what you think it does. It will only actually check if the last field in your fields array is a relation.