Passed
Pull Request — next (#84)
by Bas
15:17 queued 11:12
created

CompilesColumns::normalizeColumnReferences()   B

Complexity

Conditions 9
Paths 33

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 18
c 0
b 0
f 0
nc 33
nop 3
dl 0
loc 37
ccs 18
cts 18
cp 1
crap 9
rs 8.0555
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