Skip to content

Commit

Permalink
FEATURE: Introduce SearchTermMatcher implemented according to datab…
Browse files Browse the repository at this point in the history
…ase filtering
  • Loading branch information
mhsdesign committed Aug 4, 2024
1 parent 989e7c5 commit ef93f94
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 0 deletions.
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'
};
}
}
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)
),
]);
}
}

0 comments on commit ef93f94

Please sign in to comment.