Skip to content

Commit

Permalink
Merge pull request #348 from remi-san/not-expression
Browse files Browse the repository at this point in the history
`NOT` CompositeExpression in order to filter-out values
  • Loading branch information
greg0ire authored Nov 16, 2022
2 parents fc15508 + 4c9668a commit 6930dc3
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 9 deletions.
12 changes: 12 additions & 0 deletions docs/en/expression-builder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ orX
$collection->matching(new Criteria($expression));
not
---

.. code-block:: php
$expressionBuilder = Criteria::expr();
$expression = $expressionBuilder->not(
$expressionBuilder->eq('foo', 1)
);
$collection->matching(new Criteria($expression));
eq
---

Expand Down
15 changes: 14 additions & 1 deletion docs/en/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Expressions
===========

The ``Doctrine\Common\Collections\Expr\Comparison`` class
can be used to create expressions to be used with the
can be used to create comparison expressions to be used with the
``Doctrine\Common\Collections\Criteria`` class. It has the
following operator constants:

Expand All @@ -20,6 +20,19 @@ following operator constants:
- ``Comparison::STARTS_WITH``
- ``Comparison::ENDS_WITH``

The ``Doctrine\Common\Collections\Expr\CompositeExpression`` class
can be used to create composite expressions to be used with the
``Doctrine\Common\Collections\Criteria`` class. It has the
following operator constants:

- ``CompositeExpression::TYPE_AND``
- ``CompositeExpression::TYPE_OR``
- ``CompositeExpression::TYPE_NOT``

When using the ``TYPE_OR`` and ``TYPE_AND`` operators the
``CompositeExpression`` accepts multiple expressions as parameter
but only one expression can be provided when using the ``NOT`` operator.

The ``Doctrine\Common\Collections\Criteria`` class has the following
API to be used with expressions:

Expand Down
7 changes: 7 additions & 0 deletions src/Expr/ClosureExpressionVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ public function walkCompositeExpression(CompositeExpression $expr)
return match ($expr->getType()) {
CompositeExpression::TYPE_AND => $this->andExpressions($expressionList),
CompositeExpression::TYPE_OR => $this->orExpressions($expressionList),
CompositeExpression::TYPE_NOT => $this->notExpression($expressionList),
default => throw new RuntimeException('Unknown composite ' . $expr->getType()),
};
}
Expand Down Expand Up @@ -212,4 +213,10 @@ private function orExpressions(array $expressions): Closure
return false;
};
}

/** @param callable[] $expressions */
private function notExpression(array $expressions): Closure
{
return static fn ($object) => ! $expressions[0]($object);
}
}
7 changes: 7 additions & 0 deletions src/Expr/CompositeExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@

use RuntimeException;

use function count;

/**
* Expression of Expressions combined by AND or OR operation.
*/
class CompositeExpression implements Expression
{
final public const TYPE_AND = 'AND';
final public const TYPE_OR = 'OR';
final public const TYPE_NOT = 'NOT';

/** @var list<Expression> */
private array $expressions = [];
Expand All @@ -35,6 +38,10 @@ public function __construct(private readonly string $type, array $expressions)

$this->expressions[] = $expr;
}

if ($type === self::TYPE_NOT && count($this->expressions) !== 1) {
throw new RuntimeException('Not expression only allows one expression as child.');
}
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/ExpressionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public function orX(Expression ...$expressions)
return new CompositeExpression(CompositeExpression::TYPE_OR, $expressions);
}

public function not(Expression $expression): CompositeExpression
{
return new CompositeExpression(CompositeExpression::TYPE_NOT, [$expression]);
}

/** @return Comparison */
public function eq(string $field, mixed $value)
{
Expand Down
12 changes: 12 additions & 0 deletions tests/Common/Collections/ClosureExpressionVisitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,18 @@ public function testWalkAndOrCompositeExpression(): void
self::assertFalse($closure(new TestObject(0, 4)));
}

public function testWalkNotCompositeExpression(): void
{
$closure = $this->visitor->walkCompositeExpression(
$this->builder->not(
$this->builder->eq('foo', 1),
),
);

self::assertFalse($closure(new TestObject(1)));
self::assertTrue($closure(new TestObject(0)));
}

public function testWalkUnknownCompositeExpressionThrowException(): void
{
self::expectException(RuntimeException::class);
Expand Down
18 changes: 10 additions & 8 deletions tests/Common/Collections/Expr/CompositeExpressionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@
/** @covers \Doctrine\Common\Collections\Expr\CompositeExpression */
class CompositeExpressionTest extends TestCase
{
/** @return list<array{expression: mixed}> */
/** @return list<array{type:string, expressions: list<mixed>}> */
public function invalidDataProvider(): array
{
return [
['expression' => new Value('value')],
['expression' => 'wrong-type'],
['type' => CompositeExpression::TYPE_AND, 'expressions' => [new Value('value')]],
['type' => CompositeExpression::TYPE_AND, 'expressions' => ['wrong-type']],
['type' => CompositeExpression::TYPE_NOT, 'expressions' => [$this->createMock(Expression::class), $this->createMock(Expression::class)]],
];
}

/** @dataProvider invalidDataProvider */
public function testExceptions(string|Value $expression): void
/**
* @param list<mixed> $expressions
*
* @dataProvider invalidDataProvider
*/
public function testExceptions(string $type, array $expressions): void
{
$type = CompositeExpression::TYPE_AND;
$expressions = [$expression];

$this->expectException(RuntimeException::class);
new CompositeExpression($type, $expressions);
}
Expand Down

0 comments on commit 6930dc3

Please sign in to comment.