HandlesAqlGrammar::convertJsonKeysToDotNotation()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent\Query\Concerns;
6
7
use Illuminate\Support\Arr;
8
use Illuminate\Database\Query\Expression;
9
10
trait HandlesAqlGrammar
11
{
12
    /**
13
     * Available predicate operators.
14
     *
15
     * @var array<string, int>
16
     */
17
    protected array $comparisonOperators = [
18
        '=='      => 1,
19
        '!='      => 1,
20
        '<'       => 1,
21
        '>'       => 1,
22
        '<='      => 1,
23
        '>='      => 1,
24
        'IN'      => 1,
25
        'NOT IN'  => 1,
26
        'LIKE'    => 1,
27
        '~'       => 1,
28
        '!~'      => 1,
29
        'ALL =='  => 1,
30
        'ALL !='  => 1,
31
        'ALL <'   => 1,
32
        'ALL >'   => 1,
33
        'ALL <='  => 1,
34
        'ALL >='  => 1,
35
        'ALL IN'  => 1,
36
        'ANY =='  => 1,
37
        'ANY !='  => 1,
38
        'ANY <'   => 1,
39
        'ANY >'   => 1,
40
        'ANY <='  => 1,
41
        'ANY >='  => 1,
42
        'ANY IN'  => 1,
43
        'NONE ==' => 1,
44
        'NONE !=' => 1,
45
        'NONE <'  => 1,
46
        'NONE >'  => 1,
47
        'NONE <=' => 1,
48
        'NONE >=' => 1,
49
        'NONE IN' => 1,
50
    ];
51
52
    /**
53
     * @var array<string, int>
54
     */
55
    protected array $arithmeticOperators = [
56
        '+' => 1,
57
        '-' => 1,
58
        '*' => 1,
59
        '/' => 1,
60
        '%' => 1,
61
    ];
62
63
    /**
64
     * @var array|int[]
65
     */
66
    protected array $logicalOperators = [
67
        'AND' => 1,
68
        '&&'  => 1,
69
        'OR'  => 1,
70
        '||'  => 1,
71
        'NOT' => 1,
72
        '!'   => 1,
73
    ];
74
75
    protected string $rangeOperator = '..';
76
77 292
    public function isBind(mixed $value): bool
78
    {
79 292
        if (is_string($value) && preg_match('/^@?[0-9]{4}_[a-zA-Z0-9_$]_[0-9_]+$/', $value)) {
80
            return true;
81
        }
82
83 292
        return false;
84
    }
85
86
    /**
87
     * Get the appropriate query parameter place-holder for a value.
88
     *
89
     * @param  mixed  $value
90
     */
91
    public function parameter($value): string
92
    {
93
        return $this->isExpression($value) ? $this->getValue($value) : (string) $value;
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

93
        return $this->isExpression($value) ? $this->/** @scrutinizer ignore-call */ getValue($value) : (string) $value;
Loading history...
Bug introduced by
It seems like isExpression() 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

93
        return $this->/** @scrutinizer ignore-call */ isExpression($value) ? $this->getValue($value) : (string) $value;
Loading history...
94
    }
95
96
97
    /**
98
     * Quote the given string literal.
99
     *
100
     * @param  string|array<string>  $value
101
     * @return string
102
     */
103
    public function quoteString($value)
104
    {
105
        if (is_array($value)) {
106
            return implode(', ', array_map([$this, __FUNCTION__], $value));
107
        }
108
109
        return "`$value`";
110
    }
111
112
113
    /**
114
     * Wrap a value in keyword identifiers.
115
     *
116
     * @param  Array<mixed>|Expression|string  $value
117
     * @return array<mixed>|float|int|string
118
     *
119
     * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
120
     */
121 354
    public function wrap($value)
122
    {
123 354
        if ($value instanceof Expression) {
124
            return $value->getValue($this);
0 ignored issues
show
Bug introduced by
$this of type LaravelFreelancerNL\Aran...cerns\HandlesAqlGrammar is incompatible with the type Illuminate\Database\Grammar expected by parameter $grammar of Illuminate\Database\Query\Expression::getValue(). ( Ignorable by Annotation )

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

124
            return $value->getValue(/** @scrutinizer ignore-type */ $this);
Loading history...
125
        }
126
127 354
        if (is_array($value)) {
128
            foreach ($value as $key => $subvalue) {
129
                $value[$key] = $this->wrap($subvalue);
130
            }
131
            return $value;
132
        }
133
134
        // If the value being wrapped has a column alias we will need to separate out
135
        // the pieces so we can wrap each of the segments of the expression on its
136
        // own, and then join these both back together using the "as" connector.
137 354
        if (is_string($value) && stripos($value, ' as ') !== false) {
138
            return $this->wrapAliasedValue($value);
0 ignored issues
show
Bug introduced by
It seems like wrapAliasedValue() 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

138
            return $this->/** @scrutinizer ignore-call */ wrapAliasedValue($value);
Loading history...
139
        }
140
141 354
        return $this->wrapSegments(explode('.', $value));
0 ignored issues
show
Bug introduced by
It seems like wrapSegments() 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

141
        return $this->/** @scrutinizer ignore-call */ wrapSegments(explode('.', $value));
Loading history...
142
    }
143
144
145
    /**
146
     * Wrap a table in keyword identifiers.
147
     *
148
     * @param Expression|string  $table
149
     * @return float|int|string
150
     */
151 352
    public function wrapTable($table, $prefix = null)
152
    {
153 352
        if (!$table instanceof Expression) {
154 352
            $wrappedTable = $this->wrap(($prefix ?? $this->tablePrefix) . $table);
155
156
            assert(!is_array($wrappedTable));
157
158 352
            return $wrappedTable;
159
        }
160
161
        return $table->getValue($this);
0 ignored issues
show
Bug introduced by
$this of type LaravelFreelancerNL\Aran...cerns\HandlesAqlGrammar is incompatible with the type Illuminate\Database\Grammar expected by parameter $grammar of Illuminate\Database\Query\Expression::getValue(). ( Ignorable by Annotation )

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

161
        return $table->getValue(/** @scrutinizer ignore-type */ $this);
Loading history...
162
    }
163
164
    /**
165
     * Wrap a single string in keyword identifiers.
166
     *
167
     * @param  string  $value
168
     * @return string
169
     */
170 354
    protected function wrapValue($value)
171
    {
172 354
        $postfix = '';
173 354
        if ($value === 'groupsVariable') {
174 1
            $postfix = '[*]';
175
        }
176
177 354
        if ($value === '*') {
178 2
            return $value;
179
        }
180
181 354
        return '`' . str_replace('`', '``', $value) . '`' . $postfix;
182
    }
183
184
    /**
185
     * Wrap a single string in keyword identifiers.
186
     *
187
     * @param  string  $value
188
     * @return string
189
     */
190 196
    protected function wrapAttribute($value)
191
    {
192 196
        if (!is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
193 1
            return $value;
194
        }
195 196
        return '`' . str_replace('`', '``', $value) . '`';
196
    }
197
198
199
    /**
200
     * Wrap a subquery single string in braces.
201
     */
202 50
    public function wrapSubquery(string $subquery): string
203
    {
204 50
        return '(' . $subquery . ')';
205
    }
206
207
    /**
208
     * @param array<mixed> $data
209
     * @return string
210
     */
211 196
    public function generateAqlObject(array $data): string
212
    {
213 196
        $data = Arr::undot($data);
214 196
        return $this->generateAqlObjectString($data);
215
    }
216
217
    /**
218
     * @param array<mixed> $data
219
     * @return string
220
     */
221 196
    protected function generateAqlObjectString(array $data): string
222
    {
223 196
        foreach ($data as $key => $value) {
224 196
            $prefix = $this->wrapAttribute($key) . ': ';
225
226 196
            if (is_numeric($key)) {
227 1
                $prefix = '';
228
            }
229
230 196
            if (is_bool($value)) {
231
                $booleanString = ($value) ? 'true' : 'false';
232
                $data[$key] = $prefix . $booleanString;
233
234
                continue;
235
            }
236
237 196
            if (is_array($value)) {
238 9
                $data[$key] = $prefix . $this->generateAqlObjectString($value);
239 9
                continue;
240
            }
241
242 196
            if ($value instanceof Expression) {
243
                $data[$key] = $prefix . $value->getValue($this);
0 ignored issues
show
Bug introduced by
$this of type LaravelFreelancerNL\Aran...cerns\HandlesAqlGrammar is incompatible with the type Illuminate\Database\Grammar expected by parameter $grammar of Illuminate\Database\Query\Expression::getValue(). ( Ignorable by Annotation )

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

243
                $data[$key] = $prefix . $value->getValue(/** @scrutinizer ignore-type */ $this);
Loading history...
244
                continue;
245
            }
246
247 196
            $data[$key] = $prefix . $value;
248
        }
249
250 196
        $returnString = implode(', ', $data);
251
252 196
        if (array_is_list($data)) {
253 1
            return '[' . $returnString . ']';
254
        }
255
256 196
        return '{' . $returnString . '}';
257
    }
258
259
    /**
260
     * Substitute the given bindings into the given raw AQL query.
261
     *
262
     * @param  string  $aql
263
     * @param  array<mixed>  $bindings
264
     * @return string
265
     */
266 4
    public function substituteBindingsIntoRawSql($aql, $bindings)
267
    {
268 4
        $bindings = array_map(fn($value) => $this->escape($value), $bindings);
0 ignored issues
show
Bug introduced by
It seems like escape() 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

268
        $bindings = array_map(fn($value) => $this->/** @scrutinizer ignore-call */ escape($value), $bindings);
Loading history...
269
270 4
        $bindings = array_reverse($bindings);
271
272 4
        foreach ($bindings as $key => $value) {
273 4
            $pattern = '/(@' . $key . ')(?![^a-zA-Z_ ,\}\]])/';
274 4
            $aql = (string) preg_replace(
275 4
                $pattern,
276 4
                $value,
277 4
                $aql,
278 4
            );
279
        }
280
281 4
        return $aql;
282
    }
283
284
    /**
285
     * Determine if the given string is a JSON selector.
286
     *
287
     * @param  string  $value
288
     * @return bool
289
     */
290 389
    public function isJsonSelector($value)
291
    {
292 389
        if (!is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
293 2
            return false;
294
        }
295
296 389
        return str_contains($value, '->');
297
    }
298
299 389
    public function convertJsonFields(mixed $data): mixed
300
    {
301 389
        if (!is_array($data) && !is_string($data)) {
302
            return $data;
303
        }
304
305 389
        if (is_string($data)) {
306 330
            return str_replace('->', '.', $data);
307
        }
308
309 389
        if (array_is_list($data)) {
310 387
            return $this->convertJsonValuesToDotNotation($data);
311
        }
312
313 34
        return $this->convertJsonKeysToDotNotation($data);
314
    }
315
316
    /**
317
     * @param array<string> $fields
318
     * @return array<string>
319
     */
320 387
    public function convertJsonValuesToDotNotation(array $fields): array
321
    {
322 387
        foreach ($fields as $key => $value) {
323 387
            if ($this->isJsonSelector($value)) {
324 1
                $fields[$key] = str_replace('->', '.', $value);
325
            }
326
        }
327 387
        return $fields;
328
    }
329
330
    /**
331
     * @param array<string> $fields
332
     * @return array<string>
333
     */
334 34
    public function convertJsonKeysToDotNotation(array $fields): array
335
    {
336 34
        foreach ($fields as $key => $value) {
337 34
            if ($this->isJsonSelector($key)) {
338 2
                $fields[str_replace('->', '.', $key)] = $value;
339 2
                unset($fields[$key]);
340
            }
341
        }
342 34
        return $fields;
343
    }
344
345
    /**
346
     * Translate sql operators to their AQL equivalent where possible.
347
     *
348
     * @param string $operator
349
     *
350
     * @return mixed|string
351
     */
352
    protected function translateOperator(string $operator)
353
    {
354
        if (isset($this->operatorTranslations[strtolower($operator)])) {
355
            $operator = $this->operatorTranslations[$operator];
356
        }
357
358
        return $operator;
359
    }
360
361
}
362