Passed
Push — master ( ef1328...91a1d1 )
by Bas
13:32 queued 09:25
created

Builder::__construct()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

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

98
        $this->grammar->registerTableAlias(/** @scrutinizer ignore-type */ $table, $as);
Loading history...
99
100 97
        $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...
101
102 97
        return $this;
103
    }
104
105
    /**
106
     * Run the query as a "select" statement against the connection.
107
     *
108
     * @return array
109
     */
110 65
    protected function runSelect()
111
    {
112 65
        $this->aqb = new QueryBuilder();
113 65
        $this->grammar->compileSelect($this)->setAql();
114 65
        $results = $this->connection->select($this->aqb);
115 64
        $this->aqb = new QueryBuilder();
116 64
        return $results;
117
    }
118
119
    /**
120
     * Run a pagination count query.
121
     *
122
     * @param array $columns
123
     *
124
     * @return array
125
     */
126 1
    protected function runPaginationCountQuery($columns = ['*'])
127
    {
128 1
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
129
130 1
        $closeResults = $this->cloneWithout($without)
131 1
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
132 1
            ->setAggregate('count', $this->withoutSelectAliases($columns))
133 1
            ->get()->all();
134
135 1
        $this->aqb = new QueryBuilder();
136
137 1
        return $closeResults;
138
    }
139
140
    /**
141
     * Set the columns to be selected.
142
     *
143
     * @param  array|mixed  $columns
144
     * @return IlluminateQueryBuilder
145
     */
146 38
    public function select($columns = ['*'])
147
    {
148 38
        $this->columns = [];
149 38
        $this->bindings['select'] = [];
150 38
        $columns = is_array($columns) ? $columns : func_get_args();
151
152 38
        foreach ($columns as $as => $column) {
153 38
            if (is_string($as) && $this->isQueryable($column)) {
154
                $this->selectSub($column, $as);
155
            }
156 38
            if (! is_string($as) || ! $this->isQueryable($column)) {
157 38
                $this->columns[$as] = $column;
158
            }
159
        }
160
161 38
        return $this;
162
    }
163
164
165
    /**
166
     * Get the SQL representation of the query.
167
     *
168
     * @return string
169
     */
170 29
    public function toSql()
171
    {
172 29
        $this->grammar->compileSelect($this)->setAql();
173 29
        return $this->aqb->query;
174
    }
175
176
    /**
177
     * Insert a new record into the database.
178
     *
179
     * @param array $values
180
     *
181
     * @throws BindException
182
     *
183
     * @return bool
184
     */
185 69
    public function insert(array $values): bool
186
    {
187 69
        $this->grammar->compileInsert($this, $values)->setAql();
188 69
        $results = $this->getConnection()->insert($this->aqb);
189 69
        $this->aqb = new QueryBuilder();
190 69
        return $results;
191
    }
192
193
    /**
194
     * Insert a new record and get the value of the primary key.
195
     *
196
     * @param array       $values
197
     * @param string|null $sequence
198
     *
199
     * @throws BindException
200
     *
201
     * @return int
202
     */
203 10
    public function insertGetId(array $values, $sequence = null)
204
    {
205 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

205
        $this->grammar->/** @scrutinizer ignore-call */ 
206
                        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...
206 10
        $response = $this->getConnection()->execute($this->aqb);
207 10
        $this->aqb = new QueryBuilder();
208
209 10
        return (is_array($response)) ? end($response) : $response;
0 ignored issues
show
introduced by
The condition is_array($response) is always true.
Loading history...
210
    }
211
212
    /**
213
     * Execute the query as a "select" statement.
214
     *
215
     * @param array|string $columns
216
     *
217
     * @return Collection
218
     */
219 65
    public function get($columns = ['*'])
220
    {
221 65
        return collect($this->onceWithColumns(Arr::wrap($columns), function () {
222 65
            return $this->runSelect();
223 65
        }));
224
    }
225
226
    /**
227
     * Get the current query value bindings in a flattened array.
228
     *
229
     * @return array
230
     */
231 10
    public function getBindings()
232
    {
233 10
        return $this->aqb->binds;
234
    }
235
236 97
    protected function setAql()
237
    {
238 97
        $this->aqb = $this->aqb->get();
239
240 97
        return $this;
241
    }
242
243
    /**
244
     * Update a record in the database.
245
     *
246
     * @param array $values
247
     *
248
     * @return int
249
     */
250 15
    public function update(array $values)
251
    {
252 15
        $this->aqb = new QueryBuilder();
253
254 15
        $this->grammar->compileUpdate($this, $values)->setAql();
255 15
        $results = $this->connection->update($this->aqb);
256 15
        $this->aqb = new QueryBuilder();
257 15
        return $results;
258
    }
259
260
261
262
    /**
263
     * Execute an aggregate function on the database.
264
     *
265
     * @param string $function
266
     * @param array  $columns
267
     *
268
     * @return mixed
269
     */
270 6
    public function aggregate($function, $columns = ['*'])
271
    {
272 6
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
273 6
            ->setAggregate($function, $columns)
274 6
            ->get($columns);
275
276 6
        $this->aqb = new QueryBuilder();
277
278 6
        if (!$results->isEmpty()) {
279 6
            return array_change_key_case((array) $results[0])['aggregate'];
280
        }
281
282
        return false;
283
    }
284
285
286
    /**
287
     * Determine if the given operator is supported.
288
     *
289
     * @param string $operator
290
     *
291
     * @return bool
292
     */
293 64
    protected function invalidOperator($operator)
294
    {
295 64
        return !in_array(strtolower($operator), $this->operators, true) &&
296 64
            !isset($this->grammar->getOperators()[strtoupper($operator)]);
297
    }
298
299
300
    /**
301
     * Add an "order by" clause to the query.
302
     *
303
     * @param Closure|IlluminateQueryBuilder|string $column
304
     * @param string                                $direction
305
     *
306
     * @throws InvalidArgumentException
307
     *
308
     * @return $this
309
     */
310 1
    public function orderBy($column, $direction = 'asc')
311
    {
312 1
        if ($this->isQueryable($column)) {
313
            [$query, $bindings] = $this->createSub($column);
314
315
            //fixme: Remove binding when implementing subqueries
316
            $bindings = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
317
318
            $column = new Expression('(' . $query . ')');
319
        }
320
321 1
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
322 1
            'column'    => $column,
323 1
            'direction' => $direction,
324
        ];
325
326 1
        return $this;
327
    }
328
329
    /**
330
     * Add a raw "order by" clause to the query.
331
     *
332
     * @param string|ExpressionInterface $aql
333
     * @param array                      $bindings
334
     *
335
     * @return $this
336
     */
337 1
    public function orderByRaw($aql, $bindings = [])
338
    {
339 1
        $bindings = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $bindings is dead and can be removed.
Loading history...
340
341 1
        $type = 'Raw';
342 1
        $column = $aql;
343 1
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
344
345 1
        return $this;
346
    }
347
348
349
    /**
350
     * Put the query's results in random order.
351
     *
352
     * @param string $seed
353
     *
354
     * @return $this
355
     */
356 1
    public function inRandomOrder($seed = '')
357
    {
358
        // ArangoDB's random function doesn't accept a seed.
359 1
        $seed = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $seed is dead and can be removed.
Loading history...
360
361 1
        return $this->orderByRaw($this->grammar->compileRandom($this));
362
    }
363
}
364