Passed
Push — refactor/improve-static-analys... ( efcf20...37f12c )
by Bas
03:04
created

Builder::getBindings()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 3
rs 10
1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Query;
4
5
use Exception;
6
use Illuminate\Database\ConnectionInterface as IlluminateConnectionInterface;
7
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
8
use Illuminate\Database\Query\Expression;
9
use Illuminate\Database\Query\Grammars\Grammar as IlluminateQueryGrammar;
10
use Illuminate\Database\Query\Processors\Processor as IlluminateProcessor;
11
use LaravelFreelancerNL\Aranguent\Connection;
12
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsGroups;
13
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSearches;
14
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsInserts;
15
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsJoins;
16
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSelects;
17
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSubqueries;
18
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsUpdates;
19
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsWheres;
20
use LaravelFreelancerNL\Aranguent\Query\Concerns\ConvertsIdToKey;
21
use LaravelFreelancerNL\Aranguent\Query\Concerns\HandlesAliases;
22
use LaravelFreelancerNL\Aranguent\Query\Concerns\HandlesBindings;
23
use LaravelFreelancerNL\Aranguent\Query\Enums\VariablePosition;
24
use LaravelFreelancerNL\FluentAQL\QueryBuilder as AQB;
25
use phpDocumentor\Reflection\Types\Boolean;
26
27
class Builder extends IlluminateQueryBuilder
28
{
29
    use BuildsGroups;
0 ignored issues
show
introduced by
The trait LaravelFreelancerNL\Aran...y\Concerns\BuildsGroups requires some properties which are not provided by LaravelFreelancerNL\Aranguent\Query\Builder: $end, $start
Loading history...
30
    use BuildsInserts;
31
    use BuildsJoins;
32
    use BuildsSearches;
33
    use BuildsSelects;
34
    use BuildsSubqueries;
35
    use BuildsUpdates;
36
    use BuildsWheres;
37
    use ConvertsIdToKey;
38
    use HandlesAliases;
39
    use HandlesBindings;
40
    public AQB $aqb;
41
42
    /**
43
     * The current query value bindings.
44
     *
45
     * @var array<mixed>
46
     */
47
    public $bindings = [
48
        'preIterationVariables' => [],
49
        'from' => [],
50
        'search' => [],
51
        'join' => [],
52
        'postIterationVariables' => [],
53
        'where' => [],
54
        'groupBy' => [],
55
        'having' => [],
56
        'order' => [],
57
        'union' => [],
58
        'unionOrder' => [],
59
        'select' => [],
60
        'insert' => [],
61
        'update' => [],
62
        'upsert' => [],
63
    ];
64
65
66
    /**
67
     * @var Connection
68
     */
69
    public $connection;
70
71
    /**
72
     * @var IlluminateQueryGrammar
73
     */
74
    public $grammar;
75
76
    /**
77
     * The current query value bindings.
78
     *
79
     * @var null|array{fields: mixed, searchText: mixed, analyzer: non-falsy-string}
0 ignored issues
show
Documentation Bug introduced by
The doc comment null|array{fields: mixed...yzer: non-falsy-string} at position 14 could not be parsed: Unknown type name 'non-falsy-string' at position 14 in null|array{fields: mixed, searchText: mixed, analyzer: non-falsy-string}.
Loading history...
80
     */
81
    public ?array $search = null;
82
83
    /**
84
     * The query variables that should be set before traversals (for/joins).
85
     *
86
     * @var array<mixed>
87
     */
88
    public $preIterationVariables = [];
89
90
    /**
91
     * The query variables that should be set after traversals (for/joins).
92
     *
93
     * @var array<mixed>
94
     */
95
    public $postIterationVariables = [];
96
97
    /**
98
     * ID of the query
99
     * Used as prefix for automatically generated bindings.
100
     *
101
     * @var int
102
     */
103
    protected $queryId = 1;
104
105
    /**
106
     * @override
107
     * Create a new query builder instance.
108
     */
109 255
    public function __construct(
110
        IlluminateConnectionInterface $connection,
111
        IlluminateQueryGrammar        $grammar = null,
112
        IlluminateProcessor           $processor = null,
113
        AQB                           $aqb = null
114
    ) {
115
        assert($connection instanceof IlluminateConnectionInterface);
116
        assert($processor instanceof IlluminateProcessor);
117
118 255
        parent::__construct($connection, $grammar, $processor);
119
120 255
        if (!$aqb instanceof AQB) {
121 255
            $aqb = new AQB();
122
        }
123 255
        $this->aqb = $aqb;
124
125 255
        $this->queryId = spl_object_id($this);
126
    }
127
128 30
    public function getQueryId(): int
129
    {
130 30
        return $this->queryId;
131
    }
132
133
    /**
134
     * Get the current query value bindings in a flattened array.
135
     *
136
     * @return array<mixed>
137
     */
138 223
    public function getBindings()
139
    {
140 223
        $extractedBindings = [];
141 223
        foreach ($this->bindings as $typeBinds) {
142 223
            foreach ($typeBinds as $key => $value) {
143 142
                $extractedBindings[$key] = $value;
144
            }
145
        }
146
147 223
        return $extractedBindings;
148
    }
149
150
    /**
151
     * Run a pagination count query.
152
     *
153
     * @param array<mixed> $columns
154
     * @return array<mixed>
155
     */
156 3
    protected function runPaginationCountQuery($columns = ['*'])
157
    {
158 3
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
159
160 3
        $closeResults = $this->cloneWithout($without)
161 3
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
162 3
            ->setAggregate('count', $this->withoutSelectAliases($columns))
163 3
            ->get()->all();
164
165 3
        return $closeResults;
166
    }
167
168
    /**
169
     * Delete records from the database.
170
     *
171
     * @param mixed $id
172
     * @return int
173
     */
174 21
    public function delete($id = null)
175
    {
176
        // If an ID is passed to the method, we will set the where clause to check the
177
        // ID to let developers to simply and quickly remove a single row from this
178
        // database without manually specifying the "where" clauses on the query.
179 21
        if (!is_null($id)) {
180 1
            $this->where($this->from . '._key', '=', $id);
181
        }
182
183 21
        $this->applyBeforeQueryCallbacks();
184
185 21
        return $this->connection->delete(
186 21
            $this->grammar->compileDelete($this),
187 21
            $this->cleanBindings(
188 21
                $this->grammar->prepareBindingsForDelete($this->bindings)
189 21
            )
190 21
        );
191
    }
192
193
    /**
194
     * Determine if any rows exist for the current query.
195
     *
196
     * @return bool
197
     */
198 6
    public function exists()
199
    {
200 6
        $this->applyBeforeQueryCallbacks();
201
202 6
        $results = $this->connection->select(
203 6
            $this->grammar->compileExists($this),
204 6
            $this->getBindings(),
205 6
            !$this->useWritePdo
206 6
        );
207
208
        // If the results have rows, we will get the row and see if the exists column is a
209
        // boolean true. If there are no results for this query we will return false as
210
        // there are no rows for this query at all, and we can return that info here.
211 6
        if (isset($results[0])) {
212 6
            $results = (array) $results[0];
213
214 6
            return (bool) $results['exists'];
215
        }
216
217
        return false;
218
    }
219
220
221
    /**
222
     * Execute an aggregate function on the database.
223
     *
224
     * @param string $function
225
     * @param array<mixed> $columns
226
     * @return mixed
227
     */
228 27
    public function aggregate($function, $columns = ['*'])
229
    {
230 27
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
231 27
            ->setAggregate($function, $columns)
232 27
            ->get($columns);
233
234 27
        if (!$results->isEmpty()) {
235 27
            return ($results->first())->aggregate;
236
        }
237
238
        return false;
239
    }
240
241
    /**
242
     * Set a variable
243
     * @param string $variable
244
     * @param IlluminateQueryBuilder|Expression|array<mixed>|Int|Float|String|Boolean $value
245
     * @param string|VariablePosition $variablePosition
246
     * @return Builder
247
     */
248 7
    public function set(
249
        string $variable,
250
        IlluminateQueryBuilder|Expression|array|Boolean|Int|Float|String $value,
251
        VariablePosition|string $variablePosition = VariablePosition::preIterations
252
    ): Builder {
253 7
        if (is_string($variablePosition)) {
254 3
            $variablePosition = VariablePosition::tryFrom($variablePosition) ?? VariablePosition::preIterations;
255
        }
256 7
        if ($value instanceof Expression) {
0 ignored issues
show
introduced by
$value is never a sub-type of Illuminate\Database\Query\Expression.
Loading history...
257 5
            $this->{$variablePosition->value}[$variable] = $value->getValue($this->grammar);
258
259 5
            return $this;
260
        }
261
262 3
        if ($value instanceof Builder) {
0 ignored issues
show
introduced by
$value is never a sub-type of LaravelFreelancerNL\Aranguent\Query\Builder.
Loading history...
263
264 1
            [$subquery] = $this->createSub($value);
265
266 1
            $this->{$variablePosition->value}[$variable] = $subquery;
267
268 1
            return $this;
269
        }
270 2
        $this->{$variablePosition->value}[$variable] = $this->bindValue($value, $variablePosition->value);
271
272 2
        return $this;
273
    }
274
275 206
    public function isVariable(string $value): bool
276
    {
277
        if (
278 206
            key_exists($value, $this->preIterationVariables)
279 206
            ||  key_exists($value, $this->postIterationVariables)
280
        ) {
281 1
            return true;
282
        }
283
284 206
        return false;
285
    }
286
287
    /**
288
     * @param mixed $value
289
     * @param array<mixed> $variables
290
     * @return bool
291
     */
292 226
    public function isReference(mixed $value, array $variables = []): bool
293
    {
294 226
        if (!is_string($value) || empty($value)) {
295 79
            return false;
296
        }
297
298 223
        if (empty($variables)) {
299 223
            $variables = array_merge(
300 223
                array_keys($this->preIterationVariables),
301 223
                array_keys($this->postIterationVariables),
302 223
                $this->tableAliases,
303 223
            );
304
        }
305
306 223
        $variablesRegex = implode('|', $variables);
307
308 223
        return (bool) preg_match(
309 223
            '/^\`?('
310 223
            . $variablesRegex
311 223
            . '|CURRENT|NEW|OLD)\`?(\[\`.+\`\]|\[[\d\w\*]*\])*(\.(\`.+\`|@?[\d\w]*)(\[\`.+\`\]|\[[\d\w\*]*\])*)*$/',
312 223
            $value
313 223
        );
314
    }
315
316
317
    /**
318
     * Get the database connection instance.
319
     *
320
     * @return Connection
321
     */
322 104
    public function getConnection()
323
    {
324 104
        return $this->connection;
325
    }
326
327
    /**
328
     * Prepend the database name if the given query is on another database.
329
     *
330
     * @param mixed $query
331
     * @return mixed
332
     * @throws Exception
333
     */
334
    protected function prependDatabaseNameIfCrossDatabaseQuery($query)
335
    {
336
        if ($query->getConnection()->getDatabaseName() !==
337
            $this->getConnection()->getDatabaseName()) {
338
            $databaseName = $query->getConnection()->getDatabaseName();
339
340
            if (!str_starts_with($query->from, $databaseName) && !str_contains($query->from, '.')) {
341
                throw new Exception(message: 'ArangoDB does not support cross database queries.');
342
            }
343
        }
344
345
        return $query;
346
    }
347
348
    /**
349
     * Get the AQL representation of the query.
350
     */
351 3
    public function toAql(): string
352
    {
353 3
        return $this->toSql();
354
    }
355
}
356