Passed
Push — next ( bea07e...b4e6e6 )
by Bas
05:12 queued 01:45
created

CompilesColumns::mergeJoinResults()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.1755

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
nc 4
nop 2
dl 0
loc 18
ccs 7
cts 9
cp 0.7778
crap 4.1755
rs 9.9666
c 2
b 0
f 0
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