Skip to content

Commit 75b4910

Browse files
author
Mikolaj Misiurewicz
committed
feat(search): split range queries with multiple ranges to a union of ranges
1 parent 0b59677 commit 75b4910

File tree

2 files changed

+124
-11
lines changed

2 files changed

+124
-11
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Context;
5+
6+
use Markup\NeedleBundle\Attribute\AttributeProviderInterface;
7+
use Markup\NeedleBundle\Attribute\SpecializationContextHashInterface;
8+
use Markup\NeedleBundle\Context\ContextFilterFactory;
9+
use Markup\NeedleBundle\Facet\RangeFacetField;
10+
use Markup\NeedleBundle\Filter\FilterValueInterface;
11+
use Markup\NeedleBundle\Filter\RangeFilterValue;
12+
use Markup\NeedleBundle\Filter\UnionFilterValue;
13+
use PHPUnit\Framework\TestCase;
14+
15+
class ContextFilterFactoryTest extends TestCase
16+
{
17+
/**
18+
* @dataProvider rangeUnionDataProvider
19+
*
20+
* @param float $rangeSize
21+
* @param mixed $filterValue
22+
* @param FilterValueInterface|null $expectedFilterValue
23+
* @param bool $throwsException
24+
*/
25+
public function testRangeUnion(
26+
$filterValue,
27+
float $rangeSize,
28+
?FilterValueInterface $expectedFilterValue,
29+
bool $throwsException
30+
): void {
31+
$attributeMock = $this->createMock(RangeFacetField::class);
32+
$attributeMock->method('getRangeSize')->willReturn($rangeSize);
33+
34+
$attributeProviderMock = $this->createMock(AttributeProviderInterface::class);
35+
$attributeProviderMock->method('getAttributeByName')->willReturn($attributeMock);
36+
37+
$specializationContextHashMock = $this->createMock(SpecializationContextHashInterface::class);
38+
39+
$factory = new ContextFilterFactory($attributeProviderMock);
40+
if ($throwsException) {
41+
$this->expectException(\RuntimeException::class);
42+
}
43+
$result = $factory->create('', $filterValue, $specializationContextHashMock);
44+
if (!$result) {
45+
$this->assertSame($expectedFilterValue, $result);
46+
} else {
47+
$this->assertEquals($expectedFilterValue, $result->getFilterValue());
48+
}
49+
}
50+
51+
public function rangeUnionDataProvider(): array
52+
{
53+
return [
54+
'two simple values' => [
55+
['50', '150'],
56+
50,
57+
new UnionFilterValue([
58+
new RangeFilterValue(50, 99.99),
59+
new RangeFilterValue(150, 199.99),
60+
]),
61+
false,
62+
],
63+
'multiple overlapping values not in order' => [
64+
['87.51', '42.84', '150.01'],
65+
50.5,
66+
new UnionFilterValue([
67+
new RangeFilterValue(87.51, 138),
68+
new RangeFilterValue(42.84, 93.33),
69+
new RangeFilterValue(150.01, 200.5),
70+
]),
71+
false,
72+
],
73+
'one value' => [
74+
['75'],
75+
2.5,
76+
new RangeFilterValue(75, 77.49),
77+
false,
78+
],
79+
'explicit min and max' => [
80+
['min' => 15.2, 'max' => 99.5],
81+
-256,
82+
new RangeFilterValue(15.2, 99.5),
83+
false,
84+
],
85+
'zero values' => [
86+
[],
87+
50,
88+
null,
89+
true,
90+
],
91+
'value not an array' => [
92+
'150',
93+
50,
94+
null,
95+
true,
96+
]
97+
];
98+
}
99+
}

src/Context/ContextFilterFactory.php

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,39 @@ function ($value) {
6666
return new SearchFilter\FilterQuery($attribute, new SearchFilter\ScalarFilterValue($filterValue));
6767
}
6868

69-
// if the value can/should be interpreted as a range filter value then one is returned
70-
private function getRangeFilterValue(AttributeInterface $attribute, $filterValue): ?SearchFilter\RangeFilterValue
71-
{
69+
/**
70+
* If the value should be interpreted as a range filter or a collection of range filters then one is returned.
71+
*
72+
* @param AttributeInterface $attribute
73+
* @param mixed $filterValue
74+
* @return SearchFilter\FilterValueInterface|null
75+
*/
76+
private function getRangeFilterValue(
77+
AttributeInterface $attribute,
78+
$filterValue
79+
): ?SearchFilter\FilterValueInterface {
7280
if ($attribute instanceof RangeFacetField) {
73-
if (is_array($filterValue) && isset($filterValue['min'], $filterValue['max'])) {
81+
if (!is_array($filterValue) || empty($filterValue)) {
82+
throw new \RuntimeException('Cannot provide range filter value given the passed filter values');
83+
}
84+
85+
if (isset($filterValue['min'], $filterValue['max'])) {
7486
return new SearchFilter\RangeFilterValue(
7587
floatval($filterValue['min']),
7688
floatval($filterValue['max'])
7789
);
7890
}
7991

80-
if (is_array($filterValue)) {
81-
$min = floatval(min($filterValue));
82-
$max = floatval(max($filterValue)) + (floatval($attribute->getRangeSize()) - 0.01);
83-
84-
return new SearchFilter\RangeFilterValue($min, $max);
92+
$ranges = [];
93+
foreach ($filterValue as $min) {
94+
$min = floatval($min);
95+
$max = $min + $attribute->getRangeSize() - 0.01;
96+
$ranges[] = new SearchFilter\RangeFilterValue($min, $max);
8597
}
86-
87-
throw new \RuntimeException('Cannot provide range filter value given the passed filter values');
98+
if (count($ranges) === 1) {
99+
return $ranges[0];
100+
}
101+
return new SearchFilter\UnionFilterValue($ranges);
88102
}
89103

90104
if (!$attribute instanceof FloatAttributeInterface) {

0 commit comments

Comments
 (0)