Failed Conditions
Push — master ( fa3649...11c8ec )
by Bas
05:38
created

Builder::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
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\Str;
12
use InvalidArgumentException;
13
use LaravelFreelancerNL\Aranguent\Connection;
14
use LaravelFreelancerNL\FluentAQL\Exceptions\BindException;
15
use LaravelFreelancerNL\FluentAQL\Expressions\ExpressionInterface as ExpressionInterface;
16
use LaravelFreelancerNL\FluentAQL\QueryBuilder;
17
18
class Builder extends IlluminateQueryBuilder
19
{
20
21
    /**
22
     * @var Grammar
23
     */
24
    public $grammar;
25
26
    /**
27
     * @var Connection
28
     */
29
    public $connection;
30
31
    /**
32
     * @var QueryBuilder
33
     */
34
    public $aqb;
35
36
    /**
37
     * @override
38
     * Create a new query builder instance.
39
     *
40
     * @param ConnectionInterface $connection
41
     * @param Grammar             $grammar
42
     * @param Processor           $processor
43
     * @param QueryBuilder|null   $aqb
44
     */
45 80
    public function __construct(
46
        ConnectionInterface $connection,
47
        Grammar $grammar = null,
48
        Processor $processor = null,
49
        QueryBuilder $aqb = null
50
    ) {
51 80
        $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...
52 80
        $this->grammar = $grammar ?: $connection->getQueryGrammar();
53 80
        $this->processor = $processor ?: $connection->getPostProcessor();
54 80
        if (!$aqb instanceof QueryBuilder) {
55 80
            $aqb = new QueryBuilder();
56
        }
57 80
        $this->aqb = $aqb;
58 80
    }
59
60
    /**
61
     * Set the table which the query is targeting.
62
     *
63
     * @param  \Closure|IlluminateQueryBuilder|string  $table
64
     * @param  string|null  $as
65
     * @return IlluminateQueryBuilder
66
     */
67 80
    public function from($table, $as = null)
68
    {
69 80
        if ($this->isQueryable($table)) {
70
            return $this->fromSub($table, $as);
71
        }
72
73 80
        $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

73
        $this->grammar->registerTableAlias(/** @scrutinizer ignore-type */ $table, $as);
Loading history...
74
75 80
        $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...
76
77 80
        return $this;
78
    }
79
80
    /**
81
     * Run the query as a "select" statement against the connection.
82
     *
83
     * @return array
84
     */
85 49
    protected function runSelect()
86
    {
87 49
        $response = $this->connection->select($this->grammar->compileSelect($this)->aqb);
88 49
        $this->aqb = new QueryBuilder();
89
90 49
        return $response;
91
    }
92
93
    /**
94
     * Run a pagination count query.
95
     *
96
     * @param array $columns
97
     *
98
     * @return array
99
     */
100 1
    protected function runPaginationCountQuery($columns = ['*'])
101
    {
102 1
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
103
104 1
        $closeResults = $this->cloneWithout($without)
105 1
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
106 1
            ->setAggregate('count', $this->withoutSelectAliases($columns))
107 1
            ->get()->all();
108
109 1
        $this->aqb = new QueryBuilder();
110
111 1
        return $closeResults;
112
    }
113
114
    /**
115
     * Set the columns to be selected.
116
     *
117
     * @param  array|mixed  $columns
118
     * @return IlluminateQueryBuilder
119
     */
120 26
    public function select($columns = ['*'])
121
    {
122 26
        $this->columns = [];
123 26
        $this->bindings['select'] = [];
124 26
        $columns = is_array($columns) ? $columns : func_get_args();
125
126 26
        foreach ($columns as $as => $column) {
127 26
            if (is_string($as) && $this->isQueryable($column)) {
128
                $this->selectSub($column, $as);
129
            }
130 26
            if (! is_string($as) || ! $this->isQueryable($column)) {
131 26
                $this->columns[$as] = $column;
132
            }
133
        }
134
135 26
        return $this;
136
    }
137
138
    /**
139
     * Get the SQL representation of the query.
140
     *
141
     * @return string
142
     */
143 28
    public function toSql()
144
    {
145 28
        return $this->grammar->compileSelect($this)->aqb->query;
146
    }
147
148
    /**
149
     * Insert a new record into the database.
150
     *
151
     * @param array $values
152
     *
153
     * @throws BindException
154
     *
155
     * @return bool
156
     */
157 57
    public function insert(array $values): bool
158
    {
159 57
        $response = $this->getConnection()->insert($this->grammar->compileInsert($this, $values)->aqb);
160 57
        $this->aqb = new QueryBuilder();
161
162 57
        return $response;
163
    }
164
165
    /**
166
     * Insert a new record and get the value of the primary key.
167
     *
168
     * @param array       $values
169
     * @param string|null $sequence
170
     *
171
     * @throws BindException
172
     *
173
     * @return int
174
     */
175 8
    public function insertGetId(array $values, $sequence = null)
176
    {
177 8
        $response = $this->getConnection()->execute($this->grammar->compileInsertGetId($this, $values, $sequence)->aqb);
0 ignored issues
show
Unused Code introduced by
The call to LaravelFreelancerNL\Aran...r::compileInsertGetId() has too many arguments starting with $sequence. ( Ignorable by Annotation )

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

177
        $response = $this->getConnection()->execute($this->grammar->/** @scrutinizer ignore-call */ compileInsertGetId($this, $values, $sequence)->aqb);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
178 8
        $this->aqb = new QueryBuilder();
179
180 8
        return (is_array($response)) ? end($response) : $response;
0 ignored issues
show
introduced by
The condition is_array($response) is always true.
Loading history...
181
    }
182
183
    /**
184
     * Execute the query as a "select" statement.
185
     *
186
     * @param array|string $columns
187
     *
188
     * @return Collection
189
     */
190 49
    public function get($columns = ['*'])
191
    {
192 49
        return collect($this->onceWithColumns(Arr::wrap($columns), function () {
193 49
            return $this->runSelect();
194 49
        }));
195
    }
196
197
    /**
198
     * Get the current query value bindings in a flattened array.
199
     *
200
     * @return array
201
     */
202 10
    public function getBindings()
203
    {
204 10
        return $this->aqb->binds;
205
    }
206
207
    /**
208
     * Update a record in the database.
209
     *
210
     * @param array $values
211
     *
212
     * @return int
213
     */
214 13
    public function update(array $values)
215
    {
216 13
        $response = $this->connection->update($this->grammar->compileUpdate($this, $values)->aqb);
217 13
        $this->aqb = new QueryBuilder();
218
219 13
        return $response;
220
    }
221
222
    /**
223
     * Delete a record from the database.
224
     *
225
     * @param mixed $id
226
     *
227
     * @return int
228
     */
229 6
    public function delete($id = null)
230
    {
231 6
        $response = $this->connection->delete($this->grammar->compileDelete($this, $id)->aqb);
232 6
        $this->aqb = new QueryBuilder();
233
234 6
        return $response;
235
    }
236
237
    /**
238
     * Execute an aggregate function on the database.
239
     *
240
     * @param string $function
241
     * @param array  $columns
242
     *
243
     * @return mixed
244
     */
245 6
    public function aggregate($function, $columns = ['*'])
246
    {
247 6
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
248 6
            ->setAggregate($function, $columns)
249 6
            ->get($columns);
250
251 6
        $this->aqb = new QueryBuilder();
252
253 6
        if (!$results->isEmpty()) {
254 6
            return array_change_key_case((array) $results[0])['aggregate'];
255
        }
256
257
        return false;
258
    }
259
260
    /**
261
     * Add a basic where clause to the query.
262
     *
263
     * @param Closure|string|array $column
264
     * @param mixed                $operator
265
     * @param mixed                $value
266
     * @param string               $boolean
267
     *
268
     * @return Builder
269
     */
270 47
    public function where($column, $operator = null, $value = null, $boolean = 'and')
271
    {
272
        // If the column is an array, we will assume it is an array of key-value pairs
273
        // and can add them each as a where clause. We will maintain the boolean we
274
        // received when the method was called and pass it into the nested where.
275 47
        if (is_array($column)) {
276
            return $this->addArrayOfWheres($column, $boolean);
277
        }
278
279
        // Here we will make some assumptions about the operator. If only 2 values are
280
        // passed to the method, we will assume that the operator is an equals sign
281
        // and keep going. Otherwise, we'll require the operator to be passed in.
282 47
        [$value, $operator] = $this->prepareValueAndOperator(
283 47
            $value,
284
            $operator,
285 47
            func_num_args() === 2
286
        );
287
288
        // If the columns is actually a Closure instance, we will assume the developer
289
        // wants to begin a nested where statement which is wrapped in parenthesis.
290
        // We'll add that Closure to the query then return back out immediately.
291 47
        if ($column instanceof Closure) {
292
            return $this->whereNested($column, $boolean);
293
        }
294
295
        // If the given operator is not found in the list of valid operators we will
296
        // assume that the developer is just short-cutting the '=' operators and
297
        // we will set the operators to '==' and set the values appropriately.
298 47
        if ($this->invalidOperator($operator)) {
299
            [$value, $operator] = [$operator, '=='];
300
        }
301
302
        // If the value is a Closure, it means the developer is performing an entire
303
        // sub-select within the query and we will need to compile the sub-select
304
        // within the where clause to get the appropriate query record results.
305 47
        if ($value instanceof Closure) {
306
            return $this->whereSub($column, $operator, $value, $boolean);
307
        }
308
309 47
        $type = 'Basic';
310
311
        // Now that we are working with just a simple query we can put the elements
312
        // in our array and add the query binding to our array of bindings that
313
        // will be bound to each SQL statements when it is finally executed.
314 47
        $this->wheres[] = compact(
315 47
            'type',
316 47
            'column',
317 47
            'operator',
318 47
            'value',
319 47
            'boolean'
320
        );
321
322 47
        if (!$value instanceof Expression) {
323 47
            $this->addBinding($value, 'where');
324
        }
325
326 47
        return $this;
327
    }
328
329
    /**
330
     * Add a "where JSON contains" clause to the query.
331
     *
332
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
333
     *
334
     * @param  string  $column
335
     * @param  mixed  $value
336
     * @param  string  $boolean
337
     * @param  bool  $not
338
     * @return IlluminateQueryBuilder
339
     */
340 1
    public function whereJsonContains($column, $value, $boolean = 'and', $not = false)
341
    {
342 1
        $type = 'JsonContains';
343
344 1
        $this->wheres[] = compact('type', 'column', 'value', 'boolean', 'not');
345
346 1
        return $this;
347
    }
348
349
    /**
350
     * Determine if the given operator is supported.
351
     *
352
     * @param string $operator
353
     *
354
     * @return bool
355
     */
356 50
    protected function invalidOperator($operator)
357
    {
358 50
        return !in_array(strtolower($operator), $this->operators, true) &&
359 50
            !isset($this->grammar->getOperators()[strtoupper($operator)]);
360
    }
361
362
    /**
363
     * Add a join clause to the query.
364
     *
365
     * The boolean argument flag is part of this method's API in Laravel.
366
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
367
     *
368
     * @param string          $table
369
     * @param \Closure|string $first
370
     * @param string|null     $operator
371
     * @param string|null     $second
372
     * @param string          $type
373
     * @param bool            $where
374
     *
375
     * @return $this
376
     */
377 13
    public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
378
    {
379 13
        $join = $this->newJoinClause($this, $type, $table);
380
381
        // If the first "column" of the join is really a Closure instance the developer
382
        // is trying to build a join with a complex "on" clause containing more than
383
        // one condition, so we'll add the join and call a Closure with the query.
384 13
        if ($first instanceof Closure) {
385
            $first($join);
386
387
            $this->joins[] = $join;
388
        }
389 13
        if (! $first instanceof Closure) {
390
            // If the column is simply a string, we can assume the join simply has a basic
391
            // "on" clause with a single condition. So we will just build the join with
392
            // this simple join clauses attached to it. There is not a join callback.
393
394
            //where and on are the same for aql
395 13
            $method = $where ? 'where' : 'on';
396
397 13
            $this->joins[] = $join->$method($first, $operator, $second);
398
        }
399
400 13
        return $this;
401
    }
402
403
    /**
404
     * Add an "order by" clause to the query.
405
     *
406
     * @param Closure|IlluminateQueryBuilder|string $column
407
     * @param string                                $direction
408
     *
409
     * @throws InvalidArgumentException
410
     *
411
     * @return $this
412
     */
413 1
    public function orderBy($column, $direction = 'asc')
414
    {
415 1
        if ($this->isQueryable($column)) {
416
            [$query, $bindings] = $this->createSub($column);
417
418
            //fixme: Remove binding when implementing subqueries
419
            $bindings = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
420
421
            $column = new Expression('(' . $query . ')');
422
        }
423
424 1
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
425 1
            'column'    => $column,
426 1
            'direction' => $direction,
427
        ];
428
429 1
        return $this;
430
    }
431
432
    /**
433
     * Add a raw "order by" clause to the query.
434
     *
435
     * @param string|ExpressionInterface $aql
436
     * @param array                      $bindings
437
     *
438
     * @return $this
439
     */
440 1
    public function orderByRaw($aql, $bindings = [])
441
    {
442 1
        $bindings = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
443
444 1
        $type = 'Raw';
445 1
        $column = $aql;
446 1
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
447
448 1
        return $this;
449
    }
450
451
    /**
452
     * Put the query's results in random order.
453
     *
454
     * @param string $seed
455
     *
456
     * @return $this
457
     */
458 1
    public function inRandomOrder($seed = '')
459
    {
460
        // ArangoDB's random function doesn't accept a seed.
461 1
        $seed = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $seed is dead and can be removed.
Loading history...
462
463 1
        return $this->orderByRaw($this->grammar->compileRandom($this));
464
    }
465
466
    /**
467
     * Get a new join clause.
468
     *
469
     * @param IlluminateQueryBuilder $parentQuery
470
     * @param string                 $type
471
     * @param string                 $table
472
     *
473
     * @return JoinClause
474
     */
475 14
    protected function newJoinClause(IlluminateQueryBuilder $parentQuery, $type, $table)
476
    {
477 14
        return new JoinClause($parentQuery, $type, $table);
478
    }
479
}
480