Completed
Push — master ( 25183e...34cd5e )
by Arjay
02:20
created

QueryBuilderEngine::compileColumnQuery()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
c 0
b 0
f 0
cc 5
eloc 12
nc 3
nop 5
rs 8.8571
1
<?php
2
3
namespace Yajra\Datatables\Engines;
4
5
use Closure;
6
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
8
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
9
use Illuminate\Database\Eloquent\Relations\MorphToMany;
10
use Illuminate\Database\Query\Builder;
11
use Illuminate\Database\Query\Expression;
12
use Illuminate\Support\Facades\Config;
13
use Illuminate\Support\Str;
14
use Yajra\Datatables\Helper;
15
use Yajra\Datatables\Request;
16
17
/**
18
 * Class QueryBuilderEngine.
19
 *
20
 * @package Yajra\Datatables\Engines
21
 * @author  Arjay Angeles <[email protected]>
22
 */
23
class QueryBuilderEngine extends BaseEngine
24
{
25
    /**
26
     * @param \Illuminate\Database\Query\Builder $builder
27
     * @param \Yajra\Datatables\Request $request
28
     */
29
    public function __construct(Builder $builder, Request $request)
30
    {
31
        $this->query = $builder;
32
        $this->init($request, $builder);
33
    }
34
35
    /**
36
     * Initialize attributes.
37
     *
38
     * @param  \Yajra\Datatables\Request $request
39
     * @param  \Illuminate\Database\Query\Builder $builder
40
     * @param  string $type
41
     */
42
    protected function init($request, $builder, $type = 'builder')
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
43
    {
44
        $this->request    = $request;
45
        $this->query_type = $type;
46
        $this->columns    = $builder->columns;
47
        $this->connection = $builder->getConnection();
48
        $this->prefix     = $this->connection->getTablePrefix();
49
        $this->database   = $this->connection->getDriverName();
50
        if ($this->isDebugging()) {
51
            $this->connection->enableQueryLog();
52
        }
53
    }
54
55
    /**
56
     * Set auto filter off and run your own filter.
57
     * Overrides global search
58
     *
59
     * @param \Closure $callback
60
     * @param bool $globalSearch
61
     * @return $this
62
     */
63
    public function filter(Closure $callback, $globalSearch = false)
64
    {
65
        $this->overrideGlobalSearch($callback, $this->query, $globalSearch);
66
67
        return $this;
68
    }
69
70
    /**
71
     * Organizes works
72
     *
73
     * @param bool $mDataSupport
74
     * @param bool $orderFirst
75
     * @return \Illuminate\Http\JsonResponse
76
     */
77
    public function make($mDataSupport = false, $orderFirst = false)
78
    {
79
        return parent::make($mDataSupport, $orderFirst);
80
    }
81
82
    /**
83
     * Count total items.
84
     *
85
     * @return integer
86
     */
87
    public function totalCount()
88
    {
89
        return $this->totalRecords ? $this->totalRecords : $this->count();
90
    }
91
92
    /**
93
     * Counts current query.
94
     *
95
     * @return int
96
     */
97
    public function count()
98
    {
99
        $myQuery = clone $this->query;
100
        // if its a normal query ( no union, having and distinct word )
101
        // replace the select with static text to improve performance
102 View Code Duplication
        if (! Str::contains(Str::lower($myQuery->toSql()), ['union', 'having', 'distinct', 'order by', 'group by'])) {
0 ignored issues
show
Bug introduced by
The method toSql does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
103
            $row_count = $this->wrap('row_count');
104
            $myQuery->select($this->connection->raw("'1' as {$row_count}"));
0 ignored issues
show
Bug introduced by
The method select does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
105
        }
106
107
        return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table'))
108
                                ->setBindings($myQuery->getBindings())->count();
0 ignored issues
show
Bug introduced by
The method getBindings does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
109
    }
110
111
    /**
112
     * Wrap column with DB grammar.
113
     *
114
     * @param string $column
115
     * @return string
116
     */
117
    protected function wrap($column)
118
    {
119
        return $this->connection->getQueryGrammar()->wrap($column);
120
    }
121
122
    /**
123
     * Perform global search.
124
     *
125
     * @return void
126
     */
127
    public function filtering()
128
    {
129
        $this->query->where(
130
            function ($query) {
131
                $globalKeyword = $this->request->keyword();
132
                $queryBuilder  = $this->getQueryBuilder($query);
133
134
                foreach ($this->request->searchableColumnIndex() as $index) {
135
                    $columnName = $this->getColumnName($index);
136
                    if ($this->isBlacklisted($columnName)) {
137
                        continue;
138
                    }
139
140
                    // check if custom column filtering is applied
141
                    if (isset($this->columnDef['filter'][$columnName])) {
142
                        $columnDef = $this->columnDef['filter'][$columnName];
143
                        // check if global search should be applied for the specific column
144
                        $applyGlobalSearch = count($columnDef['parameters']) == 0 || end($columnDef['parameters']) !== false;
145
                        if (! $applyGlobalSearch) {
146
                            continue;
147
                        }
148
149 View Code Duplication
                        if ($columnDef['method'] instanceof Closure) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
                            $whereQuery = $queryBuilder->newQuery();
151
                            call_user_func_array($columnDef['method'], [$whereQuery, $globalKeyword]);
152
                            $queryBuilder->addNestedWhereQuery($whereQuery, 'or');
153
                        } else {
154
                            $this->compileColumnQuery(
155
                                $queryBuilder,
156
                                Helper::getOrMethod($columnDef['method']),
157
                                $columnDef['parameters'],
158
                                $columnName,
159
                                $globalKeyword
0 ignored issues
show
Bug introduced by
It seems like $globalKeyword defined by $this->request->keyword() on line 131 can also be of type array; however, Yajra\Datatables\Engines...e::compileColumnQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
160
                            );
161
                        }
162 View Code Duplication
                    } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
                        if (count(explode('.', $columnName)) > 1) {
164
                            $eagerLoads     = $this->getEagerLoads();
165
                            $parts          = explode('.', $columnName);
166
                            $relationColumn = array_pop($parts);
167
                            $relation       = implode('.', $parts);
168
                            if (in_array($relation, $eagerLoads)) {
169
                                $this->compileRelationSearch(
170
                                    $queryBuilder,
171
                                    $relation,
172
                                    $relationColumn,
173
                                    $globalKeyword
0 ignored issues
show
Bug introduced by
It seems like $globalKeyword defined by $this->request->keyword() on line 131 can also be of type array; however, Yajra\Datatables\Engines...compileRelationSearch() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
174
                                );
175
                            } else {
176
                                $this->compileQuerySearch($queryBuilder, $columnName, $globalKeyword);
0 ignored issues
show
Bug introduced by
It seems like $globalKeyword defined by $this->request->keyword() on line 131 can also be of type array; however, Yajra\Datatables\Engines...e::compileQuerySearch() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
177
                            }
178
                        } else {
179
                            $this->compileQuerySearch($queryBuilder, $columnName, $globalKeyword);
0 ignored issues
show
Bug introduced by
It seems like $globalKeyword defined by $this->request->keyword() on line 131 can also be of type array; however, Yajra\Datatables\Engines...e::compileQuerySearch() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
180
                        }
181
                    }
182
183
                    $this->isFilterApplied = true;
184
                }
185
            }
186
        );
187
    }
188
189
    /**
190
     * Perform filter column on selected field.
191
     *
192
     * @param mixed $query
193
     * @param string|Closure $method
194
     * @param mixed $parameters
195
     * @param string $column
196
     * @param string $keyword
197
     */
198
    protected function compileColumnQuery($query, $method, $parameters, $column, $keyword)
199
    {
200
        if (method_exists($query, $method)
201
            && count($parameters) <= with(new \ReflectionMethod($query, $method))->getNumberOfParameters()
202
        ) {
203
            if (Str::contains(Str::lower($method), 'raw')
0 ignored issues
show
Bug introduced by
It seems like $method defined by parameter $method on line 198 can also be of type object<Closure>; however, Illuminate\Support\Str::lower() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
204
                || Str::contains(Str::lower($method), 'exists')
0 ignored issues
show
Bug introduced by
It seems like $method defined by parameter $method on line 198 can also be of type object<Closure>; however, Illuminate\Support\Str::lower() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
205
            ) {
206
                call_user_func_array(
207
                    [$query, $method],
208
                    $this->parameterize($parameters, $keyword)
209
                );
210
            } else {
211
                call_user_func_array(
212
                    [$query, $method],
213
                    $this->parameterize($column, $parameters, $keyword)
214
                );
215
            }
216
        }
217
    }
218
219
    /**
220
     * Build Query Builder Parameters.
221
     *
222
     * @return array
223
     */
224
    protected function parameterize()
225
    {
226
        $args       = func_get_args();
227
        $keyword    = count($args) > 2 ? $args[2] : $args[1];
228
        $parameters = Helper::buildParameters($args);
229
        $parameters = Helper::replacePatternWithKeyword($parameters, $keyword, '$1');
230
231
        return $parameters;
232
    }
233
234
    /**
235
     * Get eager loads keys if eloquent.
236
     *
237
     * @return array
238
     */
239
    protected function getEagerLoads()
240
    {
241
        if ($this->query_type == 'eloquent') {
242
            return array_keys($this->query->getEagerLoads());
0 ignored issues
show
Bug introduced by
The method getEagerLoads does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
243
        }
244
245
        return [];
246
    }
247
248
    /**
249
     * Add relation query on global search.
250
     *
251
     * @param mixed $query
252
     * @param string $relation
253
     * @param string $column
254
     * @param string $keyword
255
     */
256
    protected function compileRelationSearch($query, $relation, $column, $keyword)
257
    {
258
        $myQuery = clone $this->query;
259
260
        /**
261
         * For compile nested relation, we need store all nested relation as array
262
         * and reverse order to apply where query.
263
         * With this method we can create nested sub query with properly relation.
264
         */
265
266
        /**
267
         * Store all relation data that require in next step
268
         */
269
        $relationChunk = [];
270
271
        /**
272
         * Store last eloquent query builder for get next relation.
273
         */
274
        $lastQuery = $query;
275
276
        $relations    = explode('.', $relation);
277
        $lastRelation = end($relations);
278
        foreach ($relations as $relation) {
279
            $relationType = $myQuery->getModel()->{$relation}();
280
            $myQuery->orWhereHas($relation, function ($builder) use (
281
                $column,
282
                $keyword,
283
                $query,
284
                $relationType,
285
                $relation,
286
                $lastRelation,
287
                &$relationChunk,
288
                &$lastQuery
289
            ) {
290
                $builder->select($this->connection->raw('count(1)'));
291
292
                // We will perform search on last relation only.
293
                if ($relation == $lastRelation) {
294
                    $this->compileQuerySearch($builder, $column, $keyword, '');
295
                }
296
297
                // Put require object to next step!!
298
                $relationChunk[$relation] = [
299
                    'builder'      => $builder,
300
                    'relationType' => $relationType,
301
                    'query'        => $lastQuery,
302
                ];
303
304
                // This is trick make sub query.
305
                $lastQuery = $builder;
306
            });
307
308
            // This is trick to make nested relation by pass previous relation to be next query eloquent builder
309
            $myQuery = $relationType;
310
        }
311
312
        /**
313
         * Reverse them all
314
         */
315
        $relationChunk = array_reverse($relationChunk, true);
316
317
        /**
318
         * Create valuable for use in check last relation
319
         */
320
        end($relationChunk);
321
        $lastRelation = key($relationChunk);
322
        reset($relationChunk);
323
324
        /**
325
         * Walking ...
326
         */
327
        foreach ($relationChunk as $relation => $chunk) {
328
            // Prepare variables
329
            $builder      = $chunk['builder'];
330
            $relationType = $chunk['relationType'];
331
            $query        = $chunk['query'];
332
            $builder      = "({$builder->toSql()}) >= 1";
333
334
            // Check if it last relation we will use orWhereRaw
335
            if ($lastRelation == $relation) {
336
                $relationMethod = "orWhereRaw";
337
            } else {
338
                // For case parent relation of nested relation.
339
                // We must use and for properly query and get correct result
340
                $relationMethod = "whereRaw";
341
            }
342
343
            if ($relationType instanceof MorphToMany) {
344
                $query->{$relationMethod}($builder, [$relationType->getMorphClass(), $this->prepareKeyword($keyword)]);
345
            } else {
346
                $query->{$relationMethod}($builder, [$this->prepareKeyword($keyword)]);
347
            }
348
        }
349
    }
350
351
    /**
352
     * Compile query builder where clause depending on configurations.
353
     *
354
     * @param mixed $query
355
     * @param string $column
356
     * @param string $keyword
357
     * @param string $relation
358
     */
359
    protected function compileQuerySearch($query, $column, $keyword, $relation = 'or')
360
    {
361
        $column = $this->addTablePrefix($query, $column);
362
        $column = $this->castColumn($column);
363
        $sql    = $column . ' LIKE ?';
364
365
        if ($this->isCaseInsensitive()) {
366
            $sql = 'LOWER(' . $column . ') LIKE ?';
367
        }
368
369
        $query->{$relation . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]);
370
    }
371
372
    /**
373
     * Patch for fix about ambiguous field.
374
     * Ambiguous field error will appear when query use join table and search with keyword.
375
     *
376
     * @param mixed $query
377
     * @param string $column
378
     * @return string
379
     */
380
    protected function addTablePrefix($query, $column)
381
    {
382
        // Check if field does not have a table prefix
383
        if (strpos($column, '.') === false) {
384
            // Alternative method to check instanceof \Illuminate\Database\Eloquent\Builder
385
            if (method_exists($query, 'getQuery')) {
386
                $q = $query->getQuery();
387
            } else {
388
                $q = $query;
389
            }
390
391
            if (! $q->from instanceof Expression) {
392
                // Get table from query and add it.
393
                $column = $q->from . '.' . $column;
394
            }
395
        }
396
397
        return $this->wrap($column);
398
    }
399
400
    /**
401
     * Wrap a column and cast in pgsql.
402
     *
403
     * @param  string $column
404
     * @return string
405
     */
406
    protected function castColumn($column)
407
    {
408
        if ($this->database === 'pgsql') {
409
            $column = 'CAST(' . $column . ' as TEXT)';
410
        } elseif ($this->database === 'firebird') {
411
            $column = 'CAST(' . $column . ' as VARCHAR(255))';
412
        }
413
414
        return $column;
415
    }
416
417
    /**
418
     * Prepare search keyword based on configurations.
419
     *
420
     * @param string $keyword
421
     * @return string
422
     */
423
    protected function prepareKeyword($keyword)
424
    {
425
        if ($this->isCaseInsensitive()) {
426
            $keyword = Str::lower($keyword);
427
        }
428
429
        if ($this->isWildcard()) {
430
            $keyword = $this->wildcardLikeString($keyword);
431
        }
432
433
        if ($this->isSmartSearch()) {
434
            $keyword = "%$keyword%";
435
        }
436
437
        return $keyword;
438
    }
439
440
    /**
441
     * Perform column search.
442
     *
443
     * @return void
444
     */
445
    public function columnSearch()
446
    {
447
        $columns = (array) $this->request->input('columns');
448
449
        foreach ($columns as $index => $column) {
450
            if (! $this->request->isColumnSearchable($index)) {
451
                continue;
452
            }
453
454
            $column = $this->getColumnName($index);
455
456
            if (isset($this->columnDef['filter'][$column])) {
457
                $columnDef = $this->columnDef['filter'][$column];
458
                // get a raw keyword (without wildcards)
459
                $keyword = $this->getSearchKeyword($index, true);
460
                $builder = $this->getQueryBuilder();
461
462 View Code Duplication
                if ($columnDef['method'] instanceof Closure) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
463
                    $whereQuery = $builder->newQuery();
464
                    call_user_func_array($columnDef['method'], [$whereQuery, $keyword]);
465
                    $builder->addNestedWhereQuery($whereQuery);
466
                } else {
467
                    $this->compileColumnQuery(
468
                        $builder,
469
                        $columnDef['method'],
470
                        $columnDef['parameters'],
471
                        $column,
472
                        $keyword
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getSearchKeyword($index, true) on line 459 can also be of type array; however, Yajra\Datatables\Engines...e::compileColumnQuery() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
473
                    );
474
                }
475 View Code Duplication
            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
476
                if (count(explode('.', $column)) > 1) {
477
                    $eagerLoads     = $this->getEagerLoads();
478
                    $parts          = explode('.', $column);
479
                    $relationColumn = array_pop($parts);
480
                    $relation       = implode('.', $parts);
481
                    if (in_array($relation, $eagerLoads)) {
482
                        $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
483
                    }
484
                }
485
486
                $keyword = $this->getSearchKeyword($index);
487
                $this->compileColumnSearch($index, $column, $keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getSearchKeyword($index) on line 486 can also be of type array; however, Yajra\Datatables\Engines...::compileColumnSearch() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
488
            }
489
490
            $this->isFilterApplied = true;
491
        }
492
    }
493
494
    /**
495
     * Get proper keyword to use for search.
496
     *
497
     * @param int $i
498
     * @param bool $raw
499
     * @return string
500
     */
501
    protected function getSearchKeyword($i, $raw = false)
502
    {
503
        $keyword = $this->request->columnKeyword($i);
504
        if ($raw || $this->request->isRegex($i)) {
505
            return $keyword;
506
        }
507
508
        return $this->setupKeyword($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->columnKeyword($i) on line 503 can also be of type array; however, Yajra\Datatables\Engines...eEngine::setupKeyword() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
509
    }
510
511
    /**
512
     * Join eager loaded relation and get the related column name.
513
     *
514
     * @param string $relation
515
     * @param string $relationColumn
516
     * @return string
517
     */
518
    protected function joinEagerLoadedColumn($relation, $relationColumn)
519
    {
520
        $model = $this->query->getRelation($relation);
0 ignored issues
show
Bug introduced by
The method getRelation does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
521
        switch (true) {
522
            case $model instanceof BelongsToMany:
523
                $pivot   = $model->getTable();
524
                $pivotPK = $model->getExistenceCompareKey();
525
                $pivotFK = $model->getQualifiedParentKeyName();
526
                $this->performJoin($pivot, $pivotPK, $pivotFK);
527
528
                $related = $model->getRelated();
529
                $table   = $related->getTable();
530
                $tablePK = $related->getForeignKey();
531
                $foreign = $pivot . '.' . $tablePK;
532
                $other   = $related->getQualifiedKeyName();
533
534
                $this->query->addSelect($table . '.' . $relationColumn);
0 ignored issues
show
Bug introduced by
The method addSelect does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
535
                break;
536
537
            case $model instanceof HasOneOrMany:
538
                $table   = $model->getRelated()->getTable();
539
                $foreign = $model->getQualifiedForeignKeyName();
540
                $other   = $model->getQualifiedParentKeyName();
541
                break;
542
543
            case $model instanceof BelongsTo:
544
                $table   = $model->getRelated()->getTable();
545
                $foreign = $model->getQualifiedForeignKey();
546
                $other   = $model->getQualifiedOwnerKeyName();
547
                break;
548
549
            default:
550
                $table   = $model->getRelated()->getTable();
551
                $foreign = $model->getQualifiedForeignKey();
552
                $other   = $model->getQualifiedOtherKeyName();
553
        }
554
555
        $this->performJoin($table, $foreign, $other);
556
557
        return $table . '.' . $relationColumn;
558
    }
559
560
    /**
561
     * Perform join query.
562
     *
563
     * @param string $table
564
     * @param string $foreign
565
     * @param string $other
566
     */
567
    protected function performJoin($table, $foreign, $other)
568
    {
569
        $joins = [];
570
        foreach ((array) $this->getQueryBuilder()->joins as $key => $join) {
571
            $joins[] = $join->table;
572
        }
573
574
        if (! in_array($table, $joins)) {
575
            $this->getQueryBuilder()->leftJoin($table, $foreign, '=', $other);
576
        }
577
    }
578
579
    /**
580
     * Compile queries for column search.
581
     *
582
     * @param int $i
583
     * @param mixed $column
584
     * @param string $keyword
585
     */
586
    protected function compileColumnSearch($i, $column, $keyword)
587
    {
588
        if ($this->request->isRegex($i)) {
589
            $column = strstr($column, '(') ? $this->connection->raw($column) : $column;
590
            $this->regexColumnSearch($column, $keyword);
591
        } else {
592
            $this->compileQuerySearch($this->query, $column, $keyword, '');
593
        }
594
    }
595
596
    /**
597
     * Compile regex query column search.
598
     *
599
     * @param mixed $column
600
     * @param string $keyword
601
     */
602
    protected function regexColumnSearch($column, $keyword)
603
    {
604
        if ($this->isOracleSql()) {
605
            $sql = ! $this->isCaseInsensitive() ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
606
            $this->query->whereRaw($sql, [$keyword]);
0 ignored issues
show
Bug introduced by
The method whereRaw does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
607
        } elseif ($this->database == 'pgsql') {
608
            $sql = ! $this->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? ';
609
            $this->query->whereRaw($sql, [$keyword]);
610
        } else {
611
            $sql = ! $this->isCaseInsensitive() ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
612
            $this->query->whereRaw($sql, [Str::lower($keyword)]);
613
        }
614
    }
615
616
    /**
617
     * Perform sorting of columns.
618
     *
619
     * @return void
620
     */
621
    public function ordering()
622
    {
623
        if ($this->orderCallback) {
624
            call_user_func($this->orderCallback, $this->getQueryBuilder());
625
626
            return;
627
        }
628
629
        foreach ($this->request->orderableColumns() as $orderable) {
630
            $column = $this->getColumnName($orderable['column'], true);
631
632
            if ($this->isBlacklisted($column)) {
633
                continue;
634
            }
635
636
            if (isset($this->columnDef['order'][$column])) {
637
                $method     = $this->columnDef['order'][$column]['method'];
638
                $parameters = $this->columnDef['order'][$column]['parameters'];
639
                $this->compileColumnQuery(
640
                    $this->getQueryBuilder(),
641
                    $method,
642
                    $parameters,
643
                    $column,
644
                    $orderable['direction']
645
                );
646
            } else {
647
                $valid = 1;
648
                if (count(explode('.', $column)) > 1) {
649
                    $eagerLoads     = $this->getEagerLoads();
650
                    $parts          = explode('.', $column);
651
                    $relationColumn = array_pop($parts);
652
                    $relation       = implode('.', $parts);
653
654
                    if (in_array($relation, $eagerLoads)) {
655
                        $relationship = $this->query->getRelation($relation);
0 ignored issues
show
Bug introduced by
The method getRelation does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
656
                        if (! ($relationship instanceof MorphToMany)) {
657
                            $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
658
                        } else {
659
                            $valid = 0;
660
                        }
661
                    }
662
                }
663
664
                if ($valid == 1) {
665
                    if ($this->nullsLast) {
666
                        $this->getQueryBuilder()->orderByRaw($this->getNullsLastSql($column, $orderable['direction']));
667
                    } else {
668
                        $this->getQueryBuilder()->orderBy($column, $orderable['direction']);
669
                    }
670
                }
671
            }
672
        }
673
    }
674
675
    /**
676
     * Get NULLS LAST SQL.
677
     *
678
     * @param  string $column
679
     * @param  string $direction
680
     * @return string
681
     */
682
    protected function getNullsLastSql($column, $direction)
683
    {
684
        $sql = Config::get('datatables.nulls_last_sql', '%s %s NULLS LAST');
685
686
        return sprintf($sql, $column, $direction);
687
    }
688
689
    /**
690
     * Perform pagination
691
     *
692
     * @return void
693
     */
694
    public function paging()
695
    {
696
        $this->query->skip($this->request->input('start'))
0 ignored issues
show
Documentation introduced by
$this->request->input('start') is of type string|array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The method skip does only exist in Illuminate\Database\Query\Builder, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
697
                    ->take((int) $this->request->input('length') > 0 ? $this->request->input('length') : 10);
698
    }
699
700
    /**
701
     * Get results
702
     *
703
     * @return array|static[]
704
     */
705
    public function results()
706
    {
707
        return $this->query->get();
708
    }
709
}
710