Failed Conditions
Push — refactor/improve-static-analys... ( a7b39f...bdf823 )
by Bas
12:11
created

CompilesColumns::mergeJoinResults()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 9
c 2
b 0
f 0
dl 0
loc 16
ccs 0
cts 0
cp 0
rs 9.9666
cc 3
nc 3
nop 2
crap 12
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 IlluminateBuilder;
9
use Illuminate\Support\Arr;
10
use LaravelFreelancerNL\Aranguent\Query\Builder;
11
use LaravelFreelancerNL\FluentAQL\Expressions\FunctionExpression;
12
use LaravelFreelancerNL\FluentAQL\QueryBuilder;
13
14
trait CompilesColumns
15
{
16
    /**
17 145
     * Compile the "select *" portion of the query.
18
     *
19 145
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
20 145
     *
21 145
     * @param IlluminateBuilder $query
22
     * @param array<mixed> $columns
23
     * @return string|null
24 145
     * @throws Exception
25
     */
26 145
    protected function compileColumns(IlluminateBuilder $query, $columns)
27 12
    {
28 12
        $returnDocs = [];
29
        $returnAttributes = [];
30 12
31
        // Prepare columns
32
        foreach ($columns as $key => $column) {
33
            // Extract rows
34 145
            if (is_string($column) && substr($column, strlen($column) - 2)  === '.*') {
35 5
                $table = substr($column, 0, strlen($column) - 2);
36
                $returnDocs[] = $this->getTableAlias($table);
0 ignored issues
show
Bug introduced by
It seems like getTableAlias() 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

36
                /** @scrutinizer ignore-call */ 
37
                $returnDocs[] = $this->getTableAlias($table);
Loading history...
37 5
38
                continue;
39
            }
40 140
41 34
            // Extract groups
42
            if (is_array($query->groups) && in_array($column, $query->groups)) {
43 34
                $returnAttributes[$key] = $column;
44
45
                continue;
46 145
            }
47
48 145
            if (is_string($column) && $column != null && $column != '*') {
49
                [$column, $alias] = $this->normalizeStringColumn($query, $key, $column);
50 145
51
                if (isset($returnAttributes[$alias]) && is_array($column)) {
52
                    $normalizedColumn = $this->normalizeColumn($query, $column);
53 145
54
                    if (is_array($normalizedColumn)) {
55 145
                        foreach ($normalizedColumn as $key => $value) {
0 ignored issues
show
Comprehensibility Bug introduced by
$key is overwriting a variable from outer foreach loop.
Loading history...
56
                            $returnAttributes[$alias][$key] = $value;
57 145
                        }
58
                    }
59 145
                }
60 24
                $returnAttributes[$alias] = $column;
61
            }
62
        }
63 145
64 118
        $values = $this->determineReturnValues($query, $returnAttributes, $returnDocs);
65 118
66 4
        $aql = 'RETURN';
67
        if ((bool) $query->distinct) {
68
            $aql .= ' DISTINCT';
69
        }
70 145
        $aql .= ' ' . $this->compileValuesToAql($values);
71
72
        return $aql;
73 145
    }
74
75 145
    protected function compileValuesToAql(mixed $values): string
76 145
    {
77 39
        if (is_string($values)) {
78
            return $values;
79
        }
80
81
        $compiledValues = '{';
82 145
        foreach ($values as $key => $value) {
83 17
            if (is_array($value) && Arr::isAssoc($value)) {
84
                $value = $this->compileValuesToAql($value);
85
            }
86 145
            $compiledValues .= "{$key}: {$value}, ";
87
        }
88
        $compiledValues = substr($compiledValues, 0, strlen($compiledValues) -2);
89 145
        $compiledValues .= '}';
90
91 145
        return $compiledValues;
92 12
    }
93
94
95 145
    /**
96 12
     * @throws Exception
97
     */
98 145
    protected function normalizeColumn(IlluminateBuilder $query, mixed $column, string $table = null): mixed
99
    {
100
        if ($column instanceof QueryBuilder || $column instanceof FunctionExpression) {
101
            return $column;
102 4
        }
103
104 4
        $column = $this->convertColumnId($column);
0 ignored issues
show
Bug introduced by
It seems like convertColumnId() 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

104
        /** @scrutinizer ignore-call */ 
105
        $column = $this->convertColumnId($column);
Loading history...
105 4
106 4
        if (
107
            is_array($query->groups)
108 4
            && in_array($column, $query->groups)
109 4
            && debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT)[1]['function'] !== "compileGroups"
110
        ) {
111 4
            return $column;
112
        }
113
114
        if (is_array($column)) {
115
            foreach ($column as $key => $value) {
116
                if (! is_string($value)) {
117
                    $column[$key] = $this->normalizeColumn($query, $value, $table);
118
                }
119
120
                if (is_string($value)) {
121
                    [$subColumn, $alias] = $this->normalizeStringColumn($query, $key, $value);
122
                    $column[$alias] = $subColumn;
123
                }
124
            }
125
126
            return $column;
127
        }
128
129
        if (key_exists($column, $query->variables)) {
130
            return $column;
131
        }
132
133
        //We check for an existing alias to determine of the first reference is a table.
134
        // In which case we replace it with the alias.
135
        return $this->normalizeColumnReferences($query, $column, $table);
136
    }
137
138
    /**
139
     * @return array<mixed>
140
     * @throws Exception
141
     */
142
    protected function normalizeStringColumn(IlluminateBuilder $query, int|string $key, string $column): array
143
    {
144
        [$column, $alias] = $this->extractAlias($column, $key);
0 ignored issues
show
Bug introduced by
It seems like extractAlias() 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

144
        /** @scrutinizer ignore-call */ 
145
        [$column, $alias] = $this->extractAlias($column, $key);
Loading history...
145
146
        if (! isDotString($alias)) {
147
            $column = $this->normalizeColumn($query, $column);
148
149
            return [$column, $alias];
150
        }
151
152
        $column = Arr::undot([$column => $column]);
153
        $alias = array_key_first($column);
154
        $column = $column[$alias];
155
156
        return [$column, $alias];
157
    }
158
159
160
    /**
161
     * @param Builder $query
162
     * @param string $column
163
     * @param string|null $table
164
     * @return string
165
     */
166
    protected function normalizeColumnReferences(Builder $query, string $column, string $table = null): string
167
    {
168
        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...
169
            $table = $query->from;
170
        }
171
172
        // Replace SQL JSON arrow for AQL dot
173
        $column = str_replace('->', '.', $column);
174
175
        $references = explode('.', $column);
176
177
178
        $tableAlias = $this->getTableAlias($references[0]);
179
        if (isset($tableAlias)) {
180
            $references[0] = $tableAlias;
181
        }
182
183
        if ($tableAlias === null && $table != null && ! $this->isTableAlias($references[0])) {
0 ignored issues
show
Bug introduced by
It seems like isTableAlias() 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

183
        if ($tableAlias === null && $table != null && ! $this->/** @scrutinizer ignore-call */ isTableAlias($references[0])) {
Loading history...
184
            $tableAlias = $this->generateTableAlias($table);
0 ignored issues
show
Bug introduced by
It seems like generateTableAlias() 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

184
            /** @scrutinizer ignore-call */ 
185
            $tableAlias = $this->generateTableAlias($table);
Loading history...
185
            array_unshift($references, $tableAlias);
186
        }
187
188
        return $this->wrap(implode('.', $references));
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

188
        return $this->/** @scrutinizer ignore-call */ wrap(implode('.', $references));
Loading history...
189
    }
190
191
192
    protected function determineReturnValues($query, $returnAttributes = [], $returnDocs = [])
193
    {
194
        $values = $this->mergeReturnAttributes($returnAttributes, $returnDocs);
195
196
        $values = $this->mergeReturnDocs($values, $query, $returnAttributes, $returnDocs);
197
198
        if ($query->aggregate !== null) {
199
            $values = ['aggregate' => 'aggregateResult'];
200
        }
201
202
        if (empty($values)) {
203
            $values = $this->getTableAlias($query->from);
204
            if (is_array($query->joins) && !empty($query->joins)) {
205
                $values = $this->mergeJoinResults($query, $values);
206
            }
207
        }
208
209
        return $values;
210
    }
211
212
    protected function mergeReturnAttributes($returnAttributes, $returnDocs)
213
    {
214
        $values = [];
215
        if (! empty($returnAttributes)) {
216
            $values = $returnAttributes;
217
        }
218
219
        // If there is just one attribute/column given we assume that you want a list of values
220
        //  instead of a list of objects
221
        if (count($returnAttributes) == 1 && empty($returnDocs)) {
222
            $values = reset($returnAttributes);
223
        }
224
225
        return $values;
226
    }
227
228
    protected function mergeReturnDocs($values, $query, $returnAttributes, $returnDocs)
229
    {
230
        if (! empty($returnAttributes) && ! empty($returnDocs)) {
231
            $returnDocs[] = $returnAttributes;
232
        }
233
234
        if (! empty($returnDocs)) {
235
            $values = $query->aqb->merge(...$returnDocs);
236
        }
237
        return $values;
238
    }
239
240
241
    protected function mergeJoinResults($query, $baseTable)
242
    {
243
        $tablesToJoin = [];
244
        foreach ($query->joins as $key => $join) {
245
            $tableAlias = $this->getTableAlias($join->table);
246
247
            if (! isset($tableAlias)) {
248
                $tableAlias = $this->generateTableAlias($join->table);
249
            }
250
            $tablesToJoin[$key] = $tableAlias;
251
        }
252
253
        $tablesToJoin = array_reverse($tablesToJoin);
254
        $tablesToJoin[] = $baseTable;
255
256
        return $query->aqb->merge(...$tablesToJoin);
257
    }
258
}
259