ttocskcaj
ttocskcaj

Reputation: 1

Laravel Eloquent: Search models with array of relationships

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

Answers (3)

ttocskcaj
ttocskcaj

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

Josh
Josh

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

Mike Foxtech
Mike Foxtech

Reputation: 1651

$collection = collect([
    'field1' => 'value1', 
    'field2' => 'value2',
    'field3' => 'value3', 
]);

$outcome = $collection->contains(function ($value, $key) use ($category) {
  return $value == $category;
});

Upvotes: 0

Related Questions