Passed
Push — next ( ed4ce4...be2ec6 )
by Bas
12:50
created

Builder::forSubQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 1
cts 1
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Query;
4
5
use Closure;
6
use Illuminate\Database\ConnectionInterface;
7
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
8
use Illuminate\Database\Query\Expression;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Collection;
11
use InvalidArgumentException;
12
use LaravelFreelancerNL\Aranguent\Connection;
13
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsJoinClauses;
14
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSubqueries;
15
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsWhereClauses;
16
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException;
17
use LaravelFreelancerNL\FluentAQL\Expressions\ExpressionInterface as ExpressionInterface;
18
use LaravelFreelancerNL\FluentAQL\Expressions\FunctionExpression;
19
use LaravelFreelancerNL\FluentAQL\QueryBuilder;
20
21
class Builder extends IlluminateQueryBuilder
22
{
23
    use BuildsJoinClauses;
24
    use BuildsSubqueries;
25
    use BuildsWhereClauses;
26
27
    /**
28
     * @var QueryBuilder|FunctionExpression
29
     */
30
    public QueryBuilder|FunctionExpression $aqb;
31
32
    /**
33
     * @var Connection
34
     */
35
    public $connection;
36
37
    /**
38
     * @var Grammar
39
     */
40
    public $grammar;
41
42
    /**
43
     * The current query value bindings.
44
     *
45
     * @var null|array{predicates: array<mixed>, options: array<string, string>}
46
     */
47
    public ?array $search = null;
48
49
    /**
50
     * The query variables that should be set.
51
     *
52
     * @var array<mixed>
53
     */
54
    public $variables = [];
55
56
    /**
57
     * @override
58
     * Create a new query builder instance.
59
     */
60
    public function __construct(
61
        Connection $connection,
62
        Grammar $grammar = null,
63
        Processor $processor = null,
64
        QueryBuilder $aqb = null
65
    ) {
66 162
        $this->connection = $connection;
67
        $this->grammar = $grammar ?: $connection->getQueryGrammar();
0 ignored issues
show
Documentation Bug introduced by
It seems like $grammar ?: $connection->getQueryGrammar() can also be of type Illuminate\Database\Query\Grammars\Grammar. However, the property $grammar is declared as type LaravelFreelancerNL\Aranguent\Query\Grammar. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
68
        $this->processor = $processor ?: $connection->getPostProcessor();
69
        if (!$aqb instanceof QueryBuilder) {
70
            $aqb = new QueryBuilder();
71
        }
72 162
        $this->aqb = $aqb;
73 162
    }
74 162
75 162
    /**
76 162
     * Delete a record from the database.
77
     *
78 162
     * @param mixed $id
79 162
     *
80
     * @return int
81
     */
82
    public function delete($id = null)
83
    {
84
        $this->aqb = new QueryBuilder();
85
        $this->grammar->compileDelete($this, $id)->setAql();
86
        return $this->connection->delete($this->aqb);
87
    }
88 12
89
    /**
90 12
     * Set the table which the query is targeting.
91 12
     *
92 12
     * @param  \Closure|IlluminateQueryBuilder|string  $table
93
     * @param  string|null  $as
94
     * @return IlluminateQueryBuilder
95
     */
96
    public function from($table, $as = null)
97
    {
98
        if ($this->isQueryable($table)) {
99
            return $this->fromSub($table, $as);
100
        }
101
        $this->grammar->registerTableAlias($table, $as);
0 ignored issues
show
Bug introduced by
It seems like $table can also be of type Closure and Illuminate\Database\Query\Builder; however, parameter $table of LaravelFreelancerNL\Aran...r::registerTableAlias() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

101
        $this->grammar->registerTableAlias(/** @scrutinizer ignore-type */ $table, $as);
Loading history...
102 162
103
        $this->from = $table;
0 ignored issues
show
Documentation Bug introduced by
It seems like $table can also be of type Closure or Illuminate\Database\Query\Builder. However, the property $from is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
104 162
105
        return $this;
106
    }
107 162
108
    /**
109 162
     * Run the query as a "select" statement against the connection.
110
     *
111 162
     * @return array
112
     */
113
    protected function runSelect()
114
    {
115
        $this->aqb = new QueryBuilder();
116
        $this->grammar->compileSelect($this)->setAql();
117
        $results = $this->connection->select($this->aqb);
118
        $this->aqb = new QueryBuilder();
119 112
        return $results;
120
    }
121 112
122 112
    /**
123 112
     * Run a pagination count query.
124 111
     *
125 111
     * @param array $columns
126
     *
127
     * @return array
128
     */
129
    protected function runPaginationCountQuery($columns = ['*'])
130
    {
131
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
132
133
        $closeResults = $this->cloneWithout($without)
134
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
135 3
            ->setAggregate('count', $this->withoutSelectAliases($columns))
136
            ->get()->all();
137 3
138
        return $closeResults;
139 3
    }
140 3
141 3
    /**
142 3
     * Set the columns to be selected.
143
     *
144 3
     * @param  array|mixed  $columns
145
     * @return IlluminateQueryBuilder
146
     */
147
    public function select($columns = ['*'])
148
    {
149
        $this->columns = [];
150
        $this->bindings['select'] = [];
151
        $columns = is_array($columns) ? $columns : func_get_args();
152
153 53
        $this->addColumns($columns);
154
155 53
        return $this;
156 53
    }
157 53
158
    /**
159 53
     * Add a new select column to the query.
160
     *
161 53
     * @param  array|mixed  $column
162
     * @return $this
163
     */
164
    public function addSelect($column)
165
    {
166
        $columns = is_array($column) ? $column : func_get_args();
167
168
        $this->addColumns($columns);
169
170 11
        return $this;
171
    }
172 11
173
    /**
174 11
     * @param array<mixed> $columns
175
     */
176 11
    protected function addColumns(array $columns): void
177
    {
178
        foreach ($columns as $as => $column) {
179
            if (is_string($as) && $this->isQueryable($column)) {
180
                if (is_null($this->columns)) {
181
                    $this->select($this->from . '.*');
182 64
                }
183
184 64
                $this->selectSub($column, $as);
185 64
186
                continue;
187
            }
188
189
            if (! is_string($as) || ! $this->isQueryable($column)) {
190
                $this->columns[$as] = $column;
191
192
                continue;
193
            }
194
195 64
            $this->columns[] = $column;
196 64
        }
197
    }
198 64
199
    /**
200
     * Get the SQL representation of the query.
201
     *
202
     * @return string
203 64
     */
204
    public function toSql()
205
    {
206
        $this->grammar->compileSelect($this)->setAql();
207
        return $this->aqb->query;
0 ignored issues
show
Bug introduced by
The property query does not seem to exist on LaravelFreelancerNL\Flue...ions\FunctionExpression.
Loading history...
208
    }
209
210 40
    /**
211
     * Insert a new record into the database.
212 40
     *
213 40
     * @param array $values
214
     *
215
     * @throws BindException
216
     *
217
     * @return bool
218
     */
219
    public function insert(array $values): bool
220
    {
221
        $this->grammar->compileInsert($this, $values)->setAql();
222
        $results = $this->getConnection()->insert($this->aqb);
0 ignored issues
show
Bug introduced by
It seems like $this->aqb can also be of type LaravelFreelancerNL\Flue...ions\FunctionExpression; however, parameter $query of Illuminate\Database\Connection::insert() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

222
        $results = $this->getConnection()->insert(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
223
        $this->aqb = new QueryBuilder();
224
        return $results;
225 44
    }
226
227 44
    /**
228 44
     * Insert a new record and get the value of the primary key.
229 44
     *
230 44
     * @param array<mixed> $values
231
     */
232
    public function insertGetId(array $values, $sequence = null)
233
    {
234
        $this->grammar->compileInsertGetId($this, $values, $sequence)->setAql();
235
        $response = $this->getConnection()->execute($this->aqb);
0 ignored issues
show
Bug introduced by
It seems like $this->aqb can also be of type LaravelFreelancerNL\Flue...ions\FunctionExpression; however, parameter $query of LaravelFreelancerNL\Aran...t\Connection::execute() does only seem to accept LaravelFreelancerNL\FluentAQL\QueryBuilder|string, maybe add an additional type check? ( Ignorable by Annotation )

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

235
        $response = $this->getConnection()->execute(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
236
        $this->aqb = new QueryBuilder();
237
238 10
        return (is_array($response)) ? end($response) : $response;
239
    }
240 10
241 10
    /**
242 10
     * Insert a new record into the database.
243
     *
244 10
     * @param array $values
245
     *
246
     * @throws BindException
247
     *
248
     * @return bool
249
     */
250
    public function insertOrIgnore(array $values): bool
251
    {
252
        $this->grammar->compileInsertOrIgnore($this, $values)->setAql();
253
        $results = $this->getConnection()->insert($this->aqb);
0 ignored issues
show
Bug introduced by
It seems like $this->aqb can also be of type LaravelFreelancerNL\Flue...ions\FunctionExpression; however, parameter $query of Illuminate\Database\Connection::insert() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

253
        $results = $this->getConnection()->insert(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
254
        $this->aqb = new QueryBuilder();
255
256 98
        return $results;
257
    }
258 98
259 98
    /**
260 98
    /**
261
     * Get the current query value bindings in a flattened array.
262 98
     *
263
     * @return array
264
     */
265
    public function getBindings()
266
    {
267
        return $this->aqb->binds;
0 ignored issues
show
Bug introduced by
The property binds does not seem to exist on LaravelFreelancerNL\Flue...ions\FunctionExpression.
Loading history...
268
    }
269
270
    protected function setAql()
271 10
    {
272
        $this->aqb = $this->aqb->get();
0 ignored issues
show
Bug introduced by
The method get() does not exist on LaravelFreelancerNL\Flue...ions\FunctionExpression. ( Ignorable by Annotation )

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

272
        /** @scrutinizer ignore-call */ 
273
        $this->aqb = $this->aqb->get();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
273 10
274
        return $this;
275
    }
276 162
277
    /**
278 162
     * Update a record in the database.
279
     *
280 162
     * @param array $values
281
     *
282
     * @return int
283
     */
284
    public function update(array $values)
285
    {
286
        $this->aqb = new QueryBuilder();
287
288
        $this->grammar->compileUpdate($this, $values)->setAql();
289
        $results = $this->connection->update($this->aqb);
290 23
        $this->aqb = new QueryBuilder();
291
        return $results;
292 23
    }
293
294 23
    /**
295 23
     * Execute an aggregate function on the database.
296 23
     *
297 23
     * @param string $function
298
     * @param array  $columns
299
     *
300
     * @return mixed
301
     */
302
    public function aggregate($function, $columns = ['*'])
303
    {
304
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
305
            ->setAggregate($function, $columns)
306
            ->get($columns);
307
308
        $this->aqb = new QueryBuilder();
309
310 21
        if (!$results->isEmpty()) {
311
            return array_change_key_case((array) $results[0])['aggregate'];
312 21
        }
313 21
314 21
        return false;
315
    }
316 21
317
318 21
    /**
319 21
     * Determine if the given operator is supported.
320
     *
321
     * @param string $operator
322
     *
323
     * @return bool
324
     */
325
    protected function invalidOperator($operator)
326
    {
327
        return !in_array(strtolower($operator), $this->operators, true) &&
328
            !isset($this->grammar->getOperators()[strtoupper($operator)]);
329
    }
330
331
332
    /**
333 88
     * Add an "order by" clause to the query.
334
     *
335 88
     * @param Closure|IlluminateQueryBuilder|string $column
336 88
     * @param string                                $direction
337
     *
338
     * @throws InvalidArgumentException
339
     *
340
     * @return $this
341
     */
342
    public function orderBy($column, $direction = 'asc')
343
    {
344
        if ($this->isQueryable($column)) {
345
            [$query, $bindings] = $this->createSub($column);
346
347
            //fixme: Remove binding when implementing subqueries
348
            $bindings = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
349
350 2
            $column = new Expression('(' . $query . ')');
351
        }
352 2
353
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
354
            'column'    => $column,
355
            'direction' => $direction,
356
        ];
357
358
        return $this;
359
    }
360
361 2
    /**
362 2
     * Add a raw "order by" clause to the query.
363 2
     *
364
     * @param string|ExpressionInterface $aql
365
     * @param array                      $bindings
366 2
     *
367
     * @return $this
368
     */
369
    public function orderByRaw($aql, $bindings = [])
370
    {
371
        $bindings = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
372
373
        $type = 'Raw';
374
        $column = $aql;
375
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
376
377 1
        return $this;
378
    }
379 1
380
381 1
    /**
382 1
     * Put the query's results in random order.
383 1
     *
384
     * @param string $seed
385 1
     *
386
     * @return $this
387
     */
388
    public function inRandomOrder($seed = '')
389
    {
390
        // ArangoDB's random function doesn't accept a seed.
391
        $seed = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $seed is dead and can be removed.
Loading history...
392
393
        return $this->orderByRaw($this->grammar->compileRandom($this));
394
    }
395
396 1
    /**
397
     * Search an ArangoSearch view.
398
     *
399 1
     * @param mixed $predicates
400
     * @param array|null $options
401 1
     * @return Builder
402
     */
403
    public function search(mixed $predicates, array $options = null): Builder
404
    {
405
        if ($predicates instanceof Closure) {
406
            $predicates = $predicates($this->aqb);
407
        }
408
409
        if (! is_array($predicates)) {
410
            $predicates = [$predicates];
411 7
        }
412
413 7
        $this->search = [
414 2
            'predicates' => $predicates,
415
            'options' => $options
416
        ];
417 7
418 4
        return $this;
419
    }
420
421 7
    /**
422 7
     * Create a new query instance for a sub-query.
423 7
     *
424
     * @return $this
425
     */
426 7
    protected function forSubQuery()
427
    {
428
        return $this->newQuery();
429
    }
430
431
    /**
432
     * Get the database connection instance.
433
     *
434
     * @return Connection
435
     */
436
    public function getConnection()
437
    {
438
        return $this->connection;
439
    }
440
}
441