Passed
Pull Request — 2.1 (#71)
by Vincent
14:21 queued 08:17
created

BooleanExpressionParser::parse()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
c 1
b 0
f 0
dl 0
loc 22
ccs 12
cts 12
cp 1
rs 9.2222
cc 6
nc 6
nop 1
crap 6
1
<?php
2
3
namespace Bdf\Prime\Query\Closure\Parser;
4
5
use Bdf\Prime\Query\Closure\Filter\AndFilter;
6
use Bdf\Prime\Query\Closure\Filter\AtomicFilter;
7
use Bdf\Prime\Query\Closure\Filter\OrFilter;
8
use Bdf\Prime\Query\Closure\Value\ConstantValue;
9
use Exception;
10
use PhpParser\Node;
11
use RuntimeException;
12
13
use function count;
14
15
/**
16
 * Parse a boolean expression
17
 *
18
 * Handle:
19
 * - binary operations (e.g. $entity->foo >= 1)
20
 * - function call (e.g. in_array($entity->foo, [1, 2, 3]))
21
 * - unary boolean expression (e.g. !$entity->foo)
22
 * - And expression (e.g. $entity->foo > 5 && $entity->bar < 42)
23
 * - Or expression (e.g. $entity->foo || $entity->bar > 42)
24
 */
25
final class BooleanExpressionParser
26
{
27
    private const OPERATORS_MAPPING = [
28
        '==' => '=',
29
        '===' => '=',
30
        '!==' => '!=',
31
    ];
32
33
    private EntityAccessorParser $entityAccessorParser;
34
    private FunctionCallParser $functionCallParser;
35
    private ValueParser $valueParser;
36
37
    /**
38
     * @param EntityAccessorParser $entityAccessorParser
39
     * @param FunctionCallParser $functionCallParser
40
     * @param ValueParser $valueParser
41
     */
42 24
    public function __construct(EntityAccessorParser $entityAccessorParser, FunctionCallParser $functionCallParser, ValueParser $valueParser)
43
    {
44 24
        $this->entityAccessorParser = $entityAccessorParser;
45 24
        $this->functionCallParser = $functionCallParser;
46 24
        $this->valueParser = $valueParser;
47
    }
48
49
    /**
50
     * @param Node\Expr $expr
51
     * @return AndFilter
52
     */
53 24
    public function parse(Node\Expr $expr): AndFilter
54
    {
55 24
        if ($expr instanceof Node\Expr\BinaryOp\BooleanAnd) {
56 6
            return new AndFilter($this->parse($expr->left), $this->parse($expr->right));
57
        }
58
59 24
        if ($expr instanceof Node\Expr\BinaryOp\BooleanOr) {
60 3
            return new AndFilter($this->parseOrExpression($expr));
61
        }
62
63 24
        if ($expr instanceof Node\Expr\BinaryOp) {
64 21
            return new AndFilter($this->parseBinaryOperation($expr));
65
        }
66
67 9
        if ($expr instanceof Node\Expr\FuncCall) {
68 8
            return new AndFilter($this->functionCallParser->parse($expr));
69
        }
70
71
        try {
72 3
            return new AndFilter($this->parseUnaryBooleanExpression($expr));
73 1
        } catch (Exception $e) {
74 1
            throw new RuntimeException('Unsupported expression ' . $expr->getType() . ' in filters. Supported expressions are: binary operations, function calls getter for a boolean, and not expression.');
75
        }
76
    }
77
78 21
    private function parseBinaryOperation(Node\Expr\BinaryOp $expr): AtomicFilter
79
    {
80 21
        return new AtomicFilter(
81 21
            $this->entityAccessorParser->parse($expr->left),
82 21
            self::OPERATORS_MAPPING[$expr->getOperatorSigil()] ?? $expr->getOperatorSigil(),
83 21
            $this->valueParser->parse($expr->right),
84 21
        );
85
    }
86
87 3
    private function parseUnaryBooleanExpression(Node\Expr $expr): AtomicFilter
88
    {
89 3
        if ($expr instanceof Node\Expr\BooleanNot) {
90 2
            $expr = $expr->expr;
91 2
            $value = false;
92
        } else {
93 3
            $value = true;
94
        }
95
96
        // Simple boolean expression
97 3
        return new AtomicFilter(
98 3
            $this->entityAccessorParser->parse($expr),
99 3
            '=',
100 3
            new ConstantValue($value),
101 3
        );
102
    }
103
104 3
    private function parseOrExpression(Node\Expr\BinaryOp\BooleanOr $expr): OrFilter
105
    {
106 3
        $left = $this->parse($expr->left);
107 3
        $right = $this->parse($expr->right);
108
109 3
        $filters = [];
110
111 3
        if (count($left) === 1 && $left[0] instanceof OrFilter) {
112 2
            $filters = $left[0]->filters;
113
        } else {
114 3
            $filters[] = $left;
115
        }
116
117 3
        if (count($right) === 1 && $right[0] instanceof OrFilter) {
118
            $filters = [...$filters, ...$right[0]->filters];
119
        } else {
120 3
            $filters[] = $right;
121
        }
122
123 3
        return new OrFilter($filters);
124
    }
125
}
126