Home > front end >  Laravel filtering a hasMany relation
Laravel filtering a hasMany relation

Time:10-10

Background first on my question. A user hasMany contacts and a contact hasMany anniversaries. I want to filter the upcoming anniversaries. So far I have this:

$filtered = auth()->user()->contacts()->get()->each(function ($contact) {
    $contact->anniversaries->filter(function ($anniversary) {

        // return true or false based on a method on the anniversary model
        return $anniversary->method() == true;

    });
});

But this just returns all the contacts (obviously) with all their anniversaries, and I wish to exclude the ones that are false when calling the $anniversary->method().

Whatever is in the $anniversary->method() is not important, this just returns a true or false.

When I do the following, it works:

$collection = auth()->user()->anniversaries()->get();

$filtered = $collection->filter(function ($anniversary) {
    return $anniversary->method() == true;
});

I get only the anniversaries from where the $anniversary->method() is indeed true.

My question is mainly, why does this happen, I only want to understand it, not so much need an answer on how to make it work. Thanks in advance for any insights!

CodePudding user response:

In the first example, you are only filtering the anniversaries of each contact. You are not filtering the contacts directly per see.

$filtered = auth()->user()->contacts()->get()->each(function ($contact) {
    // You are filtering only the anniversaries of each contact
    $anniversaries = $contact->anniversaries->filter(function ($anniversary) {
        return $anniversary->method() == true;

    });

    // over here you should get the same as in your second example
    dd($anniversaries);
});

In your second example you are doing the following pseudo-code:

  1. Fetch all anniversaries of each user
  2. Filter the anniversaries that matches $anniversary->method() === true

To get the same results in the first example you would have to use a combination of filter with ->count()

$filtered = auth()->user()->contacts()->get()->filter(function ($contact) {
    $filteredByMethod = $contact->anniversaries->filter(function ($anniversary) {

        // return true or false based on a method on the anniversary model
        return $anniversary->method() == true;
    });

    return $filteredByMethod->count() > 0;
});

Which one is more performant is beyond the scope of this answer.

Quick tip

The method filter from Laravel's collections needs to return a truthy value to be filtered by. Since your method returns true or false you can just call the method directly without comparing with true:

$collection = auth()->user()->anniversaries()->get();

$filtered = $collection->filter(function ($anniversary) {
    return $anniversary->method();
});
  • Related