Test Setup Failed
Pull Request — master (#10)
by
unknown
01:32
created

Builder::insertGetId()   A

Complexity

Conditions 2
Paths 2

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 2
nc 2
nop 2
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 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...
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
     * Get the SQL representation of the query.
80
     *
81
     * @return string
82
     */
83
    public function toSql()
84
    {
85
        return $this->grammar->compileSelect($this)->aqb->query;
86
    }
87
88
    /**
89
     * Insert a new record into the database.
90
     * @param array $values
91
     * @return bool
92
     * @throws BindException
93
     */
94
    public function insert(array $values) : bool
95
    {
96
        $response = $this->getConnection()->insert($this->grammar->compileInsert($this, $values)->aqb);
97
        $this->aqb = new QueryBuilder();
98
99
        return $response;
100
    }
101
102
    /**
103
     * Insert a new record and get the value of the primary key.
104
     *
105
     * @param array $values
106
     * @param string|null $sequence
107
     * @return int
108
     * @throws BindException
109
     */
110
    public function insertGetId(array $values, $sequence = null)
111
    {
112
        $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...
113
        $this->aqb = new QueryBuilder();
114
115
        return (is_array($response)) ? end($response) : $response;
116
    }
117
118
    /**
119
     * Execute the query as a "select" statement.
120
     *
121
     * @param  array|string  $columns
122
     * @return Collection
123
     */
124
    public function get($columns = ['*'])
125
    {
126
        $results = collect($this->onceWithColumns(Arr::wrap($columns), function () {
127
            return $this->runSelect();
128
        }));
129
130
        return $results;
131
    }
132
133
    /**
134
     * Update a record in the database.
135
     *
136
     * @param  array  $values
137
     * @return int
138
     */
139
    public function update(array $values)
140
    {
141
        $response = $this->connection->update($this->grammar->compileUpdate($this, $values)->aqb);
142
        $this->aqb = new QueryBuilder();
143
144
        return $response;
145
    }
146
147
    /**
148
     * Delete a record from the database.
149
     *
150
     * @param  mixed  $_key
151
     * @return int
152
     */
153
    public function delete($_key = null)
154
    {
155
        $response = $this->connection->delete($this->grammar->compileDelete($this, $_key)->aqb);
156
        $this->aqb = new QueryBuilder();
157
158
        return $response;
159
    }
160
161
    public function registerAlias(string $table, string $alias) : void
162
    {
163
        $this->aliasRegistry[$table] = $alias;
164
    }
165
166
    public function getAlias(string $table) : string
167
    {
168
        return $this->aliasRegistry[$table];
169
    }
170
171
    /**
172
     * Execute an aggregate function on the database.
173
     *
174
     * @param  string  $function
175
     * @param  array   $columns
176
     * @return mixed
177
     */
178
    public function aggregate($function, $columns = ['*'])
179
    {
180
        $results = $this->cloneWithout($this->unions ? [] : ['columns'])
181
            ->setAggregate($function, $columns)
182
            ->get($columns);
183
        if (! $results->isEmpty()) {
184
            return $results[0];
185
        }
186
187
        return false;
188
    }
189
190
    /**
191
     * Add a basic where clause to the query.
192
     *
193
     * @param Closure|string|array $column
194
     * @param mixed $operator
195
     * @param mixed $value
196
     * @param string $boolean
197
     * @return Builder
198
     */
199
    public function where($column, $operator = null, $value = null, $boolean = 'and')
200
    {
201
        // If the column is an array, we will assume it is an array of key-value pairs
202
        // and can add them each as a where clause. We will maintain the boolean we
203
        // received when the method was called and pass it into the nested where.
204
        if (is_array($column)) {
205
            return $this->addArrayOfWheres($column, $boolean);
206
        }
207
208
        // Here we will make some assumptions about the operator. If only 2 values are
209
        // passed to the method, we will assume that the operator is an equals sign
210
        // and keep going. Otherwise, we'll require the operator to be passed in.
211
        [$value, $operator] = $this->prepareValueAndOperator(
212
            $value, $operator, func_num_args() === 2
213
        );
214
215
        // If the columns is actually a Closure instance, we will assume the developer
216
        // wants to begin a nested where statement which is wrapped in parenthesis.
217
        // We'll add that Closure to the query then return back out immediately.
218
        if ($column instanceof Closure) {
219
            return $this->whereNested($column, $boolean);
220
        }
221
222
        // If the given operator is not found in the list of valid operators we will
223
        // assume that the developer is just short-cutting the '=' operators and
224
        // we will set the operators to '=' and set the values appropriately.
225
        if ($this->invalidOperator($operator)) {
226
            [$value, $operator] = [$operator, '='];
227
        }
228
229
        // If the value is a Closure, it means the developer is performing an entire
230
        // sub-select within the query and we will need to compile the sub-select
231
        // within the where clause to get the appropriate query record results.
232
        if ($value instanceof Closure) {
233
            return $this->whereSub($column, $operator, $value, $boolean);
234
        }
235
236
        $type = 'Basic';
237
238
        // If the column is making a JSON reference we'll check to see if the value
239
        // is a boolean. If it is, we'll add the raw boolean string as an actual
240
        // value to the query to ensure this is properly handled by the query.
241
        if (Str::contains($column, '->') && is_bool($value)) {
242
            $value = new Expression($value ? 'true' : 'false');
243
244
            if (is_string($column)) {
245
                $type = 'JsonBoolean';
246
            }
247
        }
248
249
        // Now that we are working with just a simple query we can put the elements
250
        // in our array and add the query binding to our array of bindings that
251
        // will be bound to each SQL statements when it is finally executed.
252
        $this->wheres[] = compact(
253
            'type', 'column', 'operator', 'value', 'boolean'
254
        );
255
256
        if (! $value instanceof Expression) {
257
            $this->addBinding($value, 'where');
258
        }
259
260
        return $this;
261
    }
262
263
    /**
264
     * Determine if the given operator is supported.
265
     *
266
     * @param  string  $operator
267
     * @return bool
268
     */
269
    protected function invalidOperator($operator)
270
    {
271
        return ! in_array(strtolower($operator), $this->operators, true) &&
272
            ! isset($this->grammar->getOperators()[strtolower($operator)]);
273
    }
274
275
    /**
276
     * Add an "or where" clause to the query.
277
     *
278
     * @param Closure|string|array  $column
279
     * @param  mixed  $operator
280
     * @param  mixed  $value
281
     * @return IlluminateQueryBuilder|static
282
     */
283
    public function orWhere($column, $operator = null, $value = null)
284
    {
285
        return $this->where($column, $operator, $value, 'or');
286
    }
287
288
    /**
289
     * Add an "order by" clause to the query.
290
     *
291
     * @param  Closure|IlluminateQueryBuilder|string  $column
292
     * @param  string  $direction
293
     * @return $this
294
     *
295
     * @throws InvalidArgumentException
296
     */
297
    public function orderBy($column, $direction = 'asc')
298
    {
299
        if ($this->isQueryable($column)) {
300
            [$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...
301
302
            $column = new Expression('('.$query.')');
303
        }
304
305
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
306
            'column' => $column,
307
            'direction' => $direction,
308
        ];
309
310
        return $this;
311
    }
312
313
    /**
314
     * Add a raw "order by" clause to the query.
315
     *
316
     * @param \LaravelFreelancerNL\FluentAQL\Expressions\FunctionExpression $aql
317
     * @param array $bindings
318
     * @return $this
319
     */
320
    public function orderByRaw($aql, $bindings = [])
321
    {
322
        $type = 'Raw';
323
        $column = $aql;
324
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'column');
325
326
        return $this;
327
    }
328
329
    /**
330
     * Put the query's results in random order.
331
     *
332
     * @param  string  $seed
333
     * @return $this
334
     */
335
    public function inRandomOrder($seed = '')
336
    {
337
        return $this->orderByRaw($this->grammar->compileRandom($this));
338
    }
339
}
340