Passed
Push — refactor/improve-static-analys... ( efcf20...37f12c )
by Bas
03:04
created

CompilesColumns::prepareColumns()   B

Complexity

Conditions 11
Paths 6

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 12.2432

Importance

Changes 0
Metric Value
cc 11
eloc 22
c 0
b 0
f 0
nc 6
nop 2
dl 0
loc 40
ccs 18
cts 23
cp 0.7826
crap 12.2432
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent\Query\Concerns;
6
7
use Exception;
8
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
9
use Illuminate\Database\Query\Expression;
10
use LaravelFreelancerNL\Aranguent\Query\Builder;
11
12
trait CompilesColumns
13
{
14
    /**
15
     * Compile the "select *" portion of the query.
16
     *
17
     * @param IlluminateQueryBuilder $query
18
     * @param array<mixed> $columns
19
     * @return string|null
20
     * @throws Exception
21
     */
22 253
    protected function compileColumns(IlluminateQueryBuilder $query, $columns)
23
    {
24
        assert($query instanceof Builder);
25
26 253
        $columns = $this->convertJsonFields($columns);
0 ignored issues
show
Bug introduced by
It seems like convertJsonFields() 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

26
        /** @scrutinizer ignore-call */ 
27
        $columns = $this->convertJsonFields($columns);
Loading history...
27
28 253
        [$returnAttributes, $returnDocs] = $this->prepareColumns($query, $columns);
29
30 253
        $returnValues = $this->determineReturnValues($query, $returnAttributes, $returnDocs);
31
32 253
        $return = 'RETURN ';
33 253
        if ($query->distinct) {
34
            $return .= 'DISTINCT ';
35
        }
36
37 253
        return $return . $returnValues;
38
    }
39
40
    /**
41
     * @param IlluminateQueryBuilder $query
42
     * @param array<mixed> $columns
43
     * @return array<mixed>
44
     * @throws Exception
45
     *
46
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
47
     */
48 253
    protected function prepareColumns(IlluminateQueryBuilder $query, array $columns)
49
    {
50
        assert($query instanceof Builder);
51
52 253
        $returnDocs = [];
53 253
        $returnAttributes = [];
54
55 253
        foreach ($columns as $key => $column) {
56
            // Extract complete documents
57 253
            if (is_string($column) && substr($column, strlen($column) - 2)  === '.*') {
58 18
                $table = substr($column, 0, strlen($column) - 2);
59 18
                $returnDocs[] = $query->getTableAlias($table);
60
61 18
                continue;
62
            }
63
64
            // Extract groups
65 253
            if (is_array($query->groups) && in_array($column, $query->groups)) {
66 10
                $returnAttributes[$column] = $this->normalizeColumn($query, $column);
67
68 10
                continue;
69
            }
70
71 244
            if (is_string($column) && $column != null && $column != '*') {
72 72
                [$column, $alias] = $this->normalizeStringColumn($query, $key, $column);
73
74 72
                if (isset($returnAttributes[$alias]) && is_array($column)) {
75
                    $returnAttributes[$alias] = array_merge_recursive(
76
                        $returnAttributes[$alias],
77
                        $this->normalizeColumn($query, $column)
78
                    );
79
                    continue;
80
                }
81 72
                $returnAttributes[$alias] = $column;
82
            }
83
        }
84
85 253
        return [
86 253
            $returnAttributes,
87 253
            $returnDocs
88 253
        ];
89
    }
90
91
    /**
92
     * @throws Exception
93
     */
94 200
    protected function normalizeColumn(IlluminateQueryBuilder $query, mixed $column, string $table = null): mixed
95
    {
96
        assert($query instanceof Builder);
97
98 200
        if ($column instanceof Expression) {
99
            return $column;
100
        }
101
102
        if (
103 200
            is_array($query->groups)
104 200
            && in_array($column, $query->groups)
105 200
            && debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT)[1]['function'] !== "compileGroups"
106
        ) {
107 10
            return $this->wrap($query->convertIdToKey($column));
0 ignored issues
show
Bug introduced by
It seems like wrap() 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

107
            return $this->/** @scrutinizer ignore-call */ wrap($query->convertIdToKey($column));
Loading history...
108
        }
109
110 200
        if (is_array($column)) {
111
            $column = $this->convertJsonFields($column);
112
113
            foreach ($column as $key => $value) {
114
                $column[$key] = $this->normalizeColumn($query, $value, $table);
115
            }
116
            return $column;
117
        }
118
119 200
        if ($query->isVariable($column)) {
120 1
            return $this->wrap($column);
121
        }
122
123
124 200
        $column = $this->convertJsonFields($column);
125 200
        $column = $query->convertIdToKey($column);
126
127
        //We check for an existing alias to determine of the first reference is a table.
128
        // In which case we replace it with the alias.
129 200
        return $this->wrap($this->normalizeColumnReferences($query, $column, $table));
130
    }
131
132
    /**
133
     * @param string $column
134
     * @return array<mixed>
135
     * @throws Exception
136
     */
137 72
    protected function normalizeStringColumn(Builder $query, int|string $key, string $column, string $table = null): array
138
    {
139 72
        [$column, $alias] = $query->extractAlias($column, $key);
140
141 72
        $column = $query->convertIdToKey($column);
142
143 72
        $column = $this->wrap($this->normalizeColumnReferences($query, $column, $table));
144
145 72
        $alias = $this->cleanAlias($query, $alias);
146
147 72
        return [$column, $alias];
148
    }
149
150
151
    /**
152
     * @param IlluminateQueryBuilder $query
153
     * @param string $column
154
     * @param string|null $table
155
     * @return string
156
     */
157 223
    protected function normalizeColumnReferences(IlluminateQueryBuilder $query, string $column, string $table = null): string
158
    {
159
        assert($query instanceof Builder);
160
161 223
        if ($query->isReference($column)) {
162 79
            return $column;
163
        }
164
165 216
        if ($table == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $table of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
166 216
            $table = $query->from;
167
        }
168
169 216
        $references = explode('.', $column);
170
171
172 216
        $tableAlias = $query->getTableAlias($references[0]);
173
174 216
        if (isset($tableAlias)) {
175 70
            $references[0] = $tableAlias;
176
        }
177
178 216
        if (array_key_exists('groupsVariable', $query->tableAliases)) {
179 1
            $tableAlias = 'groupsVariable';
180 1
            array_unshift($references, $tableAlias);
181
        }
182
183 216
        if ($tableAlias === null  && array_key_exists($table, $query->tableAliases)) {
184 187
            array_unshift($references, $query->tableAliases[$table]);
185
        }
186
187 216
        if ($tableAlias === null && !$query->isReference($references[0])) {
188 1
            $tableAlias = $query->generateTableAlias($table);
189 1
            array_unshift($references, $tableAlias);
190
        }
191
192
        /** @phpstan-ignore-next-line */
193 216
        return implode('.', $references);
194
    }
195
196 72
    protected function cleanAlias(IlluminateQueryBuilder $query, int|null|string $alias): int|string|null
197
    {
198
        assert($query instanceof Builder);
199
200 72
        if (!is_string($alias)) {
201
            return $alias;
202
        }
203
204 72
        if (!str_contains($alias, '.')) {
205 70
            return $alias;
206
        }
207
208 8
        $elements = explode('.', $alias);
209
210
        if(
211 8
            !$query->isTable($elements[0])
212 8
            && !$query->isVariable($elements[0])
213
        ) {
214 6
            return $alias;
215
        }
216
217 2
        array_shift($elements);
218
219 2
        return implode($elements);
220
    }
221
222
223
    /**
224
     * @param IlluminateQueryBuilder $query
225
     * @param array<string> $returnAttributes
226
     * @param array<string>  $returnDocs
227
     * @return string
228
     */
229 253
    protected function determineReturnValues(IlluminateQueryBuilder $query, $returnAttributes = [], $returnDocs = []): string
230
    {
231
        assert($query instanceof Builder);
232
233
        // If nothing was specifically requested, we return everything.
234 253
        if (empty($returnAttributes) && empty($returnDocs)) {
235 202
            $returnDocs[] = $query->getTableAlias($query->from);
236
237 202
            if ($query->joins !== null) {
238 5
                $returnDocs = $this->mergeJoinResults($query, $returnDocs);
239
            }
240
        }
241
242
        // clean up returnAttributes?
243
244
        // Aggregate functions only return the aggregate, so we can clear out everything else.
245 253
        if ($query->aggregate !== null) {
246 30
            $returnDocs = [];
247 30
            $returnAttributes = ['`aggregate`' => 'aggregateResult'];
248
        }
249
250
        // Return a single value for certain subqueries
251
        if (
252 253
            $query->returnSingleValue === true
253 253
            && count($returnAttributes) === 1
254 253
            && empty($returnDocs)
255
        ) {
256 3
            return reset($returnAttributes);
257
        }
258
259 253
        if (!empty($returnAttributes)) {
260 95
            $returnDocs[] = $this->generateAqlObject($returnAttributes);
0 ignored issues
show
Bug introduced by
It seems like generateAqlObject() 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

260
            /** @scrutinizer ignore-call */ 
261
            $returnDocs[] = $this->generateAqlObject($returnAttributes);
Loading history...
261
        }
262
263 253
        $values = $this->mergeReturnDocs($returnDocs);
264
265 253
        return $values;
266
    }
267
268
    /**
269
     * @param array<string> $returnDocs
270
     * @return string
271
     */
272 253
    protected function mergeReturnDocs($returnDocs)
273
    {
274 253
        if (sizeOf($returnDocs) > 1) {
275 23
            return 'MERGE(' . implode(', ', $returnDocs) . ')';
276
        }
277
278 249
        return reset($returnDocs);
279
    }
280
281
    /**
282
     * @param IlluminateQueryBuilder $query
283
     * @param array<string> $returnDocs
284
     * @return array<string>
285
     */
286 5
    protected function mergeJoinResults(IlluminateQueryBuilder $query, $returnDocs = []): array
287
    {
288
        assert($query instanceof Builder);
289
290 5
        foreach ($query->joins as $join) {
291 5
            $tableAlias = $query->getTableAlias($join->table);
292
293 5
            if (!isset($tableAlias)) {
294
                $tableAlias = $query->generateTableAlias($join->table);
295
            }
296 5
            $returnDocs[] = $tableAlias;
297
        }
298
299 5
        return $returnDocs;
300
    }
301
}
302