Passed
Push — next ( 142d58...5325de )
by Bas
10:59
created

NormalizesExpressions::processBindExpression()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
crap 3.0416
rs 10
c 0
b 0
f 0
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
     * @param object|array<mixed>|string|int|float|bool|null $data
23
     */
24
    abstract public function bind(
25
        object|array|string|int|float|bool|null $data,
26
        string $to = null
27
    ): BindExpression;
28
29
    /**
30
     * @param null|string[]|string $allowedExpressionTypes
31
     * @throws ExpressionTypeException
32
     */
33 59
    public function normalizeArgument(
34
        mixed $argument,
35
        array|string $allowedExpressionTypes = null
36
    ): Expression {
37 59
        if ($argument instanceof Expression) {
38 48
            $argument = $this->processBindExpression($argument);
39
40 48
            return $argument;
41
        }
42
43 58
        if (is_scalar($argument)) {
44 58
            return $this->normalizeScalar($argument, $allowedExpressionTypes);
45
        }
46
47 20
        if (is_null($argument)) {
48 7
            return new NullExpression();
49
        }
50
51
        /** @var array<mixed>|object $argument */
52 15
        return $this->normalizeCompound($argument, $allowedExpressionTypes);
53
    }
54
55
    /**
56
     * @param array<mixed>|string|int|float|bool $argument
57
     * @param array<string>|string|null  $allowedExpressionTypes
58
     * @throws ExpressionTypeException|BindException
59
     */
60 58
    protected function normalizeScalar(
61
        array|string|int|float|bool $argument,
62
        null|array|string $allowedExpressionTypes = null
63
    ): Expression {
64 58
        $argumentType = $this->determineArgumentType($argument, $allowedExpressionTypes);
65
66 58
        return $this->createExpression($argument, $argumentType);
67
    }
68
69
    /**
70
     * @psalm-suppress MoreSpecificReturnType
71
     *
72
     * @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...
73
     * @param string $argumentType
74
     * @return Expression
75
     * @throws BindException
76
     */
77 58
    protected function createExpression(
78
        array|object|bool|float|int|string $argument,
79
        string $argumentType
80
    ): Expression {
81 58
        $expressionType = $this->grammar->mapArgumentTypeToExpressionType($argumentType);
82 58
        if ($expressionType == 'Bind') {
83 15
            return $this->bind($argument);
84
        }
85 54
        if ($expressionType == 'CollectionBind') {
86 1
            return $this->bindCollection($argument);
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

86
            return $this->/** @scrutinizer ignore-call */ bindCollection($argument);
Loading history...
87
        }
88 54
        $expressionClass = '\LaravelFreelancerNL\FluentAQL\Expressions\\' . $expressionType . 'Expression';
89
90 54
        return new $expressionClass($argument);
91
    }
92
93
    /**
94
     * @param array<mixed>|object $argument
95
     * @param array<string>|string|null  $allowedExpressionTypes
96
     * @return Expression
97
     * @throws ExpressionTypeException
98
     */
99 15
    protected function normalizeCompound(
100
        array|object $argument,
101
        null|array|string $allowedExpressionTypes = null
102
    ): Expression {
103 15
        if (is_array($argument)) {
0 ignored issues
show
introduced by
The condition is_array($argument) is always true.
Loading history...
104 10
            return $this->normalizeArray($argument, $allowedExpressionTypes);
105
        }
106 9
        if (!is_iterable($argument)) {
107 9
            return $this->normalizeObject($argument, $allowedExpressionTypes);
108
        }
109
110
        return new ObjectExpression($this->normalizeIterable((array) $argument, $allowedExpressionTypes));
111
    }
112
113
    /**
114
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
115
     *
116
     * @param array<mixed> $argument
117
     * @param array<string>|string|null $allowedExpressionTypes
118
     * @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...
119
     * @throws ExpressionTypeException
120
     */
121 12
    protected function normalizeIterable(
122
        array $argument,
123
        null|array|string $allowedExpressionTypes = null
124
    ): array {
125 12
        $result = [];
126
        /** @var mixed $value */
127 12
        foreach ($argument as $attribute => $value) {
128
            /** @var Expression $argument[$attribute] */
129 12
            $result[$attribute] = $this->normalizeArgument($value);
130
        }
131
132 12
        return $result;
133
    }
134
135
    /**
136
     * @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...
137
     * @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...
138
     * @throws ExpressionTypeException
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
162
     */
163 17
    protected function normalizePredicate(array|PredicateExpression $predicate): PredicateExpression
164
    {
165 17
        if ($predicate instanceof PredicateExpression) {
0 ignored issues
show
introduced by
$predicate is never a sub-type of LaravelFreelancerNL\Flue...ons\PredicateExpression.
Loading history...
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 17
        if (isset($predicate[3]) && $this->grammar->isLogicalOperator((string) $predicate[3])) {
184 6
            $logicalOperator = (string) $predicate[3];
185
        }
186
187 17
        return new PredicateExpression(
188 17
            $leftOperand,
189
            $comparisonOperator,
190
            $rightOperand,
191
            $logicalOperator
192
        );
193
    }
194
195
    /**
196
     * Return the first matching expression type for the argument from the allowed types.
197
     *
198
     * @psalm-suppress MixedArgumentTypeCoercion
199
     *
200
     * @param array<string>|string|null  $allowedExpressionTypes
201
     * @throws ExpressionTypeException
202
     */
203 58
    protected function determineArgumentType(
204
        mixed $argument,
205
        null|array|string $allowedExpressionTypes = null
206
    ): string {
207 58
        if (is_string($allowedExpressionTypes)) {
0 ignored issues
show
introduced by
The condition is_string($allowedExpressionTypes) is always false.
Loading history...
208 32
            $allowedExpressionTypes = [$allowedExpressionTypes];
209
        }
210 58
        if ($allowedExpressionTypes == null) {
211 26
            $allowedExpressionTypes = $this->grammar->getAllowedExpressionTypes();
212
        }
213
214
        /** @var string $allowedExpressionType */
215 58
        foreach ($allowedExpressionTypes as $allowedExpressionType) {
216 58
            $check = 'is' . $allowedExpressionType;
217 58
            if ($allowedExpressionType == 'Reference' || $allowedExpressionType == 'RegisteredVariable') {
218 38
                if ($this->grammar->$check($argument, $this->variables)) {
219 24
                    return $allowedExpressionType;
220
                }
221
            }
222
223 58
            if ($this->grammar->$check($argument)) {
224 58
                return $allowedExpressionType;
225
            }
226
        }
227
228 1
        throw new ExpressionTypeException(
229
            "This argument, 
230 1
            '{$argument}', does not match one of these expression types: "
231 1
                . implode(', ', $allowedExpressionTypes)
232 1
                . '.'
233
        );
234
    }
235
236
    /**
237
     * @param array<mixed> $argument
238
     * @param array<string>|string|null  $allowedExpressionTypes
239
     * @throws ExpressionTypeException
240
     */
241 10
    protected function normalizeArray(
242
        array $argument,
243
        null|array|string $allowedExpressionTypes = null
244
    ): Expression {
245 10
        if ($this->grammar->isAssociativeArray($argument)) {
246 8
            return new ObjectExpression($this->normalizeIterable($argument, $allowedExpressionTypes));
247
        }
248
249 3
        return new ListExpression($this->normalizeIterable($argument, $allowedExpressionTypes));
250
    }
251
252
    /**
253
     * @param array<string>|string|null $allowedExpressionTypes
254
     * @throws ExpressionTypeException
255
     */
256 9
    protected function normalizeObject(
257
        object $argument,
258
        null|array|string $allowedExpressionTypes = null
259
    ): Expression {
260 9
        if ($argument instanceof \DateTimeInterface) {
261 1
            return new StringExpression($argument->format(\DateTime::ATOM));
262
        }
263
264 8
        if ($argument instanceof QueryBuilder) {
265 5
            return new QueryExpression($argument);
266
        }
267
268 3
        return new ObjectExpression($this->normalizeIterable((array) $argument, $allowedExpressionTypes));
269
    }
270
271 48
    public function processBindExpression(Expression $argument): Expression
272
    {
273 48
        if ($argument instanceof BindExpression) {
274 2
            $bindKey = ltrim($argument->getBindVariable(), '@');
275
276 2
            if (!isset($this->binds[$bindKey])) {
277
                $this->binds[$bindKey] = $argument->getData();
0 ignored issues
show
Bug Best Practice introduced by
The property binds does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
278
            }
279
        }
280 48
        return $argument;
281
    }
282
}
283