Passed
Branch next (ee2197)
by Bas
02:37
created

QueryBuilder::validateBindVariable()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 2
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 3
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 $collections = [];
62
63
    /**
64
     * List of Clauses to be compiled into a query.
65
     *
66
     * @var array<mixed> $commands
67
     */
68
    protected $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
     * @var int
82
     */
83
    protected $queryId = 1;
84
85 22
    public function __construct()
86
    {
87 22
        $this->grammar = new Grammar();
88
89 22
        $this->queryId = spl_object_id($this);
90 22
    }
91
92
    /**
93
     * Add an AQL command (raw AQL and Clauses.
94
     *
95
     * @param Clause|Expression|QueryBuilder $command
96
     *
97
     * @return void
98
     */
99 13
    public function addCommand($command)
100
    {
101 13
        $this->commands[] = $command;
102 13
    }
103
104
    /**
105
     * Get the clause list.
106
     *
107
     * @return mixed
108
     */
109 3
    public function getCommands()
110
    {
111 3
        return $this->commands;
112
    }
113
114
    /**
115
     * Get the last, or a specific, command.
116
     *
117
     * @param int|null $index
118
     *
119
     * @return mixed
120
     */
121 4
    public function getCommand(int $index = null)
122
    {
123 4
        if ($index === null) {
124 1
            return end($this->commands);
125
        }
126
127 3
        return $this->commands[$index];
128
    }
129
130
    /**
131
     * Remove the last, or the specified, Command.
132
     *
133
     * @param numeric|null $index
0 ignored issues
show
Bug introduced by
The type LaravelFreelancerNL\FluentAQL\numeric was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
134
     * @return bool
135
     */
136 3
    public function removeCommand($index = null): bool
137
    {
138 3
        if ($index === null) {
139 1
            return (array_pop($this->commands)) ? true : false;
140
        }
141 2
        if (isset($this->commands[$index])) {
142 1
            unset($this->commands[$index]);
143
144 1
            return true;
145
        }
146
147 1
        return false;
148
    }
149
150
    /**
151
     * @param mixed  $collections
152
     * @param string $mode
153
     *
154
     * @return QueryBuilder
155
     */
156 2
    public function registerCollections($collections, $mode = 'write'): self
157
    {
158 2
        if (!is_array($collections)) {
159 2
            $collections = [$collections];
160
        }
161
162 2
        $this->collections[$mode] = array_unique(array_merge($collections));
163
164 2
        return $this;
165
    }
166
167
    /**
168
     * Register variables on declaration for later data normalization.
169
     * @param string|array<mixed>|object $variableName
170
     */
171 8
    public function registerVariable(
172
        string|array|object $variableName
173
    ): self {
174 8
        if ($variableName instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$variableName is never a sub-type of LaravelFreelancerNL\Flue...ons\ExpressionInterface.
Loading history...
175 1
            $variableName = $variableName->compile($this);
176
        }
177 8
        if (is_string($variableName)) {
0 ignored issues
show
introduced by
The condition is_string($variableName) is always false.
Loading history...
178 8
            $variableName = [$variableName => $variableName];
179
        }
180
181 8
        if (is_array($variableName)) {
0 ignored issues
show
introduced by
The condition is_array($variableName) is always true.
Loading history...
182 8
            $this->variables = array_unique(array_merge($this->variables, $variableName));
183
        }
184
185 8
        return $this;
186
    }
187
188
    /**
189
     * Bind data to a variable.
190
     *
191
     * @throws BindException
192
     */
193 3
    public function bind(
194
        mixed $data,
195
        string $to = null
196
    ): BindExpression {
197 3
        $this->validateBindVariable($to);
198
199 2
        $to = $this->generateBindVariable($to);
200
201 2
        $this->binds[$to] = $data;
202
203 2
        $to = $this->grammar->formatBind($to, false);
204
205 2
        return new BindExpression($to);
206
    }
207
208
    /**
209
     * Bind a collection name to a variable.
210
     *
211
     * @throws BindException
212
     */
213 1
    public function bindCollection(
214
        mixed $data,
215
        string $to = null
216
    ): BindExpression {
217 1
        $this->validateBindVariable($to);
218
219 1
        $to = $this->generateBindVariable($to);
220
221 1
        $this->binds[$to] = $data;
222
223 1
        $to = $this->grammar->formatBind($to, true);
224
225 1
        return new BindExpression($to);
226
    }
227
228 4
    protected function validateBindVariable(?string $to): void
229
    {
230 4
        if (isset($to) && !$this->grammar->isBindParameter($to)) {
231 1
            throw new BindException('Invalid bind parameter.');
232
        }
233 3
    }
234
235
    /**
236
     * @param null|string $to
237
     *
238
     * @return string
239
     */
240 3
    protected function generateBindVariable(?string $to): string
241
    {
242 3
        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 3
        return $to;
246
    }
247
248
249
    /**
250
     * Compile the query with its bindings and collection list.
251
     */
252 8
    public function compile(): self
253
    {
254 8
        $this->query = '';
255 8
        foreach ($this->commands as $command) {
256 7
            $this->query .= ' ' . $command->compile($this);
257
        }
258 7
        $this->query = trim($this->query);
259
260 7
        return $this;
261
    }
262
263
    /**
264
     * @return QueryBuilder $this
265
     */
266 8
    public function get()
267
    {
268 8
        $this->compile();
269
270 7
        return $this;
271
    }
272
273
    /**
274
     * @return int $this
275
     */
276 4
    public function getQueryId(): int
277
    {
278 4
        return $this->queryId;
279
    }
280
281
    /**
282
     * @return string
283
     */
284 6
    public function toAql()
285
    {
286 6
        return $this->get()->query ?: "";
287
    }
288
289 5
    public function __toString()
290
    {
291 5
        return $this->toAql();
292
    }
293
294
    /**
295
     * @return array<mixed>
296
     */
297 7
    public function getVariables(): array
298
    {
299 7
        return $this->variables;
300
    }
301
302
    /**
303
     * @param array<mixed> $arguments
304
     * @return array<mixed>
305
     */
306 1
    public function unsetNullValues(array $arguments): array
307
    {
308 1
        return array_filter(
309 1
            $arguments,
310 1
            function ($value) {
311 1
                return !is_null($value);
312 1
            }
313
        );
314
    }
315
}
316