Skip to content

Commit

Permalink
Merge pull request #38 from k-samuel/3.x
Browse files Browse the repository at this point in the history
Self-filtering option for individual filter
  • Loading branch information
k-samuel authored Dec 4, 2023
2 parents 8f118c9 + 7d9b9c4 commit d582dea
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 8 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,16 @@ All depends on your use case of the library.
Initially, the library was developed to simplify the construction of a search UI.
If you want to use the library at the level of technical analysis, statistics, etc. , then enabling self-filtering can help you to get expected results.

For all filters:
```php
$query = (new AggregationQuery())->filters($filters)->countItems()->sort()->selfFiltering(true);
```

For individual filter:
```php
$filters[] = (new ValueIntersectionFilter('size', [12,32]))->selfFiltering(true);
```


### More Examples
* [Demo](./examples)
Expand Down
11 changes: 11 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

### v3.2.1 (04.12.2023)

Self-filtering option for individual filter (disabled by default). [Feature Request](https://github.com/k-samuel/faceted-search/issues/37)
Advanced configuration for AggregationQuery, if enabled, then result for filter can contain only filter values.
Useful for cases with ValueIntersectionFilter (AND condition).
```php
$filters[] = (new ValueIntersectionFilter('size', [12,32]))->selfFiltering(true);
```



### v3.2.0 (29.11.2023)

- ValueIntersectionFilter added [Feature Request](https://github.com/k-samuel/faceted-search/issues/33)
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "k-samuel/faceted-search",
"version": "3.2.0",
"version": "3.2.1",
"type": "library",
"description": "PHP Faceted search",
"keywords": ["php","faceted search"],
Expand Down
27 changes: 27 additions & 0 deletions src/Filter/AbstractFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ abstract class AbstractFilter implements FilterInterface
*/
protected $value;

/**
* Self filtering is disabled by default
* @var boolean
*/
protected bool $selfFiltering = false;

/**
* AbstractFilter constructor.
* @param string $fieldName
Expand Down Expand Up @@ -86,4 +92,25 @@ public function getValue()
* @inheritDoc
*/
abstract public function filterInput(array $facetedData, array &$inputIdKeys, array $excludeRecords): void;

/**
* Enable/Disable self-filtering for current filter, disabled by default.
* Used in AggregationQuery
* @param bool $enabled
* @return self
*/
public function selfFiltering(bool $enabled): self
{
$this->selfFiltering = $enabled;
return $this;
}

/**
* Get self-filtering flag
* @return boolean
*/
public function hasSelfFiltering(): bool
{
return $this->selfFiltering;
}
}
7 changes: 7 additions & 0 deletions src/Filter/FilterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,11 @@ public function getFieldName(): string;
* @return void
*/
public function filterInput(array $facetedData, array &$inputIdKeys, array $excludeRecords): void;

/**
* Get self-filtering flag
*
* @return bool
*/
public function hasSelfFiltering(): bool;
}
23 changes: 18 additions & 5 deletions src/Index/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public function aggregate(AggregationQuery $query): array
$countValues,
$input,
$excludeMap,
$query->getSelfFiltering(),
$query->hasSelfFiltering(),
$filters
);

Expand Down Expand Up @@ -235,10 +235,22 @@ private function aggregationScan(
): array {
$result = [];
$cacheCount = count($resultCache);

$indexedFilters = [];
foreach ($filters as $filter) {
$indexedFilters[$filter->getFieldName()] = $filter;
}

/**
* @var array<int|string,array<int>> $filterValues
*/
foreach ($this->scanner->scan($this->storage) as $filterName => $filterValues) {

$needSelfFiltering = false;
if ($selfFiltering != false || (isset($indexedFilters[$filterName]) && $indexedFilters[$filterName]->hasSelfFiltering())) {
$needSelfFiltering = true;
}

/**
* @var string $filterName
*/
Expand All @@ -247,15 +259,16 @@ private function aggregationScan(
if ($cacheCount > 1) {
// optimization with cache of findRecordsMap
// do not apply self filtering
if ($selfFiltering == false) {
$skipKey = $filterName;
} else {
if ($needSelfFiltering) {
$skipKey = null;
} else {
$skipKey = $filterName;
}

$recordIds = $this->mergeFilters($resultCache, $skipKey);
} else {
// Selecting a self-filtering scenario
if ($selfFiltering) {
if ($needSelfFiltering) {
$recordIds = $this->scanner->findRecordsMap($this->storage, $filters, $input, $exclude);
} else {
$recordIds = $this->scanner->findRecordsMap($this->storage, [], $input, $exclude);
Expand Down
6 changes: 5 additions & 1 deletion src/Query/AggregationQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ public function selfFiltering(bool $enabled): self
return $this;
}

public function getSelfFiltering(): bool
/**
* Get self-filtering flag
* @return bool
*/
public function hasSelfFiltering(): bool
{
return $this->selfFiltering;
}
Expand Down
121 changes: 121 additions & 0 deletions tests/unit/Filter/SelfFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

use PHPUnit\Framework\TestCase;
use KSamuel\FacetedSearch\Filter\ValueFilter;
use KSamuel\FacetedSearch\Filter\ValueIntersectionFilter;
use KSamuel\FacetedSearch\Index\Factory;
use KSamuel\FacetedSearch\Index\IndexInterface;
use KSamuel\FacetedSearch\Query\AggregationQuery;

class SelfFilterTest extends TestCase
{
private function getIndex($type): IndexInterface
{
$index = (new Factory)->create(Factory::ARRAY_STORAGE);
$storage = $index->getStorage();
$data = [
1 => [
'brand' => 'Nony',
'first_usage' => ['weddings', 'wildlife'],
'second_usage' => ['wildlife', 'portraits']
],
2 => [
'brand' => 'Mikon',
'first_usage' => ['weddings', 'streetphoto'],
'second_usage' => ['wildlife', 'streetphoto']
],
3 => [
'brand' => 'Common',
'first_usage' => ['streetphoto', 'portraits'],
'second_usage' => ['streetphoto', 'portraits']
],
4 => [
'brand' => 'Digma',
'first_usage' => ['streetphoto', 'portraits', 'weddings'],
'second_usage' => ['streetphoto', 'portraits']
],
5 => [
'brand' => 'Digma',
'first_usage' => ['streetphoto'],
'second_usage' => ['portraits']
],
6 => [
'brand' => 'Mikon',
'first_usage' => ['weddings', 'wildlife'],
'second_usage' => ['wildlife', 'portraits']
]
];
foreach ($data as $k => $v) {
$storage->addRecord($k, $v);
}
$storage->optimize();
return $index;
}
public function testMixedFiltering(): void
{
$query1 = (new AggregationQuery())->filters([
new ValueFilter('brand', ['Nony', 'Digma', 'Mikon']),
(new ValueIntersectionFilter('first_usage', ['weddings']))->selfFiltering(true),
])->countItems()->sort();

$query2 = (new AggregationQuery())->filters([
new ValueFilter('brand', ['Nony', 'Digma']),
(new ValueIntersectionFilter('first_usage', ['weddings']))->selfFiltering(true),
])->countItems()->sort();

$expect = [
'brand' => [
'Digma' => 1,
'Mikon' => 2,
'Nony' => 1
],
'first_usage' => [
'portraits' => 1,
'streetphoto' => 2,
'weddings' => 4,
'wildlife' => 2
],
'second_usage' => [
'portraits' => 3,
'streetphoto' => 2,
'wildlife' => 3,
],
];

$expect2 = [
'brand' => [
'Digma' => 1,
'Nony' => 1,
'Mikon' => 2,
],
'first_usage' => [
'portraits' => 1,
'streetphoto' => 1,
'weddings' => 2,
'wildlife' => 1
],
'second_usage' => [
'portraits' => 2,
'streetphoto' => 1,
'wildlife' => 1,
],
];

$index = $this->getIndex(Factory::ARRAY_STORAGE);
$result = $index->aggregate($query1);

$this->assertEquals($expect, $result);

$result = $index->aggregate($query2);
$this->assertEquals($expect2, $result);


$index = $this->getIndex(Factory::FIXED_ARRAY_STORAGE);
$result = $index->aggregate($query1);

$this->assertEquals($expect, $result);

$result = $index->aggregate($query2);
$this->assertEquals($expect2, $result);
}
}
36 changes: 35 additions & 1 deletion tests/unit/Filter/ValueIntersectionFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ public function testAggregate(): void
new ValueIntersectionFilter('first_usage', ['wildlife', 'weddings', 'portraits']),
])->countItems()->sort()->selfFiltering(true);


$index = $this->getIndex(Factory::ARRAY_STORAGE);
$result = $index->aggregate($query1);
$this->assertEquals([
Expand Down Expand Up @@ -221,4 +220,39 @@ public function testAggregate(): void
$result = $index->aggregate($query4);
$this->assertEquals([], $result);
}

public function testSelfFiltering(): void
{
$query1 = (new AggregationQuery())->filters([
new ValueIntersectionFilter('first_usage', ['streetphoto', 'portraits', 'weddings']),
])->countItems()->sort()->selfFiltering(true);

$query2 = (new AggregationQuery())->filters([
(new ValueIntersectionFilter('first_usage', ['streetphoto', 'portraits', 'weddings']))->selfFiltering(true)
])->countItems()->sort();


$index = $this->getIndex(Factory::ARRAY_STORAGE);
$result = $index->aggregate($query1);
$result2 = $index->aggregate($query2);

$expected = [
'brand' => [
'Digma' => 1,
],
'first_usage' => [
'portraits' => 1,
'streetphoto' => 1,
'weddings' => 1,

],
'second_usage' => [
'streetphoto' => 1,
'portraits' => 1,
],
];

$this->assertEquals($expected, $result);
$this->assertEquals($expected, $result2);
}
}

0 comments on commit d582dea

Please sign in to comment.