Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Add PropertyValueCriteriaMatcher #4701

Merged
merged 7 commits into from
Mar 11, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

declare(strict_types=1);

namespace Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue;

use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\AndCriteria;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\NegateCriteria;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\OrCriteria;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueContains;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueCriteriaInterface;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueEndsWith;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueEquals;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueGreaterThan;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueGreaterThanOrEqual;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueLessThan;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueLessThanOrEqual;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueStartsWith;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\Projection\ContentGraph\PropertyCollection;

/**
* Performs property checks against a given set of constraints
*
* @internal
*/
final class PropertyValueCriteriaMatcher
{
public static function matchesNode(Node $node, PropertyValueCriteriaInterface $propertyValueCriteria): bool
{
return static::matchesSerializedPropertyValues($node->properties->serialized(), $propertyValueCriteria);
}

public static function matchesPropertyCollection(PropertyCollection $propertyCollection, PropertyValueCriteriaInterface $propertyValueCriteria): bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like to propose to use here as first parameter the serialised node properties directly. Using the higher level object is not necessary and might even introduce bugs as the matching should only happen on the serialised properties. Also it will be easier to test.

A last testrun is now triggered at #4898 but i assume everything will be green and youll get my approval :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this is an actual benefit. In which usecase would one have serialized properties but not the propertyCollection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduded matchesSerializedPropertyValues as separate method that is used by matchNode and matchPropertyCollection

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thänks. My reasoning here was just simply that when developing i came mostly across the SerializedPropertyValues and not the abstraction of the PropertyCollection which is not always available and harder to construct as it needs the serialiser. Also by passing in the serialised properties it becomes more obvious that the filtering happens on that level..

{
return static::matchesSerializedPropertyValues($propertyCollection->serialized(), $propertyValueCriteria);
}

public static function matchesSerializedPropertyValues(SerializedPropertyValues $serializedPropertyValues, PropertyValueCriteriaInterface $propertyValueCriteria): bool
{
return match ($propertyValueCriteria::class) {
AndCriteria::class => self::matchesSerializedPropertyValues($serializedPropertyValues, $propertyValueCriteria->criteria1)
&& self::matchesSerializedPropertyValues($serializedPropertyValues, $propertyValueCriteria->criteria2),
OrCriteria::class => self::matchesSerializedPropertyValues($serializedPropertyValues, $propertyValueCriteria->criteria1)
|| self::matchesSerializedPropertyValues($serializedPropertyValues, $propertyValueCriteria->criteria2),
NegateCriteria::class => !self::matchesSerializedPropertyValues($serializedPropertyValues, $propertyValueCriteria->criteria),
PropertyValueContains::class => self::propertyValueContains($serializedPropertyValues, $propertyValueCriteria),
PropertyValueEndsWith::class => self::propertyValueEndsWith($serializedPropertyValues, $propertyValueCriteria),
PropertyValueStartsWith::class => self::propertyValueStartsWith($serializedPropertyValues, $propertyValueCriteria),
PropertyValueEquals::class => self::propertyValueEquals($serializedPropertyValues, $propertyValueCriteria),
PropertyValueGreaterThan::class => $serializedPropertyValues->propertyExists($propertyValueCriteria->propertyName->value)
&& $serializedPropertyValues->getProperty($propertyValueCriteria->propertyName->value)?->value > $propertyValueCriteria->value,
PropertyValueGreaterThanOrEqual::class => $serializedPropertyValues->propertyExists($propertyValueCriteria->propertyName->value)
&& $serializedPropertyValues->getProperty($propertyValueCriteria->propertyName->value)?->value >= $propertyValueCriteria->value,
PropertyValueLessThan::class => $serializedPropertyValues->propertyExists($propertyValueCriteria->propertyName->value)
&& $serializedPropertyValues->getProperty($propertyValueCriteria->propertyName->value)?->value < $propertyValueCriteria->value,
PropertyValueLessThanOrEqual::class => $serializedPropertyValues->propertyExists($propertyValueCriteria->propertyName->value)
&& $serializedPropertyValues->getProperty($propertyValueCriteria->propertyName->value)?->value <= $propertyValueCriteria->value,
default => throw new \InvalidArgumentException(sprintf('Invalid/unsupported property value criteria "%s"', get_debug_type($propertyValueCriteria)), 1679561073),
};
}

private static function propertyValueContains(SerializedPropertyValues $serializedPropertyValues, PropertyValueContains $propertyValueCriteria): bool
{
$propertyValue = $serializedPropertyValues->getProperty($propertyValueCriteria->propertyName->value)?->value;
if ($propertyValueCriteria->caseSensitive) {
return is_string($propertyValue) ? str_contains($propertyValue, $propertyValueCriteria->value) : false;
} else {
return is_string($propertyValue) ? str_contains(mb_strtolower($propertyValue), mb_strtolower($propertyValueCriteria->value)) : false;
}
}

private static function propertyValueStartsWith(SerializedPropertyValues $serializedPropertyValues, PropertyValueStartsWith $propertyValueCriteria): bool
{
$propertyValue = $serializedPropertyValues->getProperty($propertyValueCriteria->propertyName->value)?->value;
if ($propertyValueCriteria->caseSensitive) {
return is_string($propertyValue) ? str_starts_with($propertyValue, $propertyValueCriteria->value) : false;
} else {
return is_string($propertyValue) ? str_starts_with(mb_strtolower($propertyValue), mb_strtolower($propertyValueCriteria->value)) : false;
}
}

private static function propertyValueEndsWith(SerializedPropertyValues $serializedPropertyValues, PropertyValueEndsWith $propertyValueCriteria): bool
{
$propertyValue = $serializedPropertyValues->getProperty($propertyValueCriteria->propertyName->value)?->value;
if ($propertyValueCriteria->caseSensitive) {
return is_string($propertyValue) ? str_ends_with($propertyValue, $propertyValueCriteria->value) : false;
} else {
return is_string($propertyValue) ? str_ends_with(mb_strtolower($propertyValue), mb_strtolower($propertyValueCriteria->value)) : false;
}
}

private static function propertyValueEquals(SerializedPropertyValues $serializedPropertyValues, PropertyValueEquals $propertyValueCriteria): bool
{
if (!$serializedPropertyValues->propertyExists($propertyValueCriteria->propertyName->value)) {
return false;
}
$propertyValue = $serializedPropertyValues->getProperty($propertyValueCriteria->propertyName->value)?->value;
if ($propertyValueCriteria->caseSensitive) {
return $propertyValue === $propertyValueCriteria->value;
}
return (is_string($propertyValue) ? mb_strtolower($propertyValue) : $propertyValue) === (is_string($propertyValueCriteria->value) ? mb_strtolower($propertyValueCriteria->value) : $propertyValueCriteria->value);
}
}
Loading
Loading