Passed
Push — next ( ee2197...d54041 )
by Bas
02:37
created

src/Traits/NormalizesExpressions.php (4 issues)

Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\FluentAQL\Traits;
6
7
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException;
8
use LaravelFreelancerNL\FluentAQL\Exceptions\ExpressionTypeException;
9
use LaravelFreelancerNL\FluentAQL\Expressions\BindExpression;
10
use LaravelFreelancerNL\FluentAQL\Expressions\Expression;
11
use LaravelFreelancerNL\FluentAQL\Expressions\ListExpression;
12
use LaravelFreelancerNL\FluentAQL\Expressions\NullExpression;
13
use LaravelFreelancerNL\FluentAQL\Expressions\ObjectExpression;
14
use LaravelFreelancerNL\FluentAQL\Expressions\PredicateExpression;
15
use LaravelFreelancerNL\FluentAQL\Expressions\QueryExpression;
16
use LaravelFreelancerNL\FluentAQL\Expressions\StringExpression;
17
use LaravelFreelancerNL\FluentAQL\QueryBuilder;
18
19
trait NormalizesExpressions
20
{
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
33
     */
34 54
    public function normalizeArgument(
35
        mixed $argument,
36
        array|string $allowedExpressionTypes = null
37
    ): Expression {
38 54
        if ($argument instanceof Expression) {
39 43
            return $argument;
40
        }
41
42 53
        if (is_scalar($argument)) {
43 53
            return $this->normalizeScalar($argument, $allowedExpressionTypes);
44
        }
45
46 18
        if (is_null($argument)) {
47 7
            return new NullExpression();
48
        }
49
50
        /** @var array<mixed>|object $argument */
51 13
        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 53
    protected function normalizeScalar(
60
        array|string|int|float|bool $argument,
61
        null|array|string $allowedExpressionTypes = null
62
    ): Expression {
63 53
        $argumentType = $this->determineArgumentType($argument, $allowedExpressionTypes);
64
65 53
        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
The doc comment array<array-key, mixed>|object|scalar at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>|object|scalar.
Loading history...
72
     * @param string $argumentType
73
     * @return Expression
74
     * @throws BindException
75
     */
76 53
    protected function createExpression(
77
        array|object|bool|float|int|string $argument,
78
        string $argumentType
79
    ): Expression {
80 53
        $expressionType = $this->grammar->mapArgumentTypeToExpressionType($argumentType);
81 53
        if ($expressionType == 'Bind') {
82 15
            return $this->bind($argument);
83
        }
84 49
        if ($expressionType == 'CollectionBind') {
85 1
            return $this->bindCollection($argument);
86
        }
87 49
        $expressionClass = '\LaravelFreelancerNL\FluentAQL\Expressions\\' . $expressionType . 'Expression';
88
89 49
        return new $expressionClass($argument);
90
    }
91
92
    /**
93
     * @param array<mixed>|object $argument
94
     * @param array<string>|string|null  $allowedExpressionTypes
95
     * @return Expression
96
     * @throws ExpressionTypeException
97
     */
98 13
    protected function normalizeCompound(
99
        array|object $argument,
100
        null|array|string $allowedExpressionTypes = null
101
    ): Expression {
102 13
        if (is_array($argument)) {
103 8
            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
Documentation Bug introduced by
The doc comment array<array-key, Expression> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, Expression>.
Loading history...
118
     * @throws ExpressionTypeException
119
     */
120 10
    protected function normalizeIterable(
121
        array $argument,
122
        null|array|string $allowedExpressionTypes = null
123
    ): array {
124 10
        $result = [];
125
        /** @var mixed $value */
126 10
        foreach ($argument as $attribute => $value) {
127
            /** @var Expression $argument[$attribute] */
128 10
            $result[$attribute] = $this->normalizeArgument($value);
129
        }
130
131 10
        return $result;
132
    }
133
134
    /**
135
     * @param array<array-key, mixed>|PredicateExpression $predicates
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed>|PredicateExpression at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>|PredicateExpression.
Loading history...
136
     * @return array<array-key, mixed>|PredicateExpression
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed>|PredicateExpression at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>|PredicateExpression.
Loading history...
137
     * @throws ExpressionTypeException
138
     */
139 14
    public function normalizePredicates(
140
        array|PredicateExpression $predicates
141
    ): array|PredicateExpression {
142 14
        if ($this->grammar->isPredicate($predicates)) {
143 14
            return $this->normalizePredicate($predicates);
144
        }
145
146 13
        $normalizedPredicates = [];
147 13
        if (is_iterable($predicates)) {
148
            /** @var array<array-key, mixed> $predicate */
149 13
            foreach ($predicates as $predicate) {
150 13
                $normalizedPredicates[] = $this->normalizePredicates($predicate);
151
            }
152
        }
153
154 13
        return $normalizedPredicates;
155
    }
156
157
    /**
158
     * @param array<mixed>|PredicateExpression $predicate
159
     * @return PredicateExpression
160
     * @throws ExpressionTypeException
161
     */
162 14
    protected function normalizePredicate(array|PredicateExpression $predicate): PredicateExpression
163
    {
164 14
        if ($predicate instanceof PredicateExpression) {
165 2
            return $predicate;
166
        }
167
168 14
        $leftOperand = $this->normalizeArgument($predicate[0]);
169
170 14
        $comparisonOperator = null;
171 14
        if (isset($predicate[1])) {
172 13
            $comparisonOperator = (string) $predicate[1];
173
        }
174
175
176 14
        $rightOperand = null;
177 14
        if (isset($predicate[2])) {
178 12
            $rightOperand = $this->normalizeArgument($predicate[2]);
179
        }
180
181 14
        $logicalOperator = 'AND';
182 14
        if (isset($predicate[3]) && $this->grammar->isLogicalOperator((string) $predicate[3])) {
183 6
            $logicalOperator = (string) $predicate[3];
184
        }
185
186 14
        return new PredicateExpression(
187 14
            $leftOperand,
188
            $comparisonOperator,
189
            $rightOperand,
190
            $logicalOperator
191
        );
192
    }
193
194
    /**
195
     * Return the first matching expression type for the argument from the allowed types.
196
     *
197
     * @psalm-suppress MixedArgumentTypeCoercion
198
     *
199
     * @param array<string>|string|null  $allowedExpressionTypes
200
     * @throws ExpressionTypeException
201
     */
202 53
    protected function determineArgumentType(
203
        mixed $argument,
204
        null|array|string $allowedExpressionTypes = null
205
    ): string {
206 53
        if (is_string($allowedExpressionTypes)) {
207 27
            $allowedExpressionTypes = [$allowedExpressionTypes];
208
        }
209 53
        if ($allowedExpressionTypes == null) {
210 21
            $allowedExpressionTypes = $this->grammar->getAllowedExpressionTypes();
211
        }
212
213
        /** @var string $allowedExpressionType */
214 53
        foreach ($allowedExpressionTypes as $allowedExpressionType) {
215 53
            $check = 'is' . $allowedExpressionType;
216 53
            if ($allowedExpressionType == 'Reference' || $allowedExpressionType == 'RegisteredVariable') {
217 33
                if ($this->grammar->$check($argument, $this->variables)) {
218 19
                    return $allowedExpressionType;
219
                }
220
            }
221
222 53
            if ($this->grammar->$check($argument)) {
223 53
                return $allowedExpressionType;
224
            }
225
        }
226
227 1
        throw new ExpressionTypeException(
228
            "This argument, 
229 1
            '{$argument}', does not match one of these expression types: "
230 1
                . implode(', ', $allowedExpressionTypes)
231 1
                . '.'
232
        );
233
    }
234
235
    /**
236
     * @param array<mixed> $argument
237
     * @param array<string>|string|null  $allowedExpressionTypes
238
     * @throws ExpressionTypeException
239
     */
240 8
    protected function normalizeArray(
241
        array $argument,
242
        null|array|string $allowedExpressionTypes = null
243
    ): Expression {
244 8
        if ($this->grammar->isAssociativeArray($argument)) {
245 6
            return new ObjectExpression($this->normalizeIterable($argument, $allowedExpressionTypes));
246
        }
247
248 3
        return new ListExpression($this->normalizeIterable($argument, $allowedExpressionTypes));
249
    }
250
251
    /**
252
     * @param array<string>|string|null $allowedExpressionTypes
253
     * @throws ExpressionTypeException
254
     */
255 9
    protected function normalizeObject(
256
        object $argument,
257
        null|array|string $allowedExpressionTypes = null
258
    ): Expression {
259 9
        if ($argument instanceof \DateTimeInterface) {
260 1
            return new StringExpression($argument->format(\DateTime::ATOM));
261
        }
262
263 8
        if ($argument instanceof QueryBuilder) {
264 5
            return new QueryExpression($argument);
265
        }
266
267 3
        return new ObjectExpression($this->normalizeIterable((array) $argument, $allowedExpressionTypes));
268
    }
269
}
270