Reputation: 1
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?
Upvotes: 0
Views: 2036
Reputation: 1
The solution that Josh suggested works, but I tweaked it a bit because I wanted to match ALL fields, but ANY category.
public function scopeMatching( Builder $query, Collection $fields, Collection $categories ) {
foreach ( $fields as $field ) {
$query = $query->whereHas( 'fields', function ( Builder $q ) use ( $field ) {
$q->where( 'id', $field->id );
} );
}
$query->whereHas( 'categories', function ( Builder $q ) use ( $categories ) {
$q->whereIn( 'id', $categories->pluck( 'id' ) );
});
return $query->get();
}
Upvotes: 0
Reputation: 956
It would be better to use Eloquent's whereHas
method alongside whereIn
because these will use SQL rather than your PHP for loops which will end up being much more efficient.
$query->whereHas('fields', function($q) use($fields) {
$q->whereIn('id', $fields->pluck('id'));
})->orWhereHas('categories', function($q) use($categories) {
$q->whereIn('id', $categories->pluck('id');
})->get();
This query with get all Candidates that have at least one field relation who's id is in the fields
array or has at least one category relation who's id is in the categories
array.
You can read more about these methods in the documentation Laravel Query Builder Documentation
Upvotes: 2
Reputation: 1651
$collection = collect([
'field1' => 'value1',
'field2' => 'value2',
'field3' => 'value3',
]);
$outcome = $collection->contains(function ($value, $key) use ($category) {
return $value == $category;
});
Upvotes: 0