NormalizesExpressions   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Test Coverage

Coverage 97.73%

Importance

Changes 6
Bugs 0 Features 1
Metric Value
eloc 85
c 6
b 0
f 1
dl 0
loc 272
ccs 86
cts 88
cp 0.9773
rs 9.1199
wmc 41

11 Methods

Rating   Name   Duplication   Size   Complexity  
A normalizeArgument() 0 18 4
A normalizeIterable() 0 12 2
A normalizeCompound() 0 12 3
A normalizeScalar() 0 7 1
A normalizePredicates() 0 16 4
A normalizeArray() 0 9 2
A processBindExpression() 0 10 3
B normalizePredicate() 0 33 7
B determineArgumentType() 0 36 9
A createExpression() 0 15 3
A normalizeObject() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like NormalizesExpressions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NormalizesExpressions, and based on these observations, apply Extract Interface, too.

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
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
     * @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
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

83
            return $this->/** @scrutinizer ignore-call */ bindCollection($argument);
Loading history...
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
introduced by
The condition is_array($argument) is always true.
Loading history...
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
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|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
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
     * @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
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
        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
introduced by
The condition is_string($allowedExpressionTypes) is always false.
Loading history...
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
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...
289
            }
290
        }
291 48
        return $argument;
292
    }
293
}
294