Passed
Pull Request — next (#84)
by Bas
15:17 queued 11:12
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 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 72
    protected function normalizeStringColumn(Builder $query, int|string $key, string $column, string $table = null): array
141
    {
142 72
        [$column, $alias] = $query->extractAlias($column, $key);
143
144 72
        $column = $query->convertIdToKey($column);
145
146 72
        $column = $this->wrap($this->normalizeColumnReferences($query, $column, $table));
147
148
        /** @phpstan-ignore-next-line */
149 72
        $alias = $this->cleanAlias($query, $alias);
150
151 72
        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 223
    protected function normalizeColumnReferences(IlluminateQueryBuilder $query, string $column, string $table = null): string
162
    {
163
        assert($query instanceof Builder);
164
165 223
        if ($query->isReference($column)) {
166 79
            return $column;
167
        }
168
169 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...
170 216
            $table = $query->from;
171
        }
172
173 216
        $references = explode('.', $column);
174
175
176 216
        $tableAlias = $query->getTableAlias($references[0]);
177
178 216
        if (isset($tableAlias)) {
179 70
            $references[0] = $tableAlias;
180
        }
181
182 216
        if (array_key_exists('groupsVariable', $query->tableAliases)) {
183 1
            $tableAlias = 'groupsVariable';
184 1
            array_unshift($references, $tableAlias);
185
        }
186
187 216
        if ($tableAlias === null  && array_key_exists($table, $query->tableAliases)) {
188 187
            array_unshift($references, $query->tableAliases[$table]);
189
        }
190
191 216
        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 216
        return implode('.', $references);
198
    }
199
200 72
    protected function cleanAlias(IlluminateQueryBuilder $query, int|null|string $alias): int|string|null
201
    {
202
        assert($query instanceof Builder);
203
204 72
        if (!is_string($alias)) {
205
            return $alias;
206
        }
207
208 72
        if (!str_contains($alias, '.')) {
209 70
            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 253
    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 253
        if (empty($returnAttributes) && empty($returnDocs)) {
239 202
            $returnDocs[] = (string) $query->getTableAlias($query->from);
240
241 202
            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 253
        if ($query->aggregate !== null) {
248 30
            $returnDocs = [];
249 30
            $returnAttributes = ['`aggregate`' => 'aggregateResult'];
250
        }
251
252
        // Return a single value for certain subqueries
253
        if (
254 253
            $query->returnSingleValue === true
255 253
            && count($returnAttributes) === 1
256 253
            && empty($returnDocs)
257
        ) {
258 3
            return reset($returnAttributes);
259
        }
260
261 253
        if (!empty($returnAttributes)) {
262 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

262
            /** @scrutinizer ignore-call */ 
263
            $returnDocs[] = $this->generateAqlObject($returnAttributes);
Loading history...
263
        }
264
265 253
        $values = $this->mergeReturnDocs($returnDocs);
266
267 253
        return $values;
268
    }
269
270
    /**
271
     * @param array<string> $returnDocs
272
     * @return string
273
     */
274 253
    protected function mergeReturnDocs($returnDocs)
275
    {
276 253
        if (sizeOf($returnDocs) > 1) {
277 23
            return 'MERGE(' . implode(', ', $returnDocs) . ')';
278
        }
279
280 249
        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
        foreach ($query->joins as $join) {
293 5
            $tableAlias = $query->getTableAlias($join->table);
294
295 5
            if (!isset($tableAlias)) {
296
                $tableAlias = $query->generateTableAlias($join->table);
297
            }
298 5
            $returnDocs[] = (string) $tableAlias;
299
        }
300
301 5
        return $returnDocs;
302
    }
303
}
304