QueryBuilder::getCommand()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\FluentAQL;
6
7
use LaravelFreelancerNL\FluentAQL\AQL\HasFunctions;
8
use LaravelFreelancerNL\FluentAQL\AQL\HasGraphClauses;
9
use LaravelFreelancerNL\FluentAQL\AQL\HasOperatorExpressions;
10
use LaravelFreelancerNL\FluentAQL\AQL\HasQueryClauses;
11
use LaravelFreelancerNL\FluentAQL\AQL\HasStatementClauses;
12
use LaravelFreelancerNL\FluentAQL\AQL\HasSupportCommands;
13
use LaravelFreelancerNL\FluentAQL\Clauses\Clause;
14
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException;
15
use LaravelFreelancerNL\FluentAQL\Expressions\BindExpression;
16
use LaravelFreelancerNL\FluentAQL\Expressions\Expression;
17
use LaravelFreelancerNL\FluentAQL\Expressions\ExpressionInterface;
18
use LaravelFreelancerNL\FluentAQL\Traits\CompilesPredicates;
19
use LaravelFreelancerNL\FluentAQL\Traits\NormalizesExpressions;
20
21
/**
22
 * Class QueryBuilder
23
 * Fluent ArangoDB AQL Query Builder.
24
 * Creates and compiles AQL queries. Returns all data necessary to run the query,
25
 * including bindings and a list of used read/write collections.
26
 */
27
class QueryBuilder
28
{
29
    use NormalizesExpressions;
30
    use CompilesPredicates;
0 ignored issues
show
Bug introduced by
The trait LaravelFreelancerNL\Flue...aits\CompilesPredicates requires the property $logicalOperator which is not provided by LaravelFreelancerNL\FluentAQL\QueryBuilder.
Loading history...
31
    use HasQueryClauses;
32
    use HasStatementClauses;
33
    use HasGraphClauses;
34
    use HasFunctions;
35
    use HasOperatorExpressions;
36
    use HasSupportCommands;
37
38
    /**
39
     * The database query grammar instance.
40
     *
41
     */
42
    public Grammar $grammar;
43
44
    /**
45
     * The AQL query.
46
     */
47
    public ?string $query = null;
48
49
    /**
50
     * Bindings for $query.
51
     *
52
     * @var array<mixed> $binds
53
     */
54
    public array $binds = [];
55
56
    /**
57
     * List of read/write/exclusive collections required for transactions.
58
     *
59
     * @var array<mixed> $collections
60
     */
61
    public array $collections = [];
62
63
    /**
64
     * List of Clauses to be compiled into a query.
65
     *
66
     * @var array<mixed> $commands
67
     */
68
    protected array $commands = [];
69
70
    /**
71
     * Registry of variable names used in this query.
72
     *
73
     * @var array<mixed> $variables
74
     */
75
    protected array $variables = [];
76
77
    /**
78
     * ID of the query
79
     * Used as prefix for automatically generated bindings.
80
     */
81
    protected int $queryId = 1;
82
83 29
    public function __construct()
84
    {
85 29
        $this->grammar = new Grammar();
86
87 29
        $this->queryId = spl_object_id($this);
88
    }
89
90
    /**
91
     * Add an AQL command (raw AQL and Clauses.
92
     *
93
     * @param Clause|Expression|QueryBuilder $command
94
     */
95 19
    public function addCommand($command): void
96
    {
97 19
        $this->commands[] = $command;
98
    }
99
100
    /**
101
     * Get the clause list.
102
     */
103 3
    public function getCommands(): mixed
104
    {
105 3
        return $this->commands;
106
    }
107
108
    /**
109
     * Get the last, or a specific, command.
110
     */
111 4
    public function getCommand(int $index = null): mixed
112
    {
113 4
        if ($index === null) {
114 1
            return end($this->commands);
115
        }
116
117 3
        return $this->commands[$index];
118
    }
119
120
    /**
121
     * Remove the last, or the specified, Command.
122
     */
123 3
    public function removeCommand(int $index = null): bool
124
    {
125 3
        if ($index === null) {
126 1
            return (bool)array_pop($this->commands);
127
        }
128 2
        if (isset($this->commands[$index])) {
129 1
            unset($this->commands[$index]);
130
131 1
            return true;
132
        }
133
134 1
        return false;
135
    }
136
137
    /**
138
     * @param mixed  $collections
139
     * @param string $mode
140
     *
141
     * @return QueryBuilder
142
     */
143 4
    public function registerCollections($collections, $mode = 'write'): self
144
    {
145 4
        if (!is_array($collections)) {
146 2
            $collections = [$collections];
147
        }
148
149 4
        $this->collections[$mode] = array_unique(array_merge($collections));
150
151 4
        return $this;
152
    }
153
154
    /**
155
     * Register variables on declaration for later data normalization.
156
     * @param string|array<mixed>|object $variableName
157
     */
158 11
    public function registerVariable(
159
        string|array|object $variableName
160
    ): self {
161 11
        if ($variableName instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$variableName is never a sub-type of LaravelFreelancerNL\Flue...ons\ExpressionInterface.
Loading history...
162 1
            $variableName = $variableName->compile($this);
163
        }
164 11
        if (is_string($variableName)) {
0 ignored issues
show
introduced by
The condition is_string($variableName) is always false.
Loading history...
165 11
            $variableName = [$variableName => $variableName];
166
        }
167 11
        if (is_array($variableName)) {
0 ignored issues
show
introduced by
The condition is_array($variableName) is always true.
Loading history...
168 11
            $this->variables = array_unique(array_merge($this->variables, $variableName));
169
        }
170
171 11
        return $this;
172
    }
173
174
    /**
175
     * Bind data to a variable.
176
     *
177
     * @param object|array<mixed>|string|int|float|bool|null $data
178
     * @throws BindException
179
     */
180 5
    public function bind(
181
        object|array|string|int|float|bool|null $data,
182
        string $to = null
183
    ): BindExpression {
184 5
        $this->validateBindVariable($to);
185
186 4
        $to = $this->generateBindVariable($to);
187
188 4
        $this->binds[$to] = $data;
189
190 4
        $to = $this->grammar->formatBind($to, false);
191
192 4
        return new BindExpression($to, $data);
193
    }
194
195
    /**
196
     * @param array<array-key, array<array-key, mixed>|object|scalar|null> $array
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, array<a...ed>|object|scalar|null> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, array<array-key, mixed>|object|scalar|null>.
Loading history...
197
     * @throws BindException
198
     */
199 6
    protected function bindArrayValues(array $array): void
200
    {
201 6
        foreach ($array as $key => $value) {
202 2
            $to = null;
203 2
            if (is_string($key)) {
204 2
                $to = $key;
205
            }
206 2
            $this->bind($value, $to);
207
        }
208
    }
209
210
    /**
211
     * Bind a collection name to a variable.
212
     *
213
     * @throws BindException
214
     */
215 1
    public function bindCollection(
216
        mixed $data,
217
        string $to = null
218
    ): BindExpression {
219 1
        $this->validateBindVariable($to);
220
221 1
        $to = $this->generateBindVariable($to);
222
223 1
        $this->binds[$to] = $data;
224
225 1
        $to = $this->grammar->formatBind($to, true);
226
227 1
        return new BindExpression($to);
228
    }
229
230
    /**
231
     * @throws BindException
232
     */
233 6
    protected function validateBindVariable(?string $to): void
234
    {
235 6
        if (isset($to) && !$this->grammar->isBindParameter($to)) {
236 1
            throw new BindException('Invalid bind parameter.');
237
        }
238
    }
239
240 5
    protected function generateBindVariable(?string $to): string
241
    {
242 5
        if ($to == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $to of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
243 3
            $to = $this->queryId . '_' . (count($this->binds) + 1);
244
        }
245 5
        return $to;
246
    }
247
248
249
    /**
250
     * Compile the query with its bindings and collection list.
251
     */
252 14
    public function compile(): self
253
    {
254 14
        $this->query = '';
255
        /** @var Expression|Clause @command */
256 14
        foreach ($this->commands as $command) {
257 13
            $this->query .= ' ' . $command->compile($this);
258
        }
259 13
        $this->query = trim($this->query);
260
261 13
        return $this;
262
    }
263
264 14
    public function get(): static
265
    {
266 14
        $this->compile();
267
268 13
        return $this;
269
    }
270
271 4
    public function getQueryId(): int
272
    {
273 4
        return $this->queryId;
274
    }
275
276 6
    public function toAql(): string
277
    {
278 6
        return $this->get()->query ?: "";
279
    }
280
281 5
    public function __toString()
282
    {
283 5
        return $this->toAql();
284
    }
285
286
    /**
287
     * @return array<mixed>
288
     */
289 7
    public function getVariables(): array
290
    {
291 7
        return $this->variables;
292
    }
293
294
    /**
295
     * @param array<mixed> $arguments
296
     * @return array<mixed>
297
     */
298 1
    public function unsetNullValues(array $arguments): array
299
    {
300 1
        return array_filter(
301
            $arguments,
302 1
            function ($value) {
303 1
                return !is_null($value);
304
            }
305
        );
306
    }
307
}
308