From 78308cf1fc6cd247c954a521de12ca4cebf88fe4 Mon Sep 17 00:00:00 2001 From: Frans Slabbekoorn Date: Tue, 15 Apr 2025 09:51:41 +0200 Subject: [PATCH 1/3] fix: handle array-to-array comparison in filter matching --- src/Engines/ArrayEngine.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Engines/ArrayEngine.php b/src/Engines/ArrayEngine.php index 8360f2c..6caf9dd 100644 --- a/src/Engines/ArrayEngine.php +++ b/src/Engines/ArrayEngine.php @@ -139,10 +139,15 @@ private function matchesFilters($record, $filters, $not = false) } $match = function ($record, $key, $value) { + $recordValue = data_get($record, $key); if (is_array($value)) { - return in_array(data_get($record, $key), $value, true); + if (is_array($recordValue)) { + return count(array_diff($value, $recordValue)) === 0 && count(array_diff($recordValue, $value)) === 0; + } + return in_array($recordValue, $value, true); } - return data_get($record, $key) === $value; + + return $recordValue === $value; }; $match = Collection::make($filters)->every(function ($value, $key) use ($match, $record) { @@ -289,7 +294,8 @@ protected function buildSearchQuery(Builder $builder) ); return $this->constrainForSoftDeletes( - $builder, $this->addAdditionalConstraints($builder, $query->take($builder->limit)) + $builder, + $this->addAdditionalConstraints($builder, $query->take($builder->limit)) ); } } From 2de857cfc86603d5c998bd77924da16313d6c8a5 Mon Sep 17 00:00:00 2001 From: Frans Slabbekoorn Date: Fri, 18 Apr 2025 10:36:35 +0200 Subject: [PATCH 2/3] refactor: pass on single intersection to mirror search engines functionality --- src/Engines/ArrayEngine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Engines/ArrayEngine.php b/src/Engines/ArrayEngine.php index 6caf9dd..49a0500 100644 --- a/src/Engines/ArrayEngine.php +++ b/src/Engines/ArrayEngine.php @@ -112,7 +112,7 @@ protected function performSearch(Builder $builder, array $options = []) $this->matchesFilters($record, $builder->whereIns) && $this->matchesFilters($record, data_get($builder, 'whereNotIns', []), true) && !empty(array_filter(iterator_to_array($values, false), function ($value) use ($builder) { - return !$builder->query || stripos($value, $builder->query) !== false; + return !$builder->query || stripos((string) $value, $builder->query) !== false; })); }, true); @@ -142,7 +142,7 @@ private function matchesFilters($record, $filters, $not = false) $recordValue = data_get($record, $key); if (is_array($value)) { if (is_array($recordValue)) { - return count(array_diff($value, $recordValue)) === 0 && count(array_diff($recordValue, $value)) === 0; + return count(array_intersect($recordValue, $value)) > 0; } return in_array($recordValue, $value, true); } From 1b718569010c427f9eccee976a113e5f1e2e2a6f Mon Sep 17 00:00:00 2001 From: Frans Slabbekoorn Date: Fri, 18 Apr 2025 10:36:57 +0200 Subject: [PATCH 3/3] test: array to array comparison --- tests/Engines/ArrayEngineTest.php | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/Engines/ArrayEngineTest.php b/tests/Engines/ArrayEngineTest.php index 6b008c1..e4c1dd8 100644 --- a/tests/Engines/ArrayEngineTest.php +++ b/tests/Engines/ArrayEngineTest.php @@ -108,6 +108,66 @@ public function it_returns_empty_array_if_no_results_found() $this->assertEquals(0, $results['total']); } + /** @test */ + public function it_can_filter_using_where_in_with_array_intersection() + { + $engine = new ArrayEngine(new ArrayStore()); + $engine->update(Collection::make([ + new SearchableModel(['id' => 1, 'tags' => ['php', 'laravel'], 'scoutKey' => 1]), + new SearchableModel(['id' => 2, 'tags' => ['javascript', 'vue'], 'scoutKey' => 2]), + new SearchableModel(['id' => 3, 'tags' => ['php', 'symfony'], 'scoutKey' => 3]), + ])); + + $builder = new Builder(new SearchableModel(), null); + $builder->whereIns = [ + 'tags' => ['php'], + ]; + $results = $engine->search($builder); + + $this->assertCount(2, $results['hits']); + $this->assertEquals([3, 1], array_column($results['hits'], 'scoutKey')); + } + + /** @test */ + public function it_can_filter_using_where_not_in_with_array_values() + { + $engine = new ArrayEngine(new ArrayStore()); + $engine->update(Collection::make([ + new SearchableModel(['id' => 1, 'tags' => ['php', 'laravel'], 'scoutKey' => 1]), + new SearchableModel(['id' => 2, 'tags' => ['javascript', 'vue'], 'scoutKey' => 2]), + new SearchableModel(['id' => 3, 'tags' => ['php', 'symfony'], 'scoutKey' => 3]), + ])); + + $builder = new Builder(new SearchableModel(), null); + $builder->whereNotIns = [ + 'tags' => ['javascript'], + ]; + $results = $engine->search($builder); + + $this->assertCount(2, $results['hits']); + $this->assertEquals([3, 1], array_column($results['hits'], 'scoutKey')); + } + + /** @test */ + public function it_can_filter_using_where_in_with_single_value_against_array() + { + $engine = new ArrayEngine(new ArrayStore()); + $engine->update(Collection::make([ + new SearchableModel(['id' => 1, 'category' => 'php', 'scoutKey' => 1]), + new SearchableModel(['id' => 2, 'category' => 'javascript', 'scoutKey' => 2]), + new SearchableModel(['id' => 3, 'category' => 'php', 'scoutKey' => 3]), + ])); + + $builder = new Builder(new SearchableModel(), null); + $builder->whereIns = [ + 'category' => ['php'], + ]; + $results = $engine->search($builder); + + $this->assertCount(2, $results['hits']); + $this->assertEquals([3, 1], array_column($results['hits'], 'scoutKey')); + } + /** @test */ public function custom_index_can_be_passed() {