Passed
Push — master ( de4b3f...0f9a6c )
by Bas
03:26 queued 11s
created

Builder::runPaginationCountQuery()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 12
rs 10
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
     * @var Grammar
22
     */
23
    public $grammar;
24
25
    /**
26
     * @var Connection
27
     */
28
    public $connection;
29
30
    /**
31
     * @var QueryBuilder
32
     */
33
    public $aqb;
34
35
    /**
36
     * Alias' are AQL variables
37
     * Sticking with the SQL based naming as this is the Laravel driver.
38
     * @var QueryBuilder
39
     */
40
    protected $aliasRegistry = [];
41
42
    /**
43
     * @override
44
     * Create a new query builder instance.
45
     *
46
     * @param ConnectionInterface $connection
47
     * @param Grammar $grammar
48
     * @param Processor $processor
49
     * @param QueryBuilder|null $aqb
50
     */
51
    public function __construct(ConnectionInterface $connection,
52
                                Grammar $grammar = null,
53
                                Processor $processor = null,
54
                                QueryBuilder $aqb = null)
55
    {
56
        $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...
57
        $this->grammar = $grammar ?: $connection->getQueryGrammar();
58
        $this->processor = $processor ?: $connection->getPostProcessor();
59
        if (! $aqb instanceof QueryBuilder) {
60
            $aqb = new QueryBuilder();
61
        }
62
        $this->aqb = $aqb;
63
    }
64
65
    /**
66
     * Run the query as a "select" statement against the connection.
67
     *
68
     * @return array
69
     */
70
    protected function runSelect()
71
    {
72
        $response = $this->connection->select($this->grammar->compileSelect($this)->aqb);
73
        $this->aqb = new QueryBuilder();
74
75
        return $response;
76
    }
77
78
    /**
79
     * Run a pagination count query.
80
     *
81
     * @param  array  $columns
82
     * @return array
83
     */
84
    protected function runPaginationCountQuery($columns = ['*'])
85
    {
86
        $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset'];
87
88
        $closeResults = $this->cloneWithout($without)
89
            ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order'])
90
            ->setAggregate('count', $this->withoutSelectAliases($columns))
91
            ->get()->all();
92
93
        $this->aqb = new QueryBuilder();
94
95
        return $closeResults;
96
    }
97
98
    /**
99
     * Get the SQL representation of the query.
100
     *
101
     * @return string
102
     */
103
    public function toSql()
104
    {
105
        return $this->grammar->compileSelect($this)->aqb->query;
106
    }
107
108
    /**
109
     * Insert a new record into the database.
110
     * @param array $values
111
     * @return bool
112
     * @throws BindException
113
     */
114
    public function insert(array $values) : bool
115
    {
116
        $response = $this->getConnection()->insert($this->grammar->compileInsert($this, $values)->aqb);
117
        $this->aqb = new QueryBuilder();
118
119
        return $response;
120
    }
121
122
    /**
123
     * Insert a new record and get the value of the primary key.
124
     *
125
     * @param array $values
126
     * @param string|null $sequence
127
     * @return int
128
     * @throws BindException
129
     */
130
    public function insertGetId(array $values, $sequence = null)
131
    {
132
        $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

132
        $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...
133
        $this->aqb = new QueryBuilder();
134
135
        return (is_array($response)) ? end($response) : $response;
0 ignored issues
show
introduced by
The condition is_array($response) is always true.
Loading history...
136
    }
137
138
    /**
139
     * Execute the query as a "select" statement.
140
     *
141
     * @param  array|string  $columns
142
     * @return Collection
143
     */
144
    public function get($columns = ['*'])
145
    {
146
        $results = collect($this->onceWithColumns(Arr::wrap($columns), function () {
147
            return $this->runSelect();
148
        }));
149
150
        return $results;
151
    }
152
153
    /**
154
     * Update a record in the database.
155
     *
156
     * @param  array  $values
157
     * @return int
158
     */
159
    public function update(array $values)
160
    {
161
        $response = $this->connection->update($this->grammar->compileUpdate($this, $values)->aqb);
162
        $this->aqb = new QueryBuilder();
163
164
        return $response;
165
    }
166
167
    /**
168
     * Delete a record from the database.
169
     *
170
     * @param  mixed  $_key
171
     * @return int
172
     */
173
    public function delete($_key = null)
174
    {
175
        $response = $this->connection->delete($this->grammar->compileDelete($this, $_key)->aqb);
176
        $this->aqb = new QueryBuilder();
177
178
        return $response;
179
    }
180
181
    public function registerAlias(string $table, string $alias) : void
182
    {
183
        $this->aliasRegistry[$table] = $alias;
184
    }
185
186
    public function getAlias(string $table) : string
187
    {
188
        return $this->aliasRegistry[$table];
189
    }
190
191
    /**
192
     * Execute an aggregate function on the database.
193
     *
194
     * @param  string  $function
195
     * @param  array   $columns
196
     * @return mixed
197
     */
198
    public function aggregate($function, $columns = ['*'])
199
    {
200
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
201
            ->setAggregate($function, $columns)
202
            ->get($columns);
203
204
        $this->aqb = new QueryBuilder();
205
206
        if (! $results->isEmpty()) {
207
            return array_change_key_case((array) $results[0])['aggregate'];
208
        }
209
210
        return false;
211
    }
212
213
214
215
    /**
216
     * Add a basic where clause to the query.
217
     *
218
     * @param Closure|string|array $column
219
     * @param mixed $operator
220
     * @param mixed $value
221
     * @param string $boolean
222
     * @return Builder
223
     */
224
    public function where($column, $operator = null, $value = null, $boolean = 'and')
225
    {
226
        // If the column is an array, we will assume it is an array of key-value pairs
227
        // and can add them each as a where clause. We will maintain the boolean we
228
        // received when the method was called and pass it into the nested where.
229
        if (is_array($column)) {
230
            return $this->addArrayOfWheres($column, $boolean);
231
        }
232
233
        // Here we will make some assumptions about the operator. If only 2 values are
234
        // passed to the method, we will assume that the operator is an equals sign
235
        // and keep going. Otherwise, we'll require the operator to be passed in.
236
        [$value, $operator] = $this->prepareValueAndOperator(
237
            $value, $operator, func_num_args() === 2
238
        );
239
240
        // If the columns is actually a Closure instance, we will assume the developer
241
        // wants to begin a nested where statement which is wrapped in parenthesis.
242
        // We'll add that Closure to the query then return back out immediately.
243
        if ($column instanceof Closure) {
244
            return $this->whereNested($column, $boolean);
245
        }
246
247
        // If the given operator is not found in the list of valid operators we will
248
        // assume that the developer is just short-cutting the '=' operators and
249
        // we will set the operators to '=' and set the values appropriately.
250
        if ($this->invalidOperator($operator)) {
251
            [$value, $operator] = [$operator, '='];
252
        }
253
254
        // If the value is a Closure, it means the developer is performing an entire
255
        // sub-select within the query and we will need to compile the sub-select
256
        // within the where clause to get the appropriate query record results.
257
        if ($value instanceof Closure) {
258
            return $this->whereSub($column, $operator, $value, $boolean);
259
        }
260
261
        $type = 'Basic';
262
263
        // If the column is making a JSON reference we'll check to see if the value
264
        // is a boolean. If it is, we'll add the raw boolean string as an actual
265
        // value to the query to ensure this is properly handled by the query.
266
        if (Str::contains($column, '->') && is_bool($value)) {
267
            $value = new Expression($value ? 'true' : 'false');
268
269
            if (is_string($column)) {
0 ignored issues
show
introduced by
The condition is_string($column) is always true.
Loading history...
270
                $type = 'JsonBoolean';
271
            }
272
        }
273
274
        // Now that we are working with just a simple query we can put the elements
275
        // in our array and add the query binding to our array of bindings that
276
        // will be bound to each SQL statements when it is finally executed.
277
        $this->wheres[] = compact(
278
            'type', 'column', 'operator', 'value', 'boolean'
279
        );
280
281
        if (! $value instanceof Expression) {
282
            $this->addBinding($value, 'where');
283
        }
284
285
        return $this;
286
    }
287
288
    /**
289
     * Add a "where null" clause to the query.
290
     *
291
     * @param  string|array  $columns
292
     * @param  string  $boolean
293
     * @param  bool    $not
294
     * @return $this
295
     */
296
    public function whereNull($columns, $boolean = 'and', $not = false)
297
    {
298
        $type = 'Basic';
299
        $operator = $not ? '!=' : '==';
300
        $value = null;
301
302
        foreach (Arr::wrap($columns) as $column) {
303
            $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
304
        }
305
306
        return $this;
307
    }
308
309
    /**
310
     * Determine if the given operator is supported.
311
     *
312
     * @param  string  $operator
313
     * @return bool
314
     */
315
    protected function invalidOperator($operator)
316
    {
317
        return ! in_array(strtolower($operator), $this->operators, true) &&
318
            ! isset($this->grammar->getOperators()[strtolower($operator)]);
319
    }
320
321
    /**
322
     * Add an "or where" clause to the query.
323
     *
324
     * @param Closure|string|array  $column
325
     * @param  mixed  $operator
326
     * @param  mixed  $value
327
     * @return IlluminateQueryBuilder|static
328
     */
329
    public function orWhere($column, $operator = null, $value = null)
330
    {
331
        return $this->where($column, $operator, $value, 'or');
332
    }
333
334
    /**
335
     * Add an "order by" clause to the query.
336
     *
337
     * @param  Closure|IlluminateQueryBuilder|string  $column
338
     * @param  string  $direction
339
     * @return $this
340
     *
341
     * @throws InvalidArgumentException
342
     */
343
    public function orderBy($column, $direction = 'asc')
344
    {
345
        if ($this->isQueryable($column)) {
346
            [$query, $bindings] = $this->createSub($column);
347
348
            $column = new Expression('('.$query.')');
349
        }
350
351
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
352
            'column' => $column,
353
            'direction' => $direction,
354
        ];
355
356
        return $this;
357
    }
358
359
    /**
360
     * Add a raw "order by" clause to the query.
361
     *
362
     * @param string|ExpressionInterface $aql
363
     * @param array $bindings
364
     * @return $this
365
     */
366
    public function orderByRaw($aql, $bindings = [])
367
    {
368
        $type = 'Raw';
369
        $column = $aql;
370
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
371
372
        return $this;
373
    }
374
375
    /**
376
     * Put the query's results in random order.
377
     *
378
     * @param  string  $seed
379
     * @return $this
380
     */
381
    public function inRandomOrder($seed = '')
382
    {
383
        return $this->orderByRaw($this->grammar->compileRandom($this));
384
    }
385
}
386