-
-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FEATURE: Introduce
SearchTermMatcher
implemented according to datab…
…ase filtering
- Loading branch information
Showing
2 changed files
with
175 additions
and
0 deletions.
There are no files selected for viewing
44 changes: 44 additions & 0 deletions
44
...ntRepository.Core/Classes/Projection/ContentGraph/Filter/SearchTerm/SearchTermMatcher.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Neos\ContentRepository\Core\Projection\ContentGraph\Filter\SearchTerm; | ||
|
||
use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValue; | ||
use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Node; | ||
|
||
/** | ||
* Performs search term check against the nodes properties | ||
* | ||
* @internal | ||
*/ | ||
class SearchTermMatcher | ||
{ | ||
public static function matchesNode(Node $node, SearchTerm $searchTerm): bool | ||
{ | ||
return static::matchesSerializedPropertyValues($node->properties->serialized(), $searchTerm); | ||
} | ||
|
||
public static function matchesSerializedPropertyValues(SerializedPropertyValues $serializedPropertyValues, SearchTerm $searchTerm): bool | ||
{ | ||
foreach ($serializedPropertyValues as $serializedPropertyValue) { | ||
if (self::matchesSerializedPropertyValue($serializedPropertyValue, $searchTerm)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private static function matchesSerializedPropertyValue(SerializedPropertyValue $serializedPropertyValue, SearchTerm $searchTerm): bool | ||
{ | ||
return match (true) { | ||
is_string($serializedPropertyValue->value) => mb_stripos($serializedPropertyValue->value, $searchTerm->term) !== false, | ||
// the following behaviour might seem odd, but is implemented after how the database filtering should behave | ||
is_int($serializedPropertyValue->value), | ||
is_float($serializedPropertyValue->value) => str_contains((string)$serializedPropertyValue->value, $searchTerm->term), | ||
$serializedPropertyValue->value === true => $searchTerm->term === 'true', | ||
$serializedPropertyValue->value === false => $searchTerm->term === 'false' | ||
}; | ||
} | ||
} |
131 changes: 131 additions & 0 deletions
131
...itory.Core/Tests/Unit/Projection/ContentGraph/Filter/SearchTerm/SearchTermMatcherTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Neos\ContentRepository\Core\Tests\Unit\Projection\ContentGraph\Filter\SearchTerm; | ||
|
||
use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValue; | ||
use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\SearchTerm\SearchTerm; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\SearchTerm\SearchTermMatcher; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class SearchTermMatcherTest extends TestCase | ||
{ | ||
public function matchingStringComparisonExamples(): iterable | ||
{ | ||
yield 'string found inside string' => ['brown', self::value('the brown fox')]; | ||
yield 'string found inside string (ci)' => ['BrOWn', self::value('the brown fox')]; | ||
|
||
yield 'string found inside multibyte string (ci)' => ['Äpfel', self::value('Auer schreit der bauer die äpfel sind zu sauer.')]; | ||
|
||
yield 'string matches full string' => ['Sheep', self::value('Sheep')]; | ||
yield 'string matches full string (ci)' => ['sheep', self::value('Sheep')]; | ||
|
||
yield 'string found inside string with special chars' => ['ä b-c+#@', self::value('the example: "ä b-c+#@"')]; | ||
} | ||
|
||
public function matchingNumberLikeComparisonExamples(): iterable | ||
{ | ||
yield 'string-number found inside string' => [ | ||
'22', | ||
self::value('feeling like 22 ;)'), | ||
]; | ||
|
||
yield 'string-number found inside string-number' => [ | ||
'00', | ||
self::value('007'), | ||
]; | ||
|
||
yield 'string-number found inside int' => [ | ||
'23', | ||
self::value(1234), | ||
]; | ||
|
||
yield 'string-number found inside float' => [ | ||
'23', | ||
self::value(1234.56), | ||
]; | ||
|
||
yield 'string-float matches float' => [ | ||
'1234.56', | ||
self::value(1234.56), | ||
]; | ||
|
||
yield 'string-int matches int' => [ | ||
'0', | ||
self::value(0), | ||
]; | ||
} | ||
|
||
public function matchingBooleanLikeComparisonExamples(): iterable | ||
{ | ||
yield 'string-boolean inside string' => [ | ||
'true', | ||
self::value('this is true'), | ||
]; | ||
|
||
yield 'string-true matches boolean' => [ | ||
'true', | ||
self::value(true), | ||
]; | ||
|
||
yield 'string-false matches boolean' => [ | ||
'false', | ||
self::value(false), | ||
]; | ||
} | ||
|
||
public function notMatchingExamples(): iterable | ||
{ | ||
yield 'different chars' => ['aepfel', self::value('äpfel')]; | ||
yield 'upper boolean string representation' => ['TRUE', self::value(true)]; | ||
yield 'string not found inside string' => ['reptv', self::value('eras tour')]; | ||
yield 'integer' => ['0999', self::value(999)]; | ||
yield 'float with comma' => ['12,45', self::value(12.34)]; | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider matchingStringComparisonExamples | ||
* @dataProvider matchingNumberLikeComparisonExamples | ||
* @dataProvider matchingBooleanLikeComparisonExamples | ||
*/ | ||
public function searchTermMatchesProperties( | ||
string $searchTerm, | ||
SerializedPropertyValues $properties, | ||
) { | ||
self::assertTrue( | ||
SearchTermMatcher::matchesSerializedPropertyValues( | ||
$properties, | ||
SearchTerm::fulltext($searchTerm) | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider notMatchingExamples | ||
*/ | ||
public function searchTermDoesntMatchesProperties( | ||
string $searchTerm, | ||
SerializedPropertyValues $properties, | ||
) { | ||
self::assertFalse( | ||
SearchTermMatcher::matchesSerializedPropertyValues( | ||
$properties, | ||
SearchTerm::fulltext($searchTerm) | ||
) | ||
); | ||
} | ||
|
||
private static function value(string|bool|float|int $value): SerializedPropertyValues | ||
{ | ||
return SerializedPropertyValues::fromArray([ | ||
'test-property' => SerializedPropertyValue::create( | ||
$value, | ||
gettype($value) | ||
), | ||
]); | ||
} | ||
} |