Test Setup Failed
Pull Request — master (#10)
by Bas
02:56 queued 01:19
created

Builder::runSelect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 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\QueryBuilder;
16
17
class Builder extends IlluminateQueryBuilder
18
{
19
    /**
20
     * @var Grammar
21
     */
22
    public $grammar;
23
24
    /**
25
     * @var Connection
26
     */
27
    public $connection;
28
29
    /**
30
     * @var QueryBuilder
31
     */
32
    public $aqb;
33
34
    /**
35
     * Alias' are AQL variables
36
     * Sticking with the SQL based naming as this is the Laravel driver.
37
     * @var QueryBuilder
38
     */
39
    protected $aliasRegistry = [];
40
41
    /**
42
     * @override
43
     * Create a new query builder instance.
44
     *
45
     * @param ConnectionInterface $connection
46
     * @param Grammar $grammar
47
     * @param Processor $processor
48
     * @param QueryBuilder|null $aqb
49
     */
50
    public function __construct(ConnectionInterface $connection,
51
                                Grammar $grammar = null,
52
                                Processor $processor = null,
53
                                QueryBuilder $aqb = null)
54
    {
55
        $this->connection = $connection;
0 ignored issues
show
Documentation Bug introduced by
$connection is of type object<Illuminate\Database\ConnectionInterface>, but the property $connection was declared to be of type object<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...
56
        $this->grammar = $grammar ?: $connection->getQueryGrammar();
57
        $this->processor = $processor ?: $connection->getPostProcessor();
58
        if (! $aqb instanceof QueryBuilder) {
59
            $aqb = new QueryBuilder();
60
        }
61
        $this->aqb = $aqb;
62
    }
63
64
    /**
65
     * Run the query as a "select" statement against the connection.
66
     *
67
     * @return array
68
     */
69
    protected function runSelect()
70
    {
71
        $response = $this->connection->select($this->grammar->compileSelect($this)->aqb);
72
        $this->aqb = new QueryBuilder();
73
74
        return $response;
75
    }
76
77
    /**
78
     * Get the SQL representation of the query.
79
     *
80
     * @return string
81
     */
82
    public function toSql()
83
    {
84
        return $this->grammar->compileSelect($this)->aqb->query;
85
    }
86
87
    /**
88
     * Insert a new record into the database.
89
     * @param array $values
90
     * @return bool
91
     * @throws BindException
92
     */
93
    public function insert(array $values) : bool
94
    {
95
        $response = $this->getConnection()->insert($this->grammar->compileInsert($this, $values)->aqb);
96
        $this->aqb = new QueryBuilder();
97
98
        return $response;
99
    }
100
101
    /**
102
     * Insert a new record and get the value of the primary key.
103
     *
104
     * @param array $values
105
     * @param string|null $sequence
106
     * @return int
107
     * @throws BindException
108
     */
109
    public function insertGetId(array $values, $sequence = null)
110
    {
111
        $response = $this->getConnection()->execute($this->grammar->compileInsertGetId($this, $values, $sequence)->aqb);
0 ignored issues
show
Unused Code introduced by
The call to Grammar::compileInsertGetId() has too many arguments starting with $sequence.

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Illuminate\Database\ConnectionInterface as the method execute() does only exist in the following implementations of said interface: LaravelFreelancerNL\Aranguent\Connection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
112
        $this->aqb = new QueryBuilder();
113
114
        return (is_array($response)) ? end($response) : $response;
115
    }
116
117
    /**
118
     * Execute the query as a "select" statement.
119
     *
120
     * @param  array|string  $columns
121
     * @return Collection
122
     */
123
    public function get($columns = ['*'])
124
    {
125
        $results = collect($this->onceWithColumns(Arr::wrap($columns), function () {
126
            return $this->runSelect();
127
        }));
128
129
        return $results;
130
    }
131
132
    /**
133
     * Update a record in the database.
134
     *
135
     * @param  array  $values
136
     * @return int
137
     */
138
    public function update(array $values)
139
    {
140
        $response = $this->connection->update($this->grammar->compileUpdate($this, $values)->aqb);
141
        $this->aqb = new QueryBuilder();
142
143
        return $response;
144
    }
145
146
    /**
147
     * Delete a record from the database.
148
     *
149
     * @param  mixed  $_key
150
     * @return int
151
     */
152
    public function delete($_key = null)
153
    {
154
        $response = $this->connection->delete($this->grammar->compileDelete($this, $_key)->aqb);
155
        $this->aqb = new QueryBuilder();
156
157
        return $response;
158
    }
159
160
    public function registerAlias(string $table, string $alias) : void
161
    {
162
        $this->aliasRegistry[$table] = $alias;
163
    }
164
165
    public function getAlias(string $table) : string
166
    {
167
        return $this->aliasRegistry[$table];
168
    }
169
170
    /**
171
     * Execute an aggregate function on the database.
172
     *
173
     * @param  string  $function
174
     * @param  array   $columns
175
     * @return mixed
176
     */
177
    public function aggregate($function, $columns = ['*'])
178
    {
179
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
180
            ->setAggregate($function, $columns)
181
            ->get($columns);
182
        if (! $results->isEmpty()) {
183
            return $results[0];
184
        }
185
186
        return false;
187
    }
188
189
    /**
190
     * Add a basic where clause to the query.
191
     *
192
     * @param Closure|string|array $column
193
     * @param mixed $operator
194
     * @param mixed $value
195
     * @param string $boolean
196
     * @return Builder
197
     */
198
    public function where($column, $operator = null, $value = null, $boolean = 'and')
199
    {
200
        // If the column is an array, we will assume it is an array of key-value pairs
201
        // and can add them each as a where clause. We will maintain the boolean we
202
        // received when the method was called and pass it into the nested where.
203
        if (is_array($column)) {
204
            return $this->addArrayOfWheres($column, $boolean);
205
        }
206
207
        // Here we will make some assumptions about the operator. If only 2 values are
208
        // passed to the method, we will assume that the operator is an equals sign
209
        // and keep going. Otherwise, we'll require the operator to be passed in.
210
        [$value, $operator] = $this->prepareValueAndOperator(
211
            $value, $operator, func_num_args() === 2
212
        );
213
214
        // If the columns is actually a Closure instance, we will assume the developer
215
        // wants to begin a nested where statement which is wrapped in parenthesis.
216
        // We'll add that Closure to the query then return back out immediately.
217
        if ($column instanceof Closure) {
218
            return $this->whereNested($column, $boolean);
219
        }
220
221
        // If the given operator is not found in the list of valid operators we will
222
        // assume that the developer is just short-cutting the '=' operators and
223
        // we will set the operators to '=' and set the values appropriately.
224
        if ($this->invalidOperator($operator)) {
225
            [$value, $operator] = [$operator, '='];
226
        }
227
228
        // If the value is a Closure, it means the developer is performing an entire
229
        // sub-select within the query and we will need to compile the sub-select
230
        // within the where clause to get the appropriate query record results.
231
        if ($value instanceof Closure) {
232
            return $this->whereSub($column, $operator, $value, $boolean);
233
        }
234
235
        $type = 'Basic';
236
237
        // If the column is making a JSON reference we'll check to see if the value
238
        // is a boolean. If it is, we'll add the raw boolean string as an actual
239
        // value to the query to ensure this is properly handled by the query.
240
        if (Str::contains($column, '->') && is_bool($value)) {
241
            $value = new Expression($value ? 'true' : 'false');
242
243
            if (is_string($column)) {
244
                $type = 'JsonBoolean';
245
            }
246
        }
247
248
        // Now that we are working with just a simple query we can put the elements
249
        // in our array and add the query binding to our array of bindings that
250
        // will be bound to each SQL statements when it is finally executed.
251
        $this->wheres[] = compact(
252
            'type', 'column', 'operator', 'value', 'boolean'
253
        );
254
255
        if (! $value instanceof Expression) {
256
            $this->addBinding($value, 'where');
257
        }
258
259
        return $this;
260
    }
261
262
    /**
263
     * Determine if the given operator is supported.
264
     *
265
     * @param  string  $operator
266
     * @return bool
267
     */
268
    protected function invalidOperator($operator)
269
    {
270
        return ! in_array(strtolower($operator), $this->operators, true) &&
271
            ! isset($this->grammar->getOperators()[strtolower($operator)]);
272
    }
273
274
    /**
275
     * Add an "or where" clause to the query.
276
     *
277
     * @param Closure|string|array  $column
278
     * @param  mixed  $operator
279
     * @param  mixed  $value
280
     * @return IlluminateQueryBuilder|static
281
     */
282
    public function orWhere($column, $operator = null, $value = null)
283
    {
284
        return $this->where($column, $operator, $value, 'or');
285
    }
286
287
    /**
288
     * Add an "order by" clause to the query.
289
     *
290
     * @param  Closure|IlluminateQueryBuilder|string  $column
291
     * @param  string  $direction
292
     * @return $this
293
     *
294
     * @throws InvalidArgumentException
295
     */
296
    public function orderBy($column, $direction = 'asc')
297
    {
298
        if ($this->isQueryable($column)) {
299
            [$query, $bindings] = $this->createSub($column);
0 ignored issues
show
Bug introduced by
The variable $query does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $bindings does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
300
301
            $column = new Expression('('.$query.')');
302
        }
303
304
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
305
            'column' => $column,
306
            'direction' => $direction,
307
        ];
308
309
        return $this;
310
    }
311
312
    /**
313
     * Add a raw "order by" clause to the query.
314
     *
315
     * @param \LaravelFreelancerNL\FluentAQL\Expressions\FunctionExpression $aql
316
     * @param array $bindings
317
     * @return $this
318
     */
319
    public function orderByRaw($aql, $bindings = [])
320
    {
321
        $type = 'Raw';
322
        $column = $aql;
323
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
324
325
        return $this;
326
    }
327
328
    /**
329
     * Put the query's results in random order.
330
     *
331
     * @param  string  $seed
332
     * @return $this
333
     */
334
    public function inRandomOrder($seed = '')
335
    {
336
        return $this->orderByRaw($this->grammar->compileRandom($this));
337
    }
338
}
339