Passed
Push — next ( bea07e...b4e6e6 )
by Bas
05:12 queued 01:45
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 362
    protected function compileColumns(IlluminateQueryBuilder $query, $columns)
23
    {
24
        assert($query instanceof Builder);
25
26 362
        $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 362
        [$returnAttributes, $returnDocs] = $this->prepareColumns($query, $columns);
29
30 362
        $returnValues = $this->determineReturnValues($query, $returnAttributes, $returnDocs);
31
32 362
        $return = 'RETURN ';
33 362
        if ($query->distinct) {
34
            $return .= 'DISTINCT ';
35
        }
36
37 362
        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 362
    protected function prepareColumns(IlluminateQueryBuilder $query, array $columns)
49
    {
50
        assert($query instanceof Builder);
51
52 362
        $returnDocs = [];
53 362
        $returnAttributes = [];
54
55 362
        foreach ($columns as $key => $column) {
56
            // Extract complete documents
57 362
            if (is_string($column) && substr($column, strlen($column) - 2)  === '.*') {
58 21
                $table = substr($column, 0, strlen($column) - 2);
59 21
                $returnDocs[] = $query->getTableAlias($table);
60
61 21
                continue;
62
            }
63
64
            // Extract groups
65 362
            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 353
            if (is_string($column) && $column != null && $column != '*') {
72 146
                [$column, $alias] = $this->normalizeStringColumn($query, $key, $column);
73
74 146
                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 146
                $returnAttributes[$alias] = $column;
82
            }
83
        }
84
85 362
        return [
86 362
            $returnAttributes,
87 362
            $returnDocs,
88 362
        ];
89
    }
90
91
    /**
92
     * @throws Exception
93
     */
94 303
    protected function normalizeColumn(IlluminateQueryBuilder $query, mixed $column, ?string $table = null): mixed
95
    {
96
        assert($query instanceof Builder);
97
98 303
        if ($column instanceof Expression) {
99
            return $column;
100
        }
101
102
        if (
103 303
            is_array($query->groups)
104 303
            && in_array($column, $query->groups)
105 303
            && 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 303
        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 303
        if ($query->isVariable($column)) {
120 1
            return $this->wrap($column);
121
        }
122
123
124 303
        $column = $this->convertJsonFields($column);
125 303
        $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 303
        return $this->wrap($this->normalizeColumnReferences($query, $column, $table));
130
    }
131
132
    /**
133
     * @param Builder $query
134
     * @param int|string $key
135
     * @param string $column
136
     * @param string|null $table
137
     * @return array<mixed>
138
     * @throws Exception
139
     */
140 146
    protected function normalizeStringColumn(Builder $query, int|string $key, string $column, ?string $table = null): array
141
    {
142 146
        [$column, $alias] = $query->extractAlias($column, $key);
143
144 146
        $column = $query->convertIdToKey($column);
145
146 146
        $column = $this->wrap($this->normalizeColumnReferences($query, $column, $table));
147
148
        /** @phpstan-ignore-next-line */
149 146
        $alias = $this->cleanAlias($query, $alias);
150
151 146
        return [$column, $alias];
152
    }
153
154
155
    /**
156
     * @param IlluminateQueryBuilder $query
157
     * @param string $column
158
     * @param string|null $table
159
     * @return string
160
     */
161 326
    protected function normalizeColumnReferences(IlluminateQueryBuilder $query, string $column, ?string $table = null): string
162
    {
163
        assert($query instanceof Builder);
164
165 326
        if ($query->isReference($column)) {
166 101
            return $column;
167
        }
168
169 312
        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...
170 312
            $table = (string) $this->getValue($query->from);
0 ignored issues
show
Bug introduced by
It seems like getValue() 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

170
            $table = (string) $this->/** @scrutinizer ignore-call */ getValue($query->from);
Loading history...
171
        }
172
173 312
        $references = explode('.', $column);
174
175
176 312
        $tableAlias = $query->getTableAlias($references[0]);
177
178 312
        if (isset($tableAlias)) {
179 72
            $references[0] = $tableAlias;
180
        }
181
182 312
        if (array_key_exists('groupsVariable', $query->tableAliases)) {
183 1
            $tableAlias = 'groupsVariable';
184 1
            array_unshift($references, $tableAlias);
185
        }
186
187 312
        if ($tableAlias === null  && array_key_exists($table, $query->tableAliases)) {
188 285
            array_unshift($references, $query->tableAliases[$table]);
189
        }
190
191 312
        if ($tableAlias === null && !$query->isReference($references[0])) {
192 1
            $tableAlias = $query->generateTableAlias($table);
193 1
            array_unshift($references, $tableAlias);
194
        }
195
196
        /** @phpstan-ignore-next-line */
197 312
        return implode('.', $references);
198
    }
199
200 146
    protected function cleanAlias(IlluminateQueryBuilder $query, int|null|string $alias): int|string|null
201
    {
202
        assert($query instanceof Builder);
203
204 146
        if (!is_string($alias)) {
205
            return $alias;
206
        }
207
208 146
        if (!str_contains($alias, '.')) {
209 144
            return $alias;
210
        }
211
212 8
        $elements = explode('.', $alias);
213
214
        if (
215 8
            !$query->isTable($elements[0])
216 8
            && !$query->isVariable($elements[0])
217
        ) {
218 6
            return $alias;
219
        }
220
221 2
        array_shift($elements);
222
223 2
        return implode($elements);
224
    }
225
226
227
    /**
228
     * @param IlluminateQueryBuilder $query
229
     * @param array<string> $returnAttributes
230
     * @param array<string>  $returnDocs
231
     * @return string
232
     */
233 362
    protected function determineReturnValues(IlluminateQueryBuilder $query, $returnAttributes = [], $returnDocs = []): string
234
    {
235
        assert($query instanceof Builder);
236
237
        // If nothing was specifically requested, we return everything.
238 362
        if (empty($returnAttributes) && empty($returnDocs)) {
239 278
            $returnDocs[] = (string) $query->getTableAlias($query->from);
240
241 278
            if ($query->joins !== null) {
242 5
                $returnDocs = $this->mergeJoinResults($query, $returnDocs);
243
            }
244
        }
245
246
        // Aggregate functions only return the aggregate, so we can clear out everything else.
247 362
        if ($query->aggregate !== null) {
248 77
            $returnDocs = [];
249 77
            $returnAttributes = ['aggregate' => 'aggregateResult'];
250
        }
251
252
        // Return a single value for certain subqueries
253
        if (
254 362
            $query->returnSingleValue === true
255 362
            && count($returnAttributes) === 1
256 362
            && empty($returnDocs)
257
        ) {
258 3
            return reset($returnAttributes);
259
        }
260
261 362
        if (!empty($returnAttributes)) {
262 160
            $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

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