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

CompilesColumns::normalizeColumn()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 36
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 9.628

Importance

Changes 0
Metric Value
cc 8
eloc 18
c 0
b 0
f 0
nc 6
nop 3
dl 0
loc 36
ccs 12
cts 17
cp 0.7059
crap 9.628
rs 8.4444
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