1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace LaravelFreelancerNL\FluentAQL\Traits; |
||||
6 | |||||
7 | use DateTimeInterface; |
||||
8 | use LaravelFreelancerNL\FluentAQL\Exceptions\BindException; |
||||
9 | use LaravelFreelancerNL\FluentAQL\Exceptions\ExpressionTypeException; |
||||
10 | use LaravelFreelancerNL\FluentAQL\Expressions\BindExpression; |
||||
11 | use LaravelFreelancerNL\FluentAQL\Expressions\Expression; |
||||
12 | use LaravelFreelancerNL\FluentAQL\Expressions\ListExpression; |
||||
13 | use LaravelFreelancerNL\FluentAQL\Expressions\NullExpression; |
||||
14 | use LaravelFreelancerNL\FluentAQL\Expressions\ObjectExpression; |
||||
15 | use LaravelFreelancerNL\FluentAQL\Expressions\PredicateExpression; |
||||
16 | use LaravelFreelancerNL\FluentAQL\Expressions\QueryExpression; |
||||
17 | use LaravelFreelancerNL\FluentAQL\Expressions\StringExpression; |
||||
18 | use LaravelFreelancerNL\FluentAQL\QueryBuilder; |
||||
19 | |||||
20 | trait NormalizesExpressions |
||||
21 | { |
||||
22 | /** |
||||
23 | * @param object|array<mixed>|string|int|float|bool|null $data |
||||
24 | */ |
||||
25 | abstract public function bind( |
||||
26 | object|array|string|int|float|bool|null $data, |
||||
27 | string $to = null |
||||
28 | ): BindExpression; |
||||
29 | |||||
30 | /** |
||||
31 | * @param null|string[]|string $allowedExpressionTypes |
||||
32 | * @throws ExpressionTypeException|BindException |
||||
33 | */ |
||||
34 | 59 | public function normalizeArgument( |
|||
35 | mixed $argument, |
||||
36 | array|string $allowedExpressionTypes = null |
||||
37 | ): Expression { |
||||
38 | 59 | if ($argument instanceof Expression) { |
|||
39 | 48 | return $this->processBindExpression($argument); |
|||
40 | } |
||||
41 | |||||
42 | 58 | if (is_scalar($argument)) { |
|||
43 | 58 | return $this->normalizeScalar($argument, $allowedExpressionTypes); |
|||
44 | } |
||||
45 | |||||
46 | 20 | if (is_null($argument)) { |
|||
47 | 7 | return new NullExpression(); |
|||
48 | } |
||||
49 | |||||
50 | /** @var array<mixed>|object $argument */ |
||||
51 | 15 | return $this->normalizeCompound($argument, $allowedExpressionTypes); |
|||
52 | } |
||||
53 | |||||
54 | /** |
||||
55 | * @param array<mixed>|string|int|float|bool $argument |
||||
56 | * @param array<string>|string|null $allowedExpressionTypes |
||||
57 | * @throws ExpressionTypeException|BindException |
||||
58 | */ |
||||
59 | 58 | protected function normalizeScalar( |
|||
60 | array|string|int|float|bool $argument, |
||||
61 | null|array|string $allowedExpressionTypes = null |
||||
62 | ): Expression { |
||||
63 | 58 | $argumentType = $this->determineArgumentType($argument, $allowedExpressionTypes); |
|||
64 | |||||
65 | 58 | return $this->createExpression($argument, $argumentType); |
|||
66 | } |
||||
67 | |||||
68 | /** |
||||
69 | * @psalm-suppress MoreSpecificReturnType |
||||
70 | * |
||||
71 | * @param array<array-key, mixed>|object|scalar $argument |
||||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||||
72 | * @throws BindException |
||||
73 | */ |
||||
74 | 58 | protected function createExpression( |
|||
75 | array|object|bool|float|int|string $argument, |
||||
76 | string $argumentType |
||||
77 | ): Expression { |
||||
78 | 58 | $expressionType = $this->grammar->mapArgumentTypeToExpressionType($argumentType); |
|||
79 | 58 | if ($expressionType == 'Bind') { |
|||
80 | 15 | return $this->bind($argument); |
|||
81 | } |
||||
82 | 54 | if ($expressionType == 'CollectionBind') { |
|||
83 | 1 | return $this->bindCollection($argument); |
|||
0 ignored issues
–
show
It seems like
bindCollection() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
84 | } |
||||
85 | 54 | $expressionClass = '\LaravelFreelancerNL\FluentAQL\Expressions\\' . $expressionType . 'Expression'; |
|||
86 | |||||
87 | /** @phpstan-ignore-next-line */ |
||||
88 | 54 | return new $expressionClass($argument); |
|||
89 | } |
||||
90 | |||||
91 | /** |
||||
92 | * @param array<mixed>|object $argument |
||||
93 | * @param array<string>|string|null $allowedExpressionTypes |
||||
94 | * @return Expression |
||||
95 | * @throws ExpressionTypeException |
||||
96 | * @throws BindException |
||||
97 | */ |
||||
98 | 15 | protected function normalizeCompound( |
|||
99 | array|object $argument, |
||||
100 | null|array|string $allowedExpressionTypes = null |
||||
101 | ): Expression { |
||||
102 | 15 | if (is_array($argument)) { |
|||
0 ignored issues
–
show
|
|||||
103 | 10 | return $this->normalizeArray($argument, $allowedExpressionTypes); |
|||
104 | } |
||||
105 | 9 | if (!is_iterable($argument)) { |
|||
106 | 9 | return $this->normalizeObject($argument, $allowedExpressionTypes); |
|||
107 | } |
||||
108 | |||||
109 | return new ObjectExpression($this->normalizeIterable((array) $argument, $allowedExpressionTypes)); |
||||
110 | } |
||||
111 | |||||
112 | /** |
||||
113 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
||||
114 | * |
||||
115 | * @param array<mixed> $argument |
||||
116 | * @param array<string>|string|null $allowedExpressionTypes |
||||
117 | * @return array<array-key, Expression> |
||||
0 ignored issues
–
show
|
|||||
118 | * @throws ExpressionTypeException|BindException |
||||
119 | */ |
||||
120 | 12 | protected function normalizeIterable( |
|||
121 | array $argument, |
||||
122 | null|array|string $allowedExpressionTypes = null |
||||
123 | ): array { |
||||
124 | 12 | $result = []; |
|||
125 | /** @var mixed $value */ |
||||
126 | 12 | foreach ($argument as $attribute => $value) { |
|||
127 | /** @var Expression $argument[$attribute] */ |
||||
128 | 12 | $result[$attribute] = $this->normalizeArgument($value); |
|||
129 | } |
||||
130 | |||||
131 | 12 | return $result; |
|||
132 | } |
||||
133 | |||||
134 | /** |
||||
135 | * @param array<array-key, mixed>|PredicateExpression $predicates |
||||
0 ignored issues
–
show
|
|||||
136 | * @return array<array-key, mixed>|PredicateExpression |
||||
0 ignored issues
–
show
|
|||||
137 | * @throws ExpressionTypeException |
||||
138 | * @throws BindException |
||||
139 | */ |
||||
140 | 17 | public function normalizePredicates( |
|||
141 | array|PredicateExpression $predicates |
||||
142 | ): array|PredicateExpression { |
||||
143 | 17 | if ($this->grammar->isPredicate($predicates)) { |
|||
144 | 17 | return $this->normalizePredicate($predicates); |
|||
145 | } |
||||
146 | |||||
147 | 16 | $normalizedPredicates = []; |
|||
148 | 16 | if (is_iterable($predicates)) { |
|||
149 | /** @var array<array-key, mixed> $predicate */ |
||||
150 | 16 | foreach ($predicates as $predicate) { |
|||
151 | 16 | $normalizedPredicates[] = $this->normalizePredicates($predicate); |
|||
152 | } |
||||
153 | } |
||||
154 | |||||
155 | 16 | return $normalizedPredicates; |
|||
156 | } |
||||
157 | |||||
158 | /** |
||||
159 | * @param array<mixed>|PredicateExpression $predicate |
||||
160 | * @return PredicateExpression |
||||
161 | * @throws ExpressionTypeException|BindException |
||||
162 | */ |
||||
163 | 17 | protected function normalizePredicate(array|PredicateExpression $predicate): PredicateExpression |
|||
164 | { |
||||
165 | 17 | if ($predicate instanceof PredicateExpression) { |
|||
0 ignored issues
–
show
|
|||||
166 | 4 | return $predicate; |
|||
167 | } |
||||
168 | |||||
169 | 17 | $leftOperand = $this->normalizeArgument($predicate[0]); |
|||
170 | |||||
171 | 17 | $comparisonOperator = null; |
|||
172 | 17 | if (isset($predicate[1])) { |
|||
173 | 16 | $comparisonOperator = (string) $predicate[1]; |
|||
174 | } |
||||
175 | |||||
176 | |||||
177 | 17 | $rightOperand = null; |
|||
178 | 17 | if (isset($predicate[2])) { |
|||
179 | 15 | $rightOperand = $this->normalizeArgument($predicate[2]); |
|||
180 | } |
||||
181 | |||||
182 | 17 | $logicalOperator = 'AND'; |
|||
183 | if ( |
||||
184 | 17 | isset($predicate[3]) |
|||
185 | 17 | && isStringable($predicate[3]) |
|||
186 | 17 | && $this->grammar->isLogicalOperator((string) $predicate[3]) |
|||
187 | ) { |
||||
188 | 6 | $logicalOperator = (string) $predicate[3]; |
|||
189 | } |
||||
190 | |||||
191 | 17 | return new PredicateExpression( |
|||
192 | $leftOperand, |
||||
193 | $comparisonOperator, |
||||
194 | $rightOperand, |
||||
195 | $logicalOperator |
||||
196 | ); |
||||
197 | } |
||||
198 | |||||
199 | /** |
||||
200 | * Return the first matching expression type for the argument from the allowed types. |
||||
201 | * |
||||
202 | * @psalm-suppress MixedArgumentTypeCoercion |
||||
203 | * |
||||
204 | * @param array<string>|string|null $allowedExpressionTypes |
||||
205 | * @throws ExpressionTypeException |
||||
206 | */ |
||||
207 | 58 | protected function determineArgumentType( |
|||
208 | mixed $argument, |
||||
209 | null|array|string $allowedExpressionTypes = null |
||||
210 | ): string { |
||||
211 | 58 | if (is_string($allowedExpressionTypes)) { |
|||
0 ignored issues
–
show
|
|||||
212 | 32 | $allowedExpressionTypes = [$allowedExpressionTypes]; |
|||
213 | } |
||||
214 | 58 | if ($allowedExpressionTypes == null) { |
|||
215 | 26 | $allowedExpressionTypes = $this->grammar->getAllowedExpressionTypes(); |
|||
216 | } |
||||
217 | |||||
218 | /** @var string $allowedExpressionType */ |
||||
219 | 58 | foreach ($allowedExpressionTypes as $allowedExpressionType) { |
|||
220 | 58 | $check = 'is' . $allowedExpressionType; |
|||
221 | 58 | if ($allowedExpressionType == 'Reference' || $allowedExpressionType == 'RegisteredVariable') { |
|||
222 | 38 | if ($this->grammar->$check($argument, $this->variables)) { |
|||
223 | 24 | return $allowedExpressionType; |
|||
224 | } |
||||
225 | } |
||||
226 | |||||
227 | 58 | if ($this->grammar->$check($argument)) { |
|||
228 | 58 | return $allowedExpressionType; |
|||
229 | } |
||||
230 | } |
||||
231 | |||||
232 | 1 | $errorMessage = "The argument does not match one of these expression types: " |
|||
233 | 1 | . implode(', ', $allowedExpressionTypes) |
|||
234 | . '.'; |
||||
235 | |||||
236 | 1 | if (isStringable($argument)) { |
|||
237 | 1 | $errorMessage = "This argument '$argument', does not match one of these expression types: " |
|||
238 | 1 | . implode(', ', $allowedExpressionTypes) |
|||
239 | . '.'; |
||||
240 | } |
||||
241 | |||||
242 | 1 | throw new ExpressionTypeException($errorMessage); |
|||
243 | } |
||||
244 | |||||
245 | /** |
||||
246 | * @param array<mixed> $argument |
||||
247 | * @param array<string>|string|null $allowedExpressionTypes |
||||
248 | * @throws ExpressionTypeException |
||||
249 | * @throws BindException |
||||
250 | */ |
||||
251 | 10 | protected function normalizeArray( |
|||
252 | array $argument, |
||||
253 | null|array|string $allowedExpressionTypes = null |
||||
254 | ): Expression { |
||||
255 | 10 | if ($this->grammar->isAssociativeArray($argument)) { |
|||
256 | 7 | return new ObjectExpression($this->normalizeIterable($argument, $allowedExpressionTypes)); |
|||
257 | } |
||||
258 | |||||
259 | 3 | return new ListExpression($this->normalizeIterable($argument, $allowedExpressionTypes)); |
|||
260 | } |
||||
261 | |||||
262 | /** |
||||
263 | * @param array<string>|string|null $allowedExpressionTypes |
||||
264 | * @throws ExpressionTypeException |
||||
265 | * @throws BindException |
||||
266 | */ |
||||
267 | 9 | protected function normalizeObject( |
|||
268 | object $argument, |
||||
269 | null|array|string $allowedExpressionTypes = null |
||||
270 | ): Expression { |
||||
271 | 9 | if ($argument instanceof DateTimeInterface) { |
|||
272 | 1 | return new StringExpression($argument->format(DateTimeInterface::ATOM)); |
|||
273 | } |
||||
274 | |||||
275 | 8 | if ($argument instanceof QueryBuilder) { |
|||
276 | 5 | return new QueryExpression($argument); |
|||
277 | } |
||||
278 | |||||
279 | 3 | return new ObjectExpression($this->normalizeIterable((array) $argument, $allowedExpressionTypes)); |
|||
280 | } |
||||
281 | |||||
282 | 48 | public function processBindExpression(Expression $argument): Expression |
|||
283 | { |
||||
284 | 48 | if ($argument instanceof BindExpression) { |
|||
285 | 2 | $bindKey = ltrim($argument->getBindVariable(), '@'); |
|||
286 | |||||
287 | 2 | if (!isset($this->binds[$bindKey])) { |
|||
288 | $this->binds[$bindKey] = $argument->getData(); |
||||
0 ignored issues
–
show
|
|||||
289 | } |
||||
290 | } |
||||
291 | 48 | return $argument; |
|||
292 | } |
||||
293 | } |
||||
294 |