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

ValueParser::parse()   B

Complexity

Conditions 11
Paths 9

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 11.0207

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
c 1
b 0
f 0
dl 0
loc 35
ccs 17
cts 18
cp 0.9444
rs 7.3166
cc 11
nc 9
nop 1
crap 11.0207

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Bdf\Prime\Query\Closure\Parser;
4
5
use Bdf\Prime\Query\Closure\ClassNameResolver;
6
use Bdf\Prime\Query\Closure\Value\ArrayAccessValue;
7
use Bdf\Prime\Query\Closure\Value\ArrayValue;
8
use Bdf\Prime\Query\Closure\Value\ComparisonValueInterface;
9
use Bdf\Prime\Query\Closure\Value\ConstantValue;
10
use Bdf\Prime\Query\Closure\Value\GetterValue;
11
use Bdf\Prime\Query\Closure\Value\PropertyValue;
12
use Bdf\Prime\Query\Closure\Value\VariableValue;
13
use PhpParser\Node\Expr;
14
use PhpParser\Node\Identifier;
15
use PhpParser\Node\Name;
16
use PhpParser\Node\Scalar\DNumber;
17
use PhpParser\Node\Scalar\LNumber;
18
use PhpParser\Node\Scalar\String_;
19
use RuntimeException;
20
21
use function constant;
22
23
/**
24
 * Parse value of right side of comparison
25
 *
26
 * Handles:
27
 * - int, float or simple string constant e.g. 1, 1.2, 'foo'
28
 * - constant e.g. PHP_INT_MAX, true
29
 * - class constant e.g. Foo::BAR
30
 * - inline array expression e.g. [1, 2, $foo]
31
 * - array access e.g. $array['foo']
32
 * - property access e.g. $entity->property
33
 * - getter call e.g. $entity->getter()
34
 */
35
final class ValueParser
36
{
37
    private ClassNameResolver $resolver;
38
39 24
    public function __construct(ClassNameResolver $resolver)
40
    {
41 24
        $this->resolver = $resolver;
42
    }
43
44
    /**
45
     * Parse the value expression
46
     *
47
     * @param Expr $expr
48
     * @return ComparisonValueInterface
49
     */
50 16
    public function parse(Expr $expr): ComparisonValueInterface
51
    {
52 16
        if ($expr instanceof Expr\Variable) {
53 6
            return new VariableValue($expr->name);
0 ignored issues
show
Bug introduced by
It seems like $expr->name can also be of type PhpParser\Node\Expr; however, parameter $variable of Bdf\Prime\Query\Closure\...bleValue::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

53
            return new VariableValue(/** @scrutinizer ignore-type */ $expr->name);
Loading history...
54
        }
55
56 16
        if ($expr instanceof LNumber || $expr instanceof DNumber || $expr instanceof String_) {
57 8
            return new ConstantValue($expr->value);
58
        }
59
60 11
        if ($expr instanceof Expr\ConstFetch) {
61 2
            return new ConstantValue(constant($expr->name->toString()));
62
        }
63
64 11
        if ($expr instanceof Expr\ClassConstFetch) {
65 2
            return $this->parseClassConstant($expr);
66
        }
67
68 9
        if ($expr instanceof Expr\Array_) {
69 2
            return $this->parseArray($expr);
70
        }
71
72 7
        if ($expr instanceof Expr\ArrayDimFetch) {
73 1
            return $this->parseArrayAccess($expr);
74
        }
75
76 6
        if ($expr instanceof Expr\PropertyFetch) {
77 2
            return $this->parsePropertyFetch($expr);
78
        }
79
80 4
        if ($expr instanceof Expr\MethodCall) {
81 4
            return $this->parseGetter($expr);
82
        }
83
84
        throw new RuntimeException('Invalid comparison value ' . $expr->getType() . '. Only scalar values, constants, and arrays can be used in filters.');
85
    }
86
87 2
    private function parseArray(Expr\Array_ $array): ArrayValue
88
    {
89 2
        $values = [];
90
91 2
        foreach ($array->items as $item) {
92 2
            $values[] = $this->parse($item->value);
93
        }
94
95 2
        return new ArrayValue($values);
96
    }
97
98 1
    private function parseArrayAccess(Expr\ArrayDimFetch $expr): ComparisonValueInterface
99
    {
100 1
        if ($expr->dim === null) {
101
            throw new RuntimeException('A key must be provided for array access.');
102
        }
103
104 1
        return new ArrayAccessValue($this->parse($expr->var), $this->parse($expr->dim));
105
    }
106
107 2
    private function parseClassConstant(Expr\ClassConstFetch $expr): ConstantValue
108
    {
109 2
        if (!$expr->class instanceof Name) {
110 1
            throw new RuntimeException('Cannot resolve dynamic class constant. Use actual class name or store the constant into a variable.');
111
        }
112
113 1
        $className = $this->resolver->resolve($expr->class);
114 1
        return new ConstantValue(constant($className . '::' . $expr->name->toString()));
0 ignored issues
show
Bug introduced by
The method toString() does not exist on PhpParser\Node\Expr\Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

114
        return new ConstantValue(constant($className . '::' . $expr->name->/** @scrutinizer ignore-call */ toString()));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
115
    }
116
117 2
    private function parsePropertyFetch(Expr\PropertyFetch $expr): PropertyValue
118
    {
119 2
        if (!$expr->name instanceof Identifier) {
120 1
            throw new RuntimeException('Cannot resolve dynamic property.');
121
        }
122
123 1
        return new PropertyValue($this->parse($expr->var), $expr->name->toString());
124
    }
125
126 4
    private function parseGetter(Expr\MethodCall $expr): GetterValue
127
    {
128 4
        if (!$expr->name instanceof Identifier) {
129 1
            throw new RuntimeException('Cannot resolve dynamic method name.');
130
        }
131
132 3
        if ($expr->args) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $expr->args of type array<mixed,PhpParser\No...de\VariadicPlaceholder> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
133 1
            throw new RuntimeException('Cannot use method call with arguments in filters.');
134
        }
135
136 2
        return new GetterValue($this->parse($expr->var), $expr->name->toString());
137
    }
138
}
139