Home > Mobile >  Laravel filter nested collection returns duplicated results
Laravel filter nested collection returns duplicated results

Time:08-04

I'm trying to filter an existing collection to avoid querying database non-stop. As far as I understand, re-using an existing collection does not perform any query in the database, but correct me if I'm wrong.

Unfortunately the results given by the filter are not similiar with the normal query function.

Three tables only:

Clients (id, name)
Products (id, name)
ProductsClients (product_id, client_id)

The main function that works flawless:

public function findAll($productsIds)
{
    $this->clients = Clients::with(['products' => function($query) use($productsIds)
        {
            $query->whereIn('product_id', $productsIds);
        }])
        ->whereHas('products', function($query) use ($productsIds)
        {
            $query->where('active', true)
                ->whereIn('product_id', $productsIds);
        })
        ->groupBy('id')
        ->orderBy('id', 'ASC')
        ->get();
}

The following illustrates my problem:

ProductsClients table sample:
(1000, 1)
(1001, 1)
(1000, 2);
(1002, 2);

Input sample:
Customer 1 : has [1000, 1001] : misses [1002]
Customer 2 : has [1000, 1002] : misses [1001]

Output sample:
Customer 1 : [1000, 1001]   [1002]
Customer 2 : [1000, 1002]   [1001]

The function to perform query operations within the collection:

private function findInCollection($productsIds)
{
    return $this->clients->filter(function($client) use ($productsIds)
    {
        return $client->products->whereIn('product_id', $productsIds)->count() > 0;
    })->all();
}

And the logic I have to grab the missing id's:

foreach ($this->clients as $client)
{
    $missingProductsIds = [...]; // Will be 1002 & 1001 respectively
    $fromOtherClient = $this->findInCollection($missingProductsIds); 
}

Current (wrong) output:
Customer 1 : [1000, 1001]   [1000, 1001]   [1002]
Customer 2 : [1000, 1002]   [1000, 1002]   [1001]

So, it's returning the initial input plus the correct value I'm looking for.

If instead of $fromOtherClient = $this->findInCollection($missingProductsIds); I use $fromOtherClient = $this->findAll($missingProductsIds); it works great.

CodePudding user response:

Hard to understand your requirements, but if I have it right you'll need to loop through each item in the collection, pull out the list of product ids, and then diff it against the list of ids you're looking for. Something like this:

private function findInCollection(array $productsIds): Collection
{
    return $this->clients->mapWithKeys(function($client) use ($productsIds) {
        return [
            $client->id => collect($productIds)
                ->diff($client->products->pluck("id"))
                ->values()
                ->toArray();
        ];
    });
}

The returned collection will be indexed by client ID.


Here's some proof of concept code that illustrates the technique:

$clients = collect([
    collect(["id" => 1, "products" => collect([["id" => 1000, "name" => "Product 1000"], ["id" => 1001, "name" => "Product 1001"], ["id" => 1003, "name" => "Product 1003"]]), "name" => "Client 1"]),
    collect(["id" => 2, "products" => collect([["id" => 1000, "name" => "Product 1000"], ["id" => 1002, "name" => "Product 1002"], ["id" => 1003, "name" => "Product 1003"]]), "name" => "Client 2"]),
]);
$productIds = [1000, 1001, 1002];

$missing = $clients->mapWithKeys(function ($v) use ($productIds) {
    return [$v["id"] => collect($productIds)->diff($v["products"]->pluck("id"))->values()->toArray()];
});

dump($missing);

Output:

Illuminate\Support\Collection^ {#5133
  #items: array:2 [
    1 => array:1 [
      0 => 1002
    ]
    2 => array:1 [
      0 => 1001
    ]
  ]
  #escapeWhenCastingToString: false
}
  • Related