Passed
Push — refactor/improve-static-analys... ( ed4ce4...8da3ef )
by Bas
05:52 queued 02:21
created

Builder::runPaginationCountQuery()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 10
cc 3
nc 2
nop 1
crap 3
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 162
    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 162
        $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 162
        $this->processor = $processor ?: $connection->getPostProcessor();
69 162
        if (!$aqb instanceof QueryBuilder) {
70 162
            $aqb = new QueryBuilder();
71
        }
72 162
        $this->aqb = $aqb;
73 162
    }
74
75
    /**
76
     * Delete a record from the database.
77
     *
78
     * @param mixed $id
79
     *
80
     * @return int
81
     */
82 12
    public function delete($id = null)
83
    {
84 12
        $this->aqb = new QueryBuilder();
85 12
        $this->grammar->compileDelete($this, $id)->setAql();
86 12
        return $this->connection->delete($this->aqb);
87
    }
88
89
    /**
90
     * Set the table which the query is targeting.
91
     *
92
     * @param  \Closure|IlluminateQueryBuilder|string  $table
93
     * @param  string|null  $as
94
     * @return IlluminateQueryBuilder
95
     */
96 162
    public function from($table, $as = null)
97
    {
98 162
        if ($this->isQueryable($table)) {
99
            return $this->fromSub($table, $as);
100
        }
101 162
        $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
103 162
        $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
105 162
        return $this;
106
    }
107
108
    /**
109
     * Run the query as a "select" statement against the connection.
110
     *
111
     * @return array
112
     */
113 112
    protected function runSelect()
114
    {
115 112
        $this->aqb = new QueryBuilder();
116 112
        $this->grammar->compileSelect($this)->setAql();
117 112
        $results = $this->connection->select($this->aqb);
118 111
        $this->aqb = new QueryBuilder();
119 111
        return $results;
120
    }
121
122
    /**
123
     * Run a pagination count query.
124
     *
125
     * @param array $columns
126
     *
127
     * @return array
128
     */
129 3
    protected function runPaginationCountQuery($columns = ['*'])
130
    {
131 3
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
132
133 3
        $closeResults = $this->cloneWithout($without)
134 3
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
135 3
            ->setAggregate('count', $this->withoutSelectAliases($columns))
136 3
            ->get()->all();
137
138 3
        return $closeResults;
139
    }
140
141
    /**
142
     * Set the columns to be selected.
143
     *
144
     * @param  array|mixed  $columns
145
     * @return IlluminateQueryBuilder
146
     */
147 53
    public function select($columns = ['*'])
148
    {
149 53
        $this->columns = [];
150 53
        $this->bindings['select'] = [];
151 53
        $columns = is_array($columns) ? $columns : func_get_args();
152
153 53
        $this->addColumns($columns);
154
155 53
        return $this;
156
    }
157
158
    /**
159
     * Add a new select column to the query.
160
     *
161
     * @param  array|mixed  $column
162
     * @return $this
163
     */
164 11
    public function addSelect($column)
165
    {
166 11
        $columns = is_array($column) ? $column : func_get_args();
167
168 11
        $this->addColumns($columns);
169
170 11
        return $this;
171
    }
172
173
    /**
174
     * @param array<mixed> $columns
175
     */
176 64
    protected function addColumns(array $columns): void
177
    {
178 64
        foreach ($columns as $as => $column) {
179 64
            if (is_string($as) && $this->isQueryable($column)) {
180
                if (is_null($this->columns)) {
181
                    $this->select($this->from . '.*');
182
                }
183
184
                $this->selectSub($column, $as);
185
186
                continue;
187
            }
188
189 64
            if (! is_string($as) || ! $this->isQueryable($column)) {
190 64
                $this->columns[$as] = $column;
191
192 64
                continue;
193
            }
194
195
            $this->columns[] = $column;
196
        }
197 64
    }
198
199
    /**
200
     * Get the SQL representation of the query.
201
     *
202
     * @return string
203
     */
204 40
    public function toSql()
205
    {
206 40
        $this->grammar->compileSelect($this)->setAql();
207 40
        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
    /**
211
     * Insert a new record into the database.
212
     *
213
     * @param array $values
214
     *
215
     * @throws BindException
216
     *
217
     * @return bool
218
     */
219 44
    public function insert(array $values): bool
220
    {
221 44
        $this->grammar->compileInsert($this, $values)->setAql();
222 44
        $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 44
        $this->aqb = new QueryBuilder();
224 44
        return $results;
225
    }
226
227
    /**
228
     * Insert a new record and get the value of the primary key.
229
     *
230
     * @param array<mixed> $values
231
     */
232 10
    public function insertGetId(array $values, $sequence = null)
233
    {
234 10
        $this->grammar->compileInsertGetId($this, $values, $sequence)->setAql();
235 10
        $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 10
        $this->aqb = new QueryBuilder();
237
238 10
        return (is_array($response)) ? end($response) : $response;
239
    }
240
241
    /**
242
     * Insert a new record into the database.
243
     *
244
     * @param array $values
245
     *
246
     * @throws BindException
247
     *
248
     * @return bool
249
     */
250 98
    public function insertOrIgnore(array $values): bool
251
    {
252 98
        $this->grammar->compileInsertOrIgnore($this, $values)->setAql();
253 98
        $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 98
        $this->aqb = new QueryBuilder();
255
256 98
        return $results;
257
    }
258
259
    /**
260
    /**
261
     * Get the current query value bindings in a flattened array.
262
     *
263
     * @return array
264
     */
265 10
    public function getBindings()
266
    {
267 10
        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 162
    protected function setAql()
271
    {
272 162
        $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
274 162
        return $this;
275
    }
276
277
    /**
278
     * Update a record in the database.
279
     *
280
     * @param array $values
281
     *
282
     * @return int
283
     */
284 23
    public function update(array $values)
285
    {
286 23
        $this->aqb = new QueryBuilder();
287
288 23
        $this->grammar->compileUpdate($this, $values)->setAql();
289 23
        $results = $this->connection->update($this->aqb);
290 23
        $this->aqb = new QueryBuilder();
291 23
        return $results;
292
    }
293
294
    /**
295
     * Execute an aggregate function on the database.
296
     *
297
     * @param string $function
298
     * @param array  $columns
299
     *
300
     * @return mixed
301
     */
302 21
    public function aggregate($function, $columns = ['*'])
303
    {
304 21
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
305 21
            ->setAggregate($function, $columns)
306 21
            ->get($columns);
307
308 21
        $this->aqb = new QueryBuilder();
309
310 21
        if (!$results->isEmpty()) {
311 21
            return array_change_key_case((array) $results[0])['aggregate'];
312
        }
313
314
        return false;
315
    }
316
317
318
    /**
319
     * Determine if the given operator is supported.
320
     *
321
     * @param string $operator
322
     *
323
     * @return bool
324
     */
325 88
    protected function invalidOperator($operator)
326
    {
327 88
        return !in_array(strtolower($operator), $this->operators, true) &&
328 88
            !isset($this->grammar->getOperators()[strtoupper($operator)]);
329
    }
330
331
332
    /**
333
     * Add an "order by" clause to the query.
334
     *
335
     * @param Closure|IlluminateQueryBuilder|string $column
336
     * @param string                                $direction
337
     *
338
     * @throws InvalidArgumentException
339
     *
340
     * @return $this
341
     */
342 2
    public function orderBy($column, $direction = 'asc')
343
    {
344 2
        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
            $column = new Expression('(' . $query . ')');
351
        }
352
353 2
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
354 2
            'column'    => $column,
355 2
            'direction' => $direction,
356
        ];
357
358 2
        return $this;
359
    }
360
361
    /**
362
     * Add a raw "order by" clause to the query.
363
     *
364
     * @param string|ExpressionInterface $aql
365
     * @param array                      $bindings
366
     *
367
     * @return $this
368
     */
369 1
    public function orderByRaw($aql, $bindings = [])
370
    {
371 1
        $bindings = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
372
373 1
        $type = 'Raw';
374 1
        $column = $aql;
375 1
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
376
377 1
        return $this;
378
    }
379
380
381
    /**
382
     * Put the query's results in random order.
383
     *
384
     * @param string $seed
385
     *
386
     * @return $this
387
     */
388 1
    public function inRandomOrder($seed = '')
389
    {
390
        // ArangoDB's random function doesn't accept a seed.
391 1
        $seed = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $seed is dead and can be removed.
Loading history...
392
393 1
        return $this->orderByRaw($this->grammar->compileRandom($this));
394
    }
395
396
    /**
397
     * Search an ArangoSearch view.
398
     *
399
     * @param mixed $predicates
400
     * @param array|null $options
401
     * @return Builder
402
     */
403 7
    public function search(mixed $predicates, array $options = null): Builder
404
    {
405 7
        if ($predicates instanceof Closure) {
406 2
            $predicates = $predicates($this->aqb);
407
        }
408
409 7
        if (! is_array($predicates)) {
410 4
            $predicates = [$predicates];
411
        }
412
413 7
        $this->search = [
414 7
            'predicates' => $predicates,
415 7
            'options' => $options
416
        ];
417
418 7
        return $this;
419
    }
420
421
    /**
422
     * Create a new query instance for a sub-query.
423
     *
424
     * @return $this
425
     */
426 6
    protected function forSubQuery()
427
    {
428 6
        return $this->newQuery();
429
    }
430
431
    /**
432
     * Get the database connection instance.
433
     *
434
     * @return Connection
435
     */
436 137
    public function getConnection()
437
    {
438 137
        return $this->connection;
439
    }
440
}
441