0

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?

2
  • I do not think that scopeMatching does what you think it does. It will only actually check if the last field in your fields array is a relation. Commented Apr 23, 2018 at 8:44
  • True! I didn't pick up on that. Cheers :) Commented Apr 23, 2018 at 8:49

3 Answers 3

2

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

Sign up to request clarification or add additional context in comments.

4 Comments

Ok, cheers. After considering the problem, I figures I wanted to get Candidates that contained all $fields but and of $categories. I've modified it slightly, and still had to use a foreach loop, but it's doing the work sql side now.
Glad you found your solution but a cleaner solution still would be to pass , '=', $fields->count() as third and fourth parameters to the first whereHas in my answer above. This will then meet your new requirements with no need for any for loop.
Would that check if it has all those fields though? Or just at least one of them and the right amount?
It should check for those fields. It will get all records where id is in your array and ensure the count is the length of your array. It will not count relations that do not meet the requirement in that closure. In short, yes, this will check that it has all of those fields.
0
$collection = collect([
    'field1' => 'value1', 
    'field2' => 'value2',
    'field3' => 'value3', 
]);

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

2 Comments

Not sure I follow. I'm already using the contains() method. I would still have to use a foreach loop to loop through the $categories collection
You should explain why your anwser solves OP's problem. Currently your anwser contains zero information, except a non informative codeblock.
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.