diff --git a/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php index 3d789fff16d..f6577f9896a 100644 --- a/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php +++ b/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php @@ -18,6 +18,7 @@ * * @author Lee Siong Chan * @author Alan Poulain + * @author Samuel Chiriluta */ interface RangeFilterInterface { @@ -26,4 +27,5 @@ interface RangeFilterInterface public const PARAMETER_GREATER_THAN_OR_EQUAL = 'gte'; public const PARAMETER_LESS_THAN = 'lt'; public const PARAMETER_LESS_THAN_OR_EQUAL = 'lte'; + public const PARAMETER_NOT_EQUAL = 'ne'; } diff --git a/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php index d226cd21dbc..3b8679c51a3 100644 --- a/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php +++ b/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php @@ -22,6 +22,7 @@ * * @author Lee Siong Chan * @author Alan Poulain + * @author Samuel Chiriluta */ trait RangeFilterTrait { @@ -49,6 +50,7 @@ public function getDescription(string $resourceClass): array $description += $this->getFilterDescription($property, self::PARAMETER_GREATER_THAN_OR_EQUAL); $description += $this->getFilterDescription($property, self::PARAMETER_LESS_THAN); $description += $this->getFilterDescription($property, self::PARAMETER_LESS_THAN_OR_EQUAL); + $description += $this->getFilterDescription($property, self::PARAMETER_NOT_EQUAL); } return $description; @@ -78,7 +80,7 @@ protected function getFilterDescription(string $fieldName, string $operator): ar private function normalizeValues(array $values, string $property): ?array { - $operators = [self::PARAMETER_BETWEEN, self::PARAMETER_GREATER_THAN, self::PARAMETER_GREATER_THAN_OR_EQUAL, self::PARAMETER_LESS_THAN, self::PARAMETER_LESS_THAN_OR_EQUAL]; + $operators = [self::PARAMETER_BETWEEN, self::PARAMETER_GREATER_THAN, self::PARAMETER_GREATER_THAN_OR_EQUAL, self::PARAMETER_LESS_THAN, self::PARAMETER_LESS_THAN_OR_EQUAL, self::PARAMETER_NOT_EQUAL]; foreach ($values as $operator => $value) { if (!\in_array($operator, $operators, true)) { diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php index 394bd284f79..d4f6664b28b 100644 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php @@ -24,6 +24,7 @@ * * @author Lee Siong Chan * @author Alan Poulain + * @author Samuel Chiriluta */ final class RangeFilter extends AbstractFilter implements RangeFilterInterface { @@ -122,6 +123,15 @@ protected function addMatch(Builder $aggregationBuilder, string $field, string $ $aggregationBuilder->match()->field($matchField)->lte($value); + break; + case self::PARAMETER_NOT_EQUAL: + $value = $this->normalizeValue($value, $operator); + if (null === $value) { + return; + } + + $aggregationBuilder->match()->field($matchField)->notEqual($value); + break; } } diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php index ead733e20e2..e04f6f242cd 100644 --- a/src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php +++ b/src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php @@ -22,6 +22,7 @@ * Filters the collection by range. * * @author Lee Siong Chan + * @author Samuel Chiriluta * * @final */ @@ -144,6 +145,17 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf ->andWhere(sprintf('%s.%s <= :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); + break; + case self::PARAMETER_NOT_EQUAL: + $value = $this->normalizeValue($value, $operator); + if (null === $value) { + return; + } + + $queryBuilder + ->andWhere(sprintf('%s.%s <> :%s', $alias, $field, $valueParameter)) + ->setParameter($valueParameter, $value); + break; } } diff --git a/tests/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilterTest.php b/tests/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilterTest.php index 31fb18bf5ce..27293ba033e 100644 --- a/tests/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilterTest.php +++ b/tests/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilterTest.php @@ -21,6 +21,7 @@ * @group mongodb * * @author Alan Poulain + * @author Samuel Chiriluta */ class RangeFilterTest extends DoctrineMongoDbOdmFilterTestCase { @@ -58,6 +59,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'id[ne]' => [ + 'property' => 'id', + 'type' => 'string', + 'required' => false, + ], 'name[between]' => [ 'property' => 'name', 'type' => 'string', @@ -83,6 +89,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'name[ne]' => [ + 'property' => 'name', + 'type' => 'string', + 'required' => false, + ], 'alias[between]' => [ 'property' => 'alias', 'type' => 'string', @@ -108,6 +119,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'alias[ne]' => [ + 'property' => 'alias', + 'type' => 'string', + 'required' => false, + ], 'description[between]' => [ 'property' => 'description', 'type' => 'string', @@ -133,6 +149,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'description[ne]' => [ + 'property' => 'description', + 'type' => 'string', + 'required' => false, + ], 'dummy[between]' => [ 'property' => 'dummy', 'type' => 'string', @@ -158,6 +179,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummy[ne]' => [ + 'property' => 'dummy', + 'type' => 'string', + 'required' => false, + ], 'dummyDate[between]' => [ 'property' => 'dummyDate', 'type' => 'string', @@ -183,6 +209,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummyDate[ne]' => [ + 'property' => 'dummyDate', + 'type' => 'string', + 'required' => false, + ], 'dummyFloat[between]' => [ 'property' => 'dummyFloat', 'type' => 'string', @@ -208,6 +239,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummyFloat[ne]' => [ + 'property' => 'dummyFloat', + 'type' => 'string', + 'required' => false, + ], 'dummyPrice[between]' => [ 'property' => 'dummyPrice', 'type' => 'string', @@ -233,6 +269,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummyPrice[ne]' => [ + 'property' => 'dummyPrice', + 'type' => 'string', + 'required' => false, + ], 'jsonData[between]' => [ 'property' => 'jsonData', 'type' => 'string', @@ -258,6 +299,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'jsonData[ne]' => [ + 'property' => 'jsonData', + 'type' => 'string', + 'required' => false, + ], 'arrayData[between]' => [ 'property' => 'arrayData', 'type' => 'string', @@ -283,6 +329,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'arrayData[ne]' => [ + 'property' => 'arrayData', + 'type' => 'string', + 'required' => false, + ], 'nameConverted[between]' => [ 'property' => 'nameConverted', 'type' => 'string', @@ -308,6 +359,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'nameConverted[ne]' => [ + 'property' => 'nameConverted', + 'type' => 'string', + 'required' => false, + ], 'dummyBoolean[between]' => [ 'property' => 'dummyBoolean', 'type' => 'string', @@ -333,6 +389,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummyBoolean[ne]' => [ + 'property' => 'dummyBoolean', + 'type' => 'string', + 'required' => false, + ], 'relatedDummy[between]' => [ 'property' => 'relatedDummy', 'type' => 'string', @@ -358,6 +419,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'relatedDummy[ne]' => [ + 'property' => 'relatedDummy', + 'type' => 'string', + 'required' => false, + ], 'relatedDummies[between]' => [ 'property' => 'relatedDummies', 'type' => 'string', @@ -383,6 +449,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'relatedDummies[ne]' => [ + 'property' => 'relatedDummies', + 'type' => 'string', + 'required' => false, + ], 'relatedOwnedDummy[between]' => [ 'property' => 'relatedOwnedDummy', 'type' => 'string', @@ -408,6 +479,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'relatedOwnedDummy[ne]' => [ + 'property' => 'relatedOwnedDummy', + 'type' => 'string', + 'required' => false, + ], 'relatedOwningDummy[between]' => [ 'property' => 'relatedOwningDummy', 'type' => 'string', @@ -433,6 +509,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'relatedOwningDummy[ne]' => [ + 'property' => 'relatedOwningDummy', + 'type' => 'string', + 'required' => false, + ], ], $filter->getDescription($this->resourceClass)); } @@ -545,6 +626,20 @@ public function provideApplyTestData(): array ], ], ], + 'ne' => [ + [ + [ + '$match' => [ + 'dummyPrice' => [ + '$notEqual' => 9.99, + ], + ], + ], + ], + ], + 'ne (non-numeric)' => [ + [], + ], ] ); } diff --git a/tests/Core/Bridge/Doctrine/Common/Filter/RangeFilterTestTrait.php b/tests/Core/Bridge/Doctrine/Common/Filter/RangeFilterTestTrait.php index 73bab437ff9..0e8c1405a33 100644 --- a/tests/Core/Bridge/Doctrine/Common/Filter/RangeFilterTestTrait.php +++ b/tests/Core/Bridge/Doctrine/Common/Filter/RangeFilterTestTrait.php @@ -15,6 +15,7 @@ /** * @author Lee Siong Chan + * @author Samuel Chiriluta */ trait RangeFilterTestTrait { @@ -134,6 +135,22 @@ private function provideApplyTestArguments(): array ], ], ], + 'ne' => [ + null, + [ + 'dummyPrice' => [ + 'ne' => '9.99', + ], + ], + ], + 'ne (non-numeric)' => [ + null, + [ + 'dummyPrice' => [ + 'ne' => '127.0.0.1', + ], + ], + ], ]; } } diff --git a/tests/Core/Bridge/Doctrine/Orm/Filter/RangeFilterTest.php b/tests/Core/Bridge/Doctrine/Orm/Filter/RangeFilterTest.php index 971911e12fb..4434a88c767 100644 --- a/tests/Core/Bridge/Doctrine/Orm/Filter/RangeFilterTest.php +++ b/tests/Core/Bridge/Doctrine/Orm/Filter/RangeFilterTest.php @@ -20,6 +20,7 @@ /** * @author Lee Siong Chan + * @author Samuel Chiriluta */ class RangeFilterTest extends DoctrineOrmFilterTestCase { @@ -57,6 +58,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'id[ne]' => [ + 'property' => 'id', + 'type' => 'string', + 'required' => false, + ], 'name[between]' => [ 'property' => 'name', 'type' => 'string', @@ -82,6 +88,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'name[ne]' => [ + 'property' => 'name', + 'type' => 'string', + 'required' => false, + ], 'alias[between]' => [ 'property' => 'alias', 'type' => 'string', @@ -107,6 +118,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'alias[ne]' => [ + 'property' => 'alias', + 'type' => 'string', + 'required' => false, + ], 'description[between]' => [ 'property' => 'description', 'type' => 'string', @@ -132,6 +148,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'description[ne]' => [ + 'property' => 'description', + 'type' => 'string', + 'required' => false, + ], 'dummy[between]' => [ 'property' => 'dummy', 'type' => 'string', @@ -157,6 +178,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummy[ne]' => [ + 'property' => 'dummy', + 'type' => 'string', + 'required' => false, + ], 'dummyDate[between]' => [ 'property' => 'dummyDate', 'type' => 'string', @@ -182,6 +208,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummyDate[ne]' => [ + 'property' => 'dummyDate', + 'type' => 'string', + 'required' => false, + ], 'dummyFloat[between]' => [ 'property' => 'dummyFloat', 'type' => 'string', @@ -207,6 +238,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummyFloat[ne]' => [ + 'property' => 'dummyFloat', + 'type' => 'string', + 'required' => false, + ], 'dummyPrice[between]' => [ 'property' => 'dummyPrice', 'type' => 'string', @@ -232,6 +268,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummyPrice[ne]' => [ + 'property' => 'dummyPrice', + 'type' => 'string', + 'required' => false, + ], 'jsonData[between]' => [ 'property' => 'jsonData', 'type' => 'string', @@ -257,6 +298,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'jsonData[ne]' => [ + 'property' => 'jsonData', + 'type' => 'string', + 'required' => false, + ], 'arrayData[between]' => [ 'property' => 'arrayData', 'type' => 'string', @@ -282,6 +328,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'arrayData[ne]' => [ + 'property' => 'arrayData', + 'type' => 'string', + 'required' => false, + ], 'nameConverted[between]' => [ 'property' => 'nameConverted', 'type' => 'string', @@ -307,6 +358,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'nameConverted[ne]' => [ + 'property' => 'nameConverted', + 'type' => 'string', + 'required' => false, + ], 'dummyBoolean[between]' => [ 'property' => 'dummyBoolean', 'type' => 'string', @@ -332,6 +388,11 @@ public function testGetDescriptionDefaultFields() 'type' => 'string', 'required' => false, ], + 'dummyBoolean[ne]' => [ + 'property' => 'dummyBoolean', + 'type' => 'string', + 'required' => false, + ], ], $filter->getDescription($this->resourceClass)); } @@ -382,6 +443,12 @@ public function provideApplyTestData(): array 'lte + gte' => [ sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1 AND o.dummyPrice <= :dummyPrice_p2', Dummy::class), ], + 'ne' => [ + sprintf('SELECT o FROM %s o WHERE o.dummyPrice <> :dummyPrice_p1', Dummy::class), + ], + 'ne (non-numeric)' => [ + sprintf('SELECT o FROM %s o', Dummy::class), + ], ] ); }