Failed Conditions
Push — refactor/improve-static-analys... ( 8065ea...92b34c )
by Bas
05:43
created

CompilesColumns::compileColumns()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 2
b 0
f 0
nc 2
nop 2
dl 0
loc 16
ccs 8
cts 8
cp 1
crap 2
rs 10
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 145
     * @param IlluminateQueryBuilder $query
18
     * @param array $columns
19 145
     * @return string|null
20 145
     * @throws Exception
21 145
     */
22
    protected function compileColumns(IlluminateQueryBuilder $query, $columns)
23
    {
24 145
        assert($query instanceof Builder);
25
26 145
        $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 12
28 12
        [$returnAttributes, $returnDocs] = $this->prepareColumns($query, $columns);
29
30 12
        $returnValues = $this->determineReturnValues($query, $returnAttributes, $returnDocs);
31
32
        $return = 'RETURN ';
33
        if ($query->distinct) {
34 145
            $return .= 'DISTINCT ';
35 5
        }
36
37 5
        return $return . $returnValues;
38
    }
39
40 140
    /**
41 34
     * @param IlluminateQueryBuilder $query
42
     * @param array<mixed> $columns
43 34
     * @return array<mixed>
44
     * @throws Exception
45
     */
46 145
    protected function prepareColumns(IlluminateQueryBuilder $query, array $columns)
47
    {
48 145
        assert($query instanceof Builder);
49
50 145
        $returnDocs = [];
51
        $returnAttributes = [];
52
53 145
        foreach ($columns as $key => $column) {
54
            // Extract complete documents
55 145
            if (is_string($column) && substr($column, strlen($column) - 2)  === '.*') {
56
                $table = substr($column, 0, strlen($column) - 2);
57 145
                $returnDocs[] = $query->getTableAlias($table);
58
59 145
                continue;
60 24
            }
61
62
            // Extract groups
63 145
            if (is_array($query->groups) && in_array($column, $query->groups)) {
64 118
                $returnAttributes[$column] = $this->normalizeColumn($query, $column);
65 118
66 4
                continue;
67
            }
68
69
            if (is_string($column) && $column != null && $column != '*') {
70 145
                [$column, $alias] = $this->normalizeStringColumn($query, $key, $column);
71
72
                if (isset($returnAttributes[$alias]) && is_array($column)) {
73 145
                    $returnAttributes[$alias] = array_merge_recursive(
74
                        $returnAttributes[$alias],
75 145
                        $this->normalizeColumn($query, $column)
76 145
                    );
77 39
                    continue;
78
                }
79
                $returnAttributes[$alias] = $column;
80
            }
81
        }
82 145
83 17
        return [
84
            $returnAttributes,
85
            $returnDocs
86 145
        ];
87
    }
88
89 145
    /**
90
     * @throws Exception
91 145
     */
92 12
    protected function normalizeColumn(IlluminateQueryBuilder $query, mixed $column, string $table = null): mixed
93
    {
94
        assert($query instanceof Builder);
95 145
96 12
        if ($column instanceof Expression) {
97
            return $column;
98 145
        }
99
100
        if (
101
            is_array($query->groups)
102 4
            && in_array($column, $query->groups)
103
            && debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT)[1]['function'] !== "compileGroups"
104 4
        ) {
105 4
            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

105
            return $this->/** @scrutinizer ignore-call */ wrap($query->convertIdToKey($column));
Loading history...
106 4
        }
107
108 4
        if (is_array($column)) {
109 4
            $column = $this->convertJsonFields($column);
110
111 4
            foreach ($column as $key => $value) {
112
                $column[$key] = $this->normalizeColumn($query, $value, $table);
113
            }
114
            return $column;
115
        }
116
117
        if ($query->isVariable($column)) {
118
            return $this->wrap($column);
119
        }
120
121
122
        $column = $this->convertJsonFields($column);
123
        $column = $query->convertIdToKey($column);
124
125
        //We check for an existing alias to determine of the first reference is a table.
126
        // In which case we replace it with the alias.
127
        return $this->wrap($this->normalizeColumnReferences($query, $column, $table));
0 ignored issues
show
Bug introduced by
It seems like $column can also be of type array and array and array<mixed,mixed> and array<mixed,mixed>; however, parameter $column of LaravelFreelancerNL\Aran...alizeColumnReferences() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

127
        return $this->wrap($this->normalizeColumnReferences($query, /** @scrutinizer ignore-type */ $column, $table));
Loading history...
128
    }
129
130
    /**
131
     * @param string $column
132
     * @return array<mixed>
133
     * @throws Exception
134
     */
135
    protected function normalizeStringColumn(Builder $query, int|string $key, string $column, string $table = null): array
136
    {
137
        [$column, $alias] = $query->extractAlias($column, $key);
138
139
        $column = $query->convertIdToKey($column);
140
141
        $column = $this->wrap($this->normalizeColumnReferences($query, $column, $table));
0 ignored issues
show
Bug introduced by
It seems like $column can also be of type array and array and array<mixed,mixed> and array<mixed,mixed>; however, parameter $column of LaravelFreelancerNL\Aran...alizeColumnReferences() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

141
        $column = $this->wrap($this->normalizeColumnReferences($query, /** @scrutinizer ignore-type */ $column, $table));
Loading history...
142
143
        $alias = $this->cleanAlias($query, $alias);
144
145
        return [$column, $alias];
146
    }
147
148
149
    /**
150
     * @param IlluminateQueryBuilder $query
151
     * @param string $column
152
     * @param string|null $table
153
     * @return string
154
     */
155
    protected function normalizeColumnReferences(IlluminateQueryBuilder $query, string $column, string $table = null): string
156
    {
157
        assert($query instanceof Builder);
158
159
        if ($query->isReference($column)) {
160
            return $column;
161
        }
162
163
        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...
164
            $table = $query->from;
165
        }
166
167
        $references = explode('.', $column);
168
169
170
        $tableAlias = $query->getTableAlias($references[0]);
171
172
        if (isset($tableAlias)) {
173
            $references[0] = $tableAlias;
174
        }
175
176
        if (array_key_exists('groupsVariable', $query->tableAliases)) {
177
            $tableAlias = 'groupsVariable';
178
            array_unshift($references, $tableAlias);
179
        }
180
181
        if ($tableAlias === null  && array_key_exists($table, $query->tableAliases)) {
182
            array_unshift($references, $query->tableAliases[$table]);
183
        }
184
185
        if ($tableAlias === null && !$query->isReference($references[0])) {
186
            $tableAlias = $query->generateTableAlias($table);
187
            array_unshift($references, $tableAlias);
188
        }
189
190
        return implode('.', $references);
191
    }
192
193
    protected function cleanAlias(IlluminateQueryBuilder $query, int|null|string $alias): int|string|null
194
    {
195
        assert($query instanceof Builder);
196
197
        if (!is_string($alias)) {
198
            return $alias;
199
        }
200
201
        if (!str_contains($alias, '.')) {
202
            return $alias;
203
        }
204
205
        $elements = explode('.', $alias);
206
207
        if(
208
            !$query->isTable($elements[0])
209
            && !$query->isVariable($elements[0])
210
        ) {
211
            return $alias;
212
        }
213
214
        array_shift($elements);
215
216
        return implode($elements);
217
    }
218
219
220
    protected function determineReturnValues($query, $returnAttributes = [], $returnDocs = []): string
221
    {
222
        // If nothing was specifically requested, we return everything.
223
        if (empty($returnAttributes) && empty($returnDocs)) {
224
            $returnDocs[] = $query->getTableAlias($query->from);
225
226
            if ($query->joins !== null) {
227
                $returnDocs = $this->mergeJoinResults($query, $returnDocs);
228
            }
229
        }
230
231
        // clean up returnAttributes?
232
233
        // Aggregate functions only return the aggregate, so we can clear out everything else.
234
        if ($query->aggregate !== null) {
235
            $returnDocs = [];
236
            $returnAttributes = ['`aggregate`' => 'aggregateResult'];
237
        }
238
239
        // Return a single value for certain subqueries
240
        if (
241
            $query->returnSingleValue === true
242
            && count($returnAttributes) === 1
243
            && empty($returnDocs)
244
        ) {
245
            return reset($returnAttributes);
246
        }
247
248
        if (!empty($returnAttributes)) {
249
            $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

249
            /** @scrutinizer ignore-call */ 
250
            $returnDocs[] = $this->generateAqlObject($returnAttributes);
Loading history...
250
        }
251
252
        $values = $this->mergeReturnDocs($returnDocs);
253
254
        return $values;
255
    }
256
257
    protected function mergeReturnDocs($returnDocs)
258
    {
259
        if (sizeOf($returnDocs) > 1) {
260
            return 'MERGE(' . implode(', ', $returnDocs) . ')';
261
        }
262
263
        return reset($returnDocs);
264
    }
265
266
    protected function mergeJoinResults(IlluminateQueryBuilder $query, $returnDocs = []): array
267
    {
268
        assert($query instanceof Builder);
269
270
        foreach ($query->joins as $join) {
271
            $tableAlias = $query->getTableAlias($join->table);
272
273
            if (!isset($tableAlias)) {
274
                $tableAlias = $query->generateTableAlias($join->table);
275
            }
276
            $returnDocs[] = $tableAlias;
277
        }
278
279
        return $returnDocs;
280
    }
281
}
282