Passed
Push — next ( 480502...54a2e9 )
by Bas
12:53
created

Builder::search()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 17
ccs 9
cts 9
cp 1
rs 10
cc 3
nc 4
nop 2
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 Illuminate\Support\Facades\DB;
12
use InvalidArgumentException;
13
use LaravelFreelancerNL\Aranguent\Connection;
14
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsJoinClauses;
15
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsSubqueries;
16
use LaravelFreelancerNL\Aranguent\Query\Concerns\BuildsWhereClauses;
17
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException;
18
use LaravelFreelancerNL\FluentAQL\Expressions\ExpressionInterface as ExpressionInterface;
19
use LaravelFreelancerNL\FluentAQL\Expressions\FunctionExpression;
20
use LaravelFreelancerNL\FluentAQL\QueryBuilder;
21
22
class Builder extends IlluminateQueryBuilder
23
{
24
    use BuildsJoinClauses;
25
    use BuildsSubqueries;
26
    use BuildsWhereClauses;
27
28
    /**
29
     * @var QueryBuilder|FunctionExpression
30
     */
31
    public QueryBuilder|FunctionExpression $aqb;
32
33
    /**
34
     * @var Connection
35
     */
36
    public $connection;
37
38
    /**
39
     * @var Grammar
40
     */
41
    public $grammar;
42
43
    /**
44
     * The current query value bindings.
45
     *
46
     * @var null|array{predicates: array<mixed>, options: array<string, string>}
47
     */
48
    public ?array $search = null;
49
50
    /**
51
     * The query variables that should be set.
52
     *
53
     * @var array<mixed>
54
     */
55
    public $variables = [];
56
57
    /**
58
     * @override
59
     * Create a new query builder instance.
60
     *
61
     * @param  ConnectionInterface  $connection
62
     * @param  Grammar|null  $grammar
63
     * @param  Processor|null  $processor
64
     * @param  QueryBuilder|null  $aqb
65
     */
66 152
    public function __construct(
67
        ConnectionInterface $connection,
68
        Grammar $grammar = null,
69
        Processor $processor = null,
70
        QueryBuilder $aqb = null
71
    ) {
72 152
        $this->connection = $connection;
0 ignored issues
show
Documentation Bug introduced by
$connection is of type Illuminate\Database\ConnectionInterface, but the property $connection was declared to be of type LaravelFreelancerNL\Aranguent\Connection. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
73 152
        $this->grammar = $grammar ?: $connection->getQueryGrammar();
74 152
        $this->processor = $processor ?: $connection->getPostProcessor();
75 152
        if (!$aqb instanceof QueryBuilder) {
76 152
            $aqb = new QueryBuilder();
77
        }
78 152
        $this->aqb = $aqb;
79 152
    }
80
81
    /**
82
     * Delete a record from the database.
83
     *
84
     * @param mixed $id
85
     *
86
     * @return int
87
     */
88 12
    public function delete($id = null)
89
    {
90 12
        $this->aqb = new QueryBuilder();
91 12
        $this->grammar->compileDelete($this, $id)->setAql();
92 12
        return $this->connection->delete($this->aqb);
93
    }
94
95
    /**
96
     * Set the table which the query is targeting.
97
     *
98
     * @param  \Closure|IlluminateQueryBuilder|string  $table
99
     * @param  string|null  $as
100
     * @return IlluminateQueryBuilder
101
     */
102 152
    public function from($table, $as = null)
103
    {
104 152
        if ($this->isQueryable($table)) {
105
            return $this->fromSub($table, $as);
106
        }
107 152
        $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

107
        $this->grammar->registerTableAlias(/** @scrutinizer ignore-type */ $table, $as);
Loading history...
108
109 152
        $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...
110
111 152
        return $this;
112
    }
113
114
    /**
115
     * Run the query as a "select" statement against the connection.
116
     *
117
     * @return array
118
     */
119 103
    protected function runSelect()
120
    {
121 103
        $this->aqb = new QueryBuilder();
122 103
        $this->grammar->compileSelect($this)->setAql();
123 103
        $results = $this->connection->select($this->aqb);
124 102
        $this->aqb = new QueryBuilder();
125 102
        return $results;
126
    }
127
128
    /**
129
     * Run a pagination count query.
130
     *
131
     * @param array $columns
132
     *
133
     * @return array
134
     */
135 3
    protected function runPaginationCountQuery($columns = ['*'])
136
    {
137 3
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
138
139 3
        $closeResults = $this->cloneWithout($without)
140 3
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
141 3
            ->setAggregate('count', $this->withoutSelectAliases($columns))
142 3
            ->get()->all();
143
144 3
        return $closeResults;
145
    }
146
147
    /**
148
     * Set the columns to be selected.
149
     *
150
     * @param  array|mixed  $columns
151
     * @return IlluminateQueryBuilder
152
     */
153 48
    public function select($columns = ['*'])
154
    {
155 48
        $this->columns = [];
156 48
        $this->bindings['select'] = [];
157 48
        $columns = is_array($columns) ? $columns : func_get_args();
158
159 48
        $this->addColumns($columns);
160
161 48
        return $this;
162
    }
163
164
    /**
165
     * Add a new select column to the query.
166
     *
167
     * @param  array|mixed  $column
168
     * @return $this
169
     */
170 11
    public function addSelect($column)
171
    {
172 11
        $columns = is_array($column) ? $column : func_get_args();
173
174 11
        $this->addColumns($columns);
175
176 11
        return $this;
177
    }
178
179
    /**
180
     * @param array<mixed> $columns
181
     */
182 59
    protected function addColumns(array $columns): void
183
    {
184 59
        foreach ($columns as $as => $column) {
185 59
            if (is_string($as) && $this->isQueryable($column)) {
186
                if (is_null($this->columns)) {
187
                    $this->select($this->from . '.*');
188
                }
189
190
                $this->selectSub($column, $as);
191
192
                continue;
193
            }
194
195 59
            if (! is_string($as) || ! $this->isQueryable($column)) {
196 59
                $this->columns[$as] = $column;
197
198 59
                continue;
199
            }
200
201
            $this->columns[] = $column;
202
        }
203 59
    }
204
205
    /**
206
     * Get the SQL representation of the query.
207
     *
208
     * @return string
209
     */
210 34
    public function toSql()
211
    {
212 34
        $this->grammar->compileSelect($this)->setAql();
213 34
        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...
214
    }
215
216
    /**
217
     * Insert a new record into the database.
218
     *
219
     * @param array $values
220
     *
221
     * @throws BindException
222
     *
223
     * @return bool
224
     */
225 44
    public function insert(array $values): bool
226
    {
227 44
        $this->grammar->compileInsert($this, $values)->setAql();
228 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

228
        $results = $this->getConnection()->insert(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
229 44
        $this->aqb = new QueryBuilder();
230 44
        return $results;
231
    }
232
233
    /**
234
     * Insert a new record and get the value of the primary key.
235
     *
236
     * @param array<mixed> $values
237
     */
238 10
    public function insertGetId(array $values, $sequence = null)
239
    {
240 10
        $this->grammar->compileInsertGetId($this, $values, $sequence)->setAql();
241 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

241
        $response = $this->getConnection()->execute(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
242 10
        $this->aqb = new QueryBuilder();
243
244 10
        return (is_array($response)) ? end($response) : $response;
245
    }
246
247
    /**
248
     * Insert a new record into the database.
249
     *
250
     * @param array $values
251
     *
252
     * @throws BindException
253
     *
254
     * @return bool
255
     */
256 88
    public function insertOrIgnore(array $values): bool
257
    {
258 88
        $this->grammar->compileInsertOrIgnore($this, $values)->setAql();
259 88
        $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

259
        $results = $this->getConnection()->insert(/** @scrutinizer ignore-type */ $this->aqb);
Loading history...
260 88
        $this->aqb = new QueryBuilder();
261
262 88
        return $results;
263
    }
264
265
266
    /**
267
     * Execute the query as a "select" statement.
268
     *
269
     * @param array|string $columns
270
     *
271
     * @return Collection
272
     */
273 102
    public function get($columns = ['*'])
274
    {
275 102
        return collect($this->onceWithColumns(Arr::wrap($columns), function () {
276 102
            return $this->runSelect();
277 102
        }));
278
    }
279
280
    /**
281
     * Get the current query value bindings in a flattened array.
282
     *
283
     * @return array
284
     */
285 10
    public function getBindings()
286
    {
287 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...
288
    }
289
290 152
    protected function setAql()
291
    {
292 152
        $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

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