Passed
Push — next ( 64a7ed...91e38a )
by Bas
03:25 queued 12s
created

Builder::getBindings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
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 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\QueryBuilder;
19
use phpDocumentor\Reflection\Types\ArrayKey;
20
21
class Builder extends IlluminateQueryBuilder
22
{
23
    use BuildsJoinClauses;
24
    use BuildsSubqueries;
25
    use BuildsWhereClauses;
26
27
    /**
28
     * @var Grammar
29
     */
30
    public $grammar;
31
32
    /**
33
     * @var Connection
34
     */
35
    public $connection;
36
37
    /**
38
     * @var QueryBuilder
39
     */
40
    public $aqb;
41
42
    /**
43
     * The query variables that should be set.
44
     *
45
     * @var array<mixed>
46
     */
47
    public array $variables = [];
48
49
    /**
50
     * @override
51
     * Create a new query builder instance.
52
     *
53
     * @param  ConnectionInterface  $connection
54
     * @param  Grammar|null  $grammar
55
     * @param  Processor|null  $processor
56
     * @param  QueryBuilder|null  $aqb
57
     */
58 109
    public function __construct(
59
        ConnectionInterface $connection,
60
        Grammar $grammar = null,
61
        Processor $processor = null,
62
        QueryBuilder $aqb = null
63
    ) {
64 109
        $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...
65 109
        $this->grammar = $grammar ?: $connection->getQueryGrammar();
66 109
        $this->processor = $processor ?: $connection->getPostProcessor();
67 109
        if (!$aqb instanceof QueryBuilder) {
68 109
            $aqb = new QueryBuilder();
69
        }
70 109
        $this->aqb = $aqb;
71 109
    }
72
73
    /**
74
     * Delete a record from the database.
75
     *
76
     * @param mixed $id
77
     *
78
     * @return int
79
     */
80 6
    public function delete($id = null)
81
    {
82 6
        $this->aqb = new QueryBuilder();
83 6
        $this->grammar->compileDelete($this, $id)->setAql();
84 6
        return $this->connection->delete($this->aqb);
85
    }
86
87
    /**
88
     * Set the table which the query is targeting.
89
     *
90
     * @param  \Closure|IlluminateQueryBuilder|string  $table
91
     * @param  string|null  $as
92
     * @return IlluminateQueryBuilder
93
     */
94 109
    public function from($table, $as = null)
95
    {
96 109
        if ($this->isQueryable($table)) {
97
            return $this->fromSub($table, $as);
98
        }
99 109
        $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

99
        $this->grammar->registerTableAlias(/** @scrutinizer ignore-type */ $table, $as);
Loading history...
100
101 109
        $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...
102
103 109
        return $this;
104
    }
105
106
    /**
107
     * Run the query as a "select" statement against the connection.
108
     *
109
     * @return array
110
     */
111 76
    protected function runSelect()
112
    {
113 76
        $this->aqb = new QueryBuilder();
114 76
        $this->grammar->compileSelect($this)->setAql();
115 76
        $results = $this->connection->select($this->aqb);
116 75
        $this->aqb = new QueryBuilder();
117 75
        return $results;
118
    }
119
120
    /**
121
     * Run a pagination count query.
122
     *
123
     * @param array $columns
124
     *
125
     * @return array
126
     */
127 1
    protected function runPaginationCountQuery($columns = ['*'])
128
    {
129 1
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
130
131 1
        $closeResults = $this->cloneWithout($without)
132 1
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
133 1
            ->setAggregate('count', $this->withoutSelectAliases($columns))
134 1
            ->get()->all();
135
136 1
        $this->aqb = new QueryBuilder();
137
138 1
        return $closeResults;
139
    }
140
141
    /**
142
     * Set the columns to be selected.
143
     *
144
     * @param  array|mixed  $columns
145
     * @return IlluminateQueryBuilder
146
     */
147 43
    public function select($columns = ['*'])
148
    {
149 43
        $this->columns = [];
150 43
        $this->bindings['select'] = [];
151 43
        $columns = is_array($columns) ? $columns : func_get_args();
152
153 43
        $this->addColumns($columns);
154
155 43
        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 54
    protected function addColumns(array $columns): void
177
    {
178 54
        foreach ($columns as $as => $column) {
179 54
            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 54
            if (! is_string($as) || ! $this->isQueryable($column)) {
190 54
                $this->columns[$as] = $column;
191
192 54
                continue;
193
            }
194
195
            $this->columns[] = $column;
196
        }
197 54
    }
198
199
    /**
200
     * Get the SQL representation of the query.
201
     *
202
     * @return string
203
     */
204 29
    public function toSql()
205
    {
206 29
        $this->grammar->compileSelect($this)->setAql();
207 29
        return $this->aqb->query;
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 81
    public function insert(array $values): bool
220
    {
221 81
        $this->grammar->compileInsert($this, $values)->setAql();
222 81
        $results = $this->getConnection()->insert($this->aqb);
223 81
        $this->aqb = new QueryBuilder();
224 81
        return $results;
225
    }
226
227
    /**
228
     * Insert a new record and get the value of the primary key.
229
     *
230
     * @param array       $values
231
     * @param string|null $sequence
232
     *
233
     * @throws BindException
234
     *
235
     * @return int
236
     */
237 10
    public function insertGetId(array $values, $sequence = null)
238
    {
239 10
        $this->grammar->compileInsertGetId($this, $values, $sequence)->setAql();
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

239
        $this->grammar->/** @scrutinizer ignore-call */ 
240
                        compileInsertGetId($this, $values, $sequence)->setAql();

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...
240 10
        $response = $this->getConnection()->execute($this->aqb);
241 10
        $this->aqb = new QueryBuilder();
242
243 10
        return (is_array($response)) ? end($response) : $response;
244
    }
245
246
    /**
247
     * Execute the query as a "select" statement.
248
     *
249
     * @param array|string $columns
250
     *
251
     * @return Collection
252
     */
253 75
    public function get($columns = ['*'])
254
    {
255 75
        return collect($this->onceWithColumns(Arr::wrap($columns), function () {
256 75
            return $this->runSelect();
257 75
        }));
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;
268
    }
269
270 109
    protected function setAql()
271
    {
272 109
        $this->aqb = $this->aqb->get();
273
274 109
        return $this;
275
    }
276
277
    /**
278
     * Update a record in the database.
279
     *
280
     * @param array $values
281
     *
282
     * @return int
283
     */
284 16
    public function update(array $values)
285
    {
286 16
        $this->aqb = new QueryBuilder();
287
288 16
        $this->grammar->compileUpdate($this, $values)->setAql();
289 16
        $results = $this->connection->update($this->aqb);
290 16
        $this->aqb = new QueryBuilder();
291 16
        return $results;
292
    }
293
294
295
296
    /**
297
     * Execute an aggregate function on the database.
298
     *
299
     * @param string $function
300
     * @param array  $columns
301
     *
302
     * @return mixed
303
     */
304 6
    public function aggregate($function, $columns = ['*'])
305
    {
306 6
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
307 6
            ->setAggregate($function, $columns)
308 6
            ->get($columns);
309
310 6
        $this->aqb = new QueryBuilder();
311
312 6
        if (!$results->isEmpty()) {
313 6
            return array_change_key_case((array) $results[0])['aggregate'];
314
        }
315
316
        return false;
317
    }
318
319
320
    /**
321
     * Determine if the given operator is supported.
322
     *
323
     * @param string $operator
324
     *
325
     * @return bool
326
     */
327 67
    protected function invalidOperator($operator)
328
    {
329 67
        return !in_array(strtolower($operator), $this->operators, true) &&
330 67
            !isset($this->grammar->getOperators()[strtoupper($operator)]);
331
    }
332
333
334
    /**
335
     * Add an "order by" clause to the query.
336
     *
337
     * @param Closure|IlluminateQueryBuilder|string $column
338
     * @param string                                $direction
339
     *
340
     * @throws InvalidArgumentException
341
     *
342
     * @return $this
343
     */
344 1
    public function orderBy($column, $direction = 'asc')
345
    {
346 1
        if ($this->isQueryable($column)) {
347
            [$query, $bindings] = $this->createSub($column);
348
349
            //fixme: Remove binding when implementing subqueries
350
            $bindings = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
351
352
            $column = new Expression('(' . $query . ')');
353
        }
354
355 1
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
356 1
            'column'    => $column,
357 1
            'direction' => $direction,
358
        ];
359
360 1
        return $this;
361
    }
362
363
    /**
364
     * Add a raw "order by" clause to the query.
365
     *
366
     * @param string|ExpressionInterface $aql
367
     * @param array                      $bindings
368
     *
369
     * @return $this
370
     */
371 1
    public function orderByRaw($aql, $bindings = [])
372
    {
373 1
        $bindings = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
374
375 1
        $type = 'Raw';
376 1
        $column = $aql;
377 1
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
378
379 1
        return $this;
380
    }
381
382
383
    /**
384
     * Put the query's results in random order.
385
     *
386
     * @param string $seed
387
     *
388
     * @return $this
389
     */
390 1
    public function inRandomOrder($seed = '')
391
    {
392
        // ArangoDB's random function doesn't accept a seed.
393 1
        $seed = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $seed is dead and can be removed.
Loading history...
394
395 1
        return $this->orderByRaw($this->grammar->compileRandom($this));
396
    }
397
}
398