1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Bdf\Prime\Query\Closure\Parser; |
4
|
|
|
|
5
|
|
|
use PhpParser\Node\Expr; |
6
|
|
|
use PhpParser\Node\Identifier; |
7
|
|
|
use RuntimeException; |
8
|
|
|
|
9
|
|
|
use function lcfirst; |
10
|
|
|
use function str_starts_with; |
11
|
|
|
use function substr; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Parser for left operand of a comparison |
15
|
|
|
* The value must be extracted from the entity parameter |
16
|
|
|
* |
17
|
|
|
* Handles: |
18
|
|
|
* - $entity->property |
19
|
|
|
* - $entity->getter() |
20
|
|
|
* - $entity->subEntity->property |
21
|
|
|
*/ |
22
|
|
|
final class EntityAccessorParser |
23
|
|
|
{ |
24
|
|
|
private string $parameterName; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @param string $parameterName Name of the entity parameter |
28
|
|
|
*/ |
29
|
24 |
|
public function __construct(string $parameterName) |
30
|
|
|
{ |
31
|
24 |
|
$this->parameterName = $parameterName; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param Expr $expr Left operand expression |
36
|
|
|
* @return string The property path separated by dots. Ex: "property", "subEntity.property" |
37
|
|
|
*/ |
38
|
22 |
|
public function parse(Expr $expr): string |
39
|
|
|
{ |
40
|
|
|
switch (true) { |
41
|
22 |
|
case $expr instanceof Expr\PropertyFetch: |
42
|
18 |
|
return $this->parsePropertyFetch($expr); |
43
|
|
|
|
44
|
6 |
|
case $expr instanceof Expr\MethodCall: |
45
|
4 |
|
return $this->parseGetterCall($expr); |
46
|
|
|
|
47
|
|
|
default: |
48
|
2 |
|
throw new RuntimeException('Invalid entity accessor ' . $expr->getType() . '. Only properties and getters can be used in filters.'); |
49
|
|
|
} |
50
|
|
|
} |
51
|
|
|
|
52
|
18 |
|
private function parsePropertyFetch(Expr\PropertyFetch $propertyFetch): string |
53
|
|
|
{ |
54
|
18 |
|
if (!$propertyFetch->name instanceof Identifier) { |
55
|
1 |
|
throw new RuntimeException('Dynamic property access is not allowed in filters'); |
56
|
|
|
} |
57
|
|
|
|
58
|
17 |
|
$name = $propertyFetch->name->name; |
59
|
|
|
|
60
|
17 |
|
if ($propertyFetch->var instanceof Expr\Variable) { |
61
|
17 |
|
$this->checkLeftOperandVariable($propertyFetch->var); |
62
|
|
|
|
63
|
16 |
|
return $name; |
64
|
|
|
} |
65
|
|
|
|
66
|
4 |
|
return $this->parse($propertyFetch->var) . '.' . $name; |
67
|
|
|
} |
68
|
|
|
|
69
|
4 |
|
private function parseGetterCall(Expr\MethodCall $expr): string |
70
|
|
|
{ |
71
|
4 |
|
if ($expr->args) { |
|
|
|
|
72
|
1 |
|
throw new RuntimeException('Only getters can be used in filters'); |
73
|
|
|
} |
74
|
|
|
|
75
|
3 |
|
if (!$expr->name instanceof Identifier) { |
76
|
1 |
|
throw new RuntimeException('Dynamic method name is not allowed in filters'); |
77
|
|
|
} |
78
|
|
|
|
79
|
2 |
|
$name = $expr->name->name; |
80
|
|
|
|
81
|
2 |
|
if (str_starts_with($name, 'get')) { |
82
|
2 |
|
$name = lcfirst(substr($name, 3)); |
83
|
|
|
} |
84
|
|
|
|
85
|
2 |
|
if ($expr->var instanceof Expr\Variable) { |
86
|
2 |
|
$this->checkLeftOperandVariable($expr->var); |
87
|
|
|
|
88
|
2 |
|
return $name; |
89
|
|
|
} |
90
|
|
|
|
91
|
2 |
|
return $this->parse($expr->var) . '.' . $name; |
92
|
|
|
} |
93
|
|
|
|
94
|
17 |
|
private function checkLeftOperandVariable(Expr\Variable $variable): void |
95
|
|
|
{ |
96
|
17 |
|
if ($variable->name !== $this->parameterName) { |
97
|
1 |
|
throw new RuntimeException('The left operand of a comparison must be a property or a getter of the entity.'); |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
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.