Failed Conditions
Push — next ( d84956...c6ac1e )
by Bas
06:14 queued 03:00
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
     * @param mixed[] $returnAttributes
16
     * @param mixed[] $returnDocs
17
     * @param Builder $query
18
     * @return mixed[]
19
     */
20 389
    public function processEmptyReturnValues(array $returnAttributes, array $returnDocs, Builder $query): array
21
    {
22 389
        if (empty($returnAttributes) && empty($returnDocs)) {
23 304
            $returnDocs[] = (string) $query->getTableAlias($query->from);
24
25 304
            if ($query->joins !== null) {
26 6
                $returnDocs = $this->mergeJoinResults($query, $returnDocs);
27
            }
28
        }
29 389
        return $returnDocs;
30
    }
31
32
    /**
33
     * @param Builder $query
34
     * @param mixed[] $returnDocs
35
     * @param mixed[] $returnAttributes
36
     * @return mixed[]
37
     */
38 389
    public function processAggregateReturnValues(Builder $query, array $returnDocs, array $returnAttributes): array
39
    {
40 389
        if ($query->aggregate !== null && $query->unions === null) {
41 77
            $returnDocs = [];
42 77
            $returnAttributes = ['aggregate' => 'aggregateResult'];
43
        }
44 389
        return [$returnDocs, $returnAttributes];
45
    }
46
47
    /**
48
     * Compile the "select *" portion of the query.
49
     *
50
     * @param IlluminateQueryBuilder $query
51
     * @param array<mixed> $columns
52
     * @return string|null
53
     * @throws Exception
54
     */
55 389
    protected function compileColumns(IlluminateQueryBuilder $query, $columns)
56
    {
57
        assert($query instanceof Builder);
58
59 389
        $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

59
        /** @scrutinizer ignore-call */ 
60
        $columns = $this->convertJsonFields($columns);
Loading history...
60
61 389
        [$returnAttributes, $returnDocs] = $this->prepareColumns($query, $columns);
62
63 389
        $returnValues = $this->determineReturnValues($query, $returnAttributes, $returnDocs);
64
65 389
        $return = 'RETURN ';
66 389
        if ($query->distinct) {
67 4
            $return .= 'DISTINCT ';
68
        }
69
70 389
        return $return . $returnValues;
71
    }
72
73
    /**
74
     * @param IlluminateQueryBuilder $query
75
     * @param array<mixed> $columns
76
     * @return array<mixed>
77
     * @throws Exception
78
     *
79
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
80
     */
81 389
    protected function prepareColumns(IlluminateQueryBuilder $query, array $columns)
82
    {
83
        assert($query instanceof Builder);
84
85 389
        $returnDocs = [];
86 389
        $returnAttributes = [];
87
88 389
        foreach ($columns as $key => $column) {
89
            // Extract complete documents
90 389
            if (is_string($column) && substr($column, strlen($column) - 2)  === '.*') {
91 21
                $table = substr($column, 0, strlen($column) - 2);
92 21
                $returnDocs[] = $query->getTableAlias($table);
93
94 21
                continue;
95
            }
96
97
            // Extract groups
98 389
            if (is_array($query->groups) && in_array($column, $query->groups)) {
99 10
                $returnAttributes[$column] = $this->normalizeColumn($query, $column);
100
101 10
                continue;
102
            }
103
104 380
            if (is_string($column) && $column != null && $column != '*') {
105 151
                [$column, $alias] = $this->normalizeStringColumn($query, $key, $column);
106
107 151
                if (isset($returnAttributes[$alias]) && is_array($column)) {
108
                    $returnAttributes[$alias] = array_merge_recursive(
109
                        $returnAttributes[$alias],
110
                        $this->normalizeColumn($query, $column),
111
                    );
112
                    continue;
113
                }
114 151
                $returnAttributes[$alias] = $column;
115
            }
116
        }
117
118 389
        return [
119 389
            $returnAttributes,
120 389
            $returnDocs,
121 389
        ];
122
    }
123
124
    /**
125
     * @throws Exception
126
     */
127 330
    protected function normalizeColumn(IlluminateQueryBuilder $query, mixed $column, ?string $table = null): mixed
128
    {
129
        assert($query instanceof Builder);
130
131 330
        if ($column instanceof Expression) {
132
            return $column;
133
        }
134
135
        if (
136 330
            is_array($query->groups)
137 330
            && in_array($column, $query->groups)
138 330
            && debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT)[1]['function'] !== "compileGroups"
139
        ) {
140 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

140
            return $this->/** @scrutinizer ignore-call */ wrap($query->convertIdToKey($column));
Loading history...
141
        }
142
143 330
        if (is_array($column)) {
144
            $column = $this->convertJsonFields($column);
145
146
            foreach ($column as $key => $value) {
147
                $column[$key] = $this->normalizeColumn($query, $value, $table);
148
            }
149
            return $column;
150
        }
151
152 330
        if ($query->isVariable($column)) {
153 1
            return $this->wrap($column);
154
        }
155
156
157 330
        $column = $this->convertJsonFields($column);
158 330
        $column = $query->convertIdToKey($column);
159
160
        //We check for an existing alias to determine of the first reference is a table.
161
        // In which case we replace it with the alias.
162 330
        return $this->wrap($this->normalizeColumnReferences($query, $column, $table));
163
    }
164
165
    /**
166
     * @param Builder $query
167
     * @param int|string $key
168
     * @param string $column
169
     * @param string|null $table
170
     * @return array<mixed>
171
     * @throws Exception
172
     */
173 151
    protected function normalizeStringColumn(Builder $query, int|string $key, string $column, ?string $table = null): array
174
    {
175 151
        [$column, $alias] = $query->extractAlias($column, $key);
176
177 151
        $column = $query->convertIdToKey($column);
178
179 151
        $column = $this->wrap($this->normalizeColumnReferences($query, $column, $table));
180
181
        /** @phpstan-ignore-next-line */
182 151
        $alias = $this->cleanAlias($query, $alias);
183
184 151
        return [$column, $alias];
185
    }
186
187
188
    /**
189
     * @param IlluminateQueryBuilder $query
190
     * @param string $column
191
     * @param string|null $table
192
     * @return string
193
     */
194 353
    protected function normalizeColumnReferences(IlluminateQueryBuilder $query, string $column, ?string $table = null): string
195
    {
196
        assert($query instanceof Builder);
197
198 353
        if ($query->isReference($column)) {
199 121
            return $column;
200
        }
201
202 322
        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...
203 322
            $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

203
            $table = (string) $this->/** @scrutinizer ignore-call */ getValue($query->from);
Loading history...
204
        }
205
206 322
        $references = explode('.', $column);
207
208 322
        $tableAlias = $query->getTableAlias($references[0]);
209
210 322
        if (isset($tableAlias)) {
211 50
            $references[0] = $tableAlias;
212
        }
213
214 322
        if (array_key_exists('groupsVariable', $query->tableAliases)) {
215 1
            $tableAlias = 'groupsVariable';
216 1
            array_unshift($references, $tableAlias);
217
        }
218
219
        // geen tableAlias, table is parent...waarom geen tableAlias?
220 322
        if ($tableAlias === null  && array_key_exists($table, $query->tableAliases)) {
221 310
            array_unshift($references, $query->tableAliases[$table]);
222
        }
223
224 322
        if ($tableAlias === null && !$query->isReference($references[0])) {
225 3
            $tableAlias = $query->generateTableAlias($table);
226 3
            array_unshift($references, $tableAlias);
227
        }
228
229
        /** @phpstan-ignore-next-line */
230 322
        return implode('.', $references);
231
    }
232
233 151
    protected function cleanAlias(IlluminateQueryBuilder $query, int|null|string $alias): int|string|null
234
    {
235
        assert($query instanceof Builder);
236
237 151
        if (!is_string($alias)) {
238
            return $alias;
239
        }
240
241 151
        if (!str_contains($alias, '.')) {
242 149
            return $alias;
243
        }
244
245 8
        $elements = explode('.', $alias);
246
247
        if (
248 8
            !$query->isTable($elements[0])
249 8
            && !$query->isVariable($elements[0])
250
        ) {
251 6
            return $alias;
252
        }
253
254 2
        array_shift($elements);
255
256 2
        return implode($elements);
257
    }
258
259
260
    /**
261
     * @param IlluminateQueryBuilder $query
262
     * @param array<string> $returnAttributes
263
     * @param array<string>  $returnDocs
264
     * @return string
265
     */
266 389
    protected function determineReturnValues(IlluminateQueryBuilder $query, $returnAttributes = [], $returnDocs = []): string
267
    {
268
        assert($query instanceof Builder);
269
270
        // If nothing was specifically requested, we return everything.
271 389
        $returnDocs = $this->processEmptyReturnValues($returnAttributes, $returnDocs, $query);
272
273
        // Aggregate functions only return the aggregate, so we can clear out everything else.
274 389
        list($returnDocs, $returnAttributes) = $this->processAggregateReturnValues($query, $returnDocs, $returnAttributes);
275
276
        // Return a single value for certain subqueries
277
        if (
278 389
            $query->returnSingleValue === true
279 389
            && count($returnAttributes) === 1
280 389
            && empty($returnDocs)
281
        ) {
282 3
            return reset($returnAttributes);
283
        }
284
285 389
        if (!empty($returnAttributes)) {
286 165
            $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

286
            /** @scrutinizer ignore-call */ 
287
            $returnDocs[] = $this->generateAqlObject($returnAttributes);
Loading history...
287
        }
288
289 389
        $values = $this->mergeReturnDocs($returnDocs);
290
291 389
        return $values;
292
    }
293
294
    /**
295
     * @param array<string> $returnDocs
296
     * @return string
297
     */
298 389
    protected function mergeReturnDocs($returnDocs)
299
    {
300 389
        if (sizeOf($returnDocs) > 1) {
301 27
            return 'MERGE(' . implode(', ', $returnDocs) . ')';
302
        }
303
304 385
        return $returnDocs[0];
305
    }
306
307
    /**
308
     * @param IlluminateQueryBuilder $query
309
     * @param array<string> $returnDocs
310
     * @return array<string>
311
     */
312 6
    protected function mergeJoinResults(IlluminateQueryBuilder $query, $returnDocs = []): array
313
    {
314
        assert($query instanceof Builder);
315
316 6
        if (!is_array($query->joins)) {
317
            return $returnDocs;
318
        }
319
320 6
        foreach ($query->joins as $join) {
321 6
            $tableAlias = $query->getTableAlias($join->table);
322
323 6
            if (!isset($tableAlias)) {
324
                $tableAlias = $query->generateTableAlias($join->table);
325
            }
326 6
            $returnDocs[] = (string) $tableAlias;
327
        }
328
329 6
        return $returnDocs;
330
    }
331
}
332