Completed
Push — master ( 3a0338...c73ed2 )
by Arjay
01:57
created

QueryBuilderEngine::paging()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
c 0
b 0
f 0
cc 2
eloc 3
nc 1
nop 0
rs 9.4285
1
<?php
2
3
namespace Yajra\Datatables\Engines;
4
5
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
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\Str;
13
use Yajra\Datatables\Request;
14
15
/**
16
 * Class QueryBuilderEngine.
17
 *
18
 * @package Yajra\Datatables\Engines
19
 * @author  Arjay Angeles <[email protected]>
20
 */
21
class QueryBuilderEngine extends BaseEngine
22
{
23
    /**
24
     * Builder object.
25
     *
26
     * @var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
27
     */
28
    protected $query;
29
30
    /**
31
     * Query builder object.
32
     *
33
     * @var \Illuminate\Database\Query\Builder
34
     */
35
    protected $builder;
36
37
    /**
38
     * Database connection used.
39
     *
40
     * @var \Illuminate\Database\Connection
41
     */
42
    protected $connection;
43
44
    /**
45
     * @param \Illuminate\Database\Query\Builder $builder
46
     * @param \Yajra\Datatables\Request          $request
47
     */
48
    public function __construct(Builder $builder, Request $request)
49
    {
50
        $this->query      = $builder;
51
        $this->request    = $request;
52
        $this->columns    = $builder->columns;
53
        $this->connection = $builder->getConnection();
54
        if ($this->config()->isDebugging()) {
55
            $this->connection->enableQueryLog();
56
        }
57
    }
58
59
    /**
60
     * Set auto filter off and run your own filter.
61
     * Overrides global search.
62
     *
63
     * @param callable $callback
64
     * @param bool     $globalSearch
65
     * @return $this
66
     */
67
    public function filter(callable $callback, $globalSearch = false)
68
    {
69
        $this->overrideGlobalSearch($callback, $this->query, $globalSearch);
70
71
        return $this;
72
    }
73
74
    /**
75
     * Perform global search.
76
     *
77
     * @return void
78
     */
79 View Code Duplication
    public function filtering()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
80
    {
81
        $keyword = $this->request->keyword();
82
83
        if ($this->config()->isSmartSearch()) {
84
            $this->smartGlobalSearch($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->keyword() on line 81 can also be of type array; however, Yajra\Datatables\Engines...ne::smartGlobalSearch() 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...
85
86
            return;
87
        }
88
89
        $this->globalSearch($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->keyword() on line 81 can also be of type array; however, Yajra\Datatables\Engines...rEngine::globalSearch() 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...
90
    }
91
92
    /**
93
     * Perform multi-term search by splitting keyword into
94
     * individual words and searches for each of them.
95
     *
96
     * @param string $keyword
97
     */
98
    private function smartGlobalSearch($keyword)
99
    {
100
        $keywords = array_filter(explode(' ', $keyword));
101
102
        foreach ($keywords as $keyword) {
103
            $this->globalSearch($keyword);
104
        }
105
    }
106
107
    /**
108
     * Perform global search for the given keyword.
109
     *
110
     * @param string $keyword
111
     */
112
    protected function globalSearch($keyword)
113
    {
114
        $this->query->where(function ($query) use ($keyword) {
115
            $query = $this->getBaseQueryBuilder($query);
116
117
            foreach ($this->request->searchableColumnIndex() as $index) {
118
                $columnName = $this->getColumnName($index);
119
                if ($this->isBlacklisted($columnName) && !$this->hasCustomFilter($columnName)) {
120
                    continue;
121
                }
122
123
                if ($this->hasCustomFilter($columnName)) {
124
                    $this->applyFilterColumn($query, $columnName, $keyword, 'or');
125
                } else {
126
                    $this->compileQuerySearch($query, $columnName, $keyword);
127
                }
128
129
                $this->isFilterApplied = true;
130
            }
131
        });
132
    }
133
134
    /**
135
     * Get the base query builder instance.
136
     *
137
     * @param mixed $instance
138
     * @return \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
139
     */
140
    protected function getBaseQueryBuilder($instance = null)
141
    {
142
        if (!$instance) {
143
            $instance = $this->query;
144
        }
145
146
        if ($instance instanceof EloquentBuilder) {
147
            return $instance->getQuery();
148
        }
149
150
        return $instance;
151
    }
152
153
    /**
154
     * Check if column has custom filter handler.
155
     *
156
     * @param  string $columnName
157
     * @return bool
158
     */
159
    public function hasCustomFilter($columnName)
160
    {
161
        return isset($this->columnDef['filter'][$columnName]);
162
    }
163
164
    /**
165
     * Apply filterColumn api search.
166
     *
167
     * @param mixed  $query
168
     * @param string $columnName
169
     * @param string $keyword
170
     * @param string $boolean
171
     */
172
    protected function applyFilterColumn($query, $columnName, $keyword, $boolean = 'and')
173
    {
174
        $callback = $this->columnDef['filter'][$columnName]['method'];
175
        $builder  = $query->newQuery();
176
        $callback($builder, $keyword);
177
        $query->addNestedWhereQuery($builder, $boolean);
178
    }
179
180
    /**
181
     * Compile query builder where clause depending on configurations.
182
     *
183
     * @param mixed  $query
184
     * @param string $column
185
     * @param string $keyword
186
     * @param string $relation
187
     */
188
    protected function compileQuerySearch($query, $column, $keyword, $relation = 'or')
189
    {
190
        $column = $this->addTablePrefix($query, $column);
191
        $column = $this->castColumn($column);
192
        $sql    = $column . ' LIKE ?';
193
194
        if ($this->config()->isCaseInsensitive()) {
195
            $sql = 'LOWER(' . $column . ') LIKE ?';
196
        }
197
198
        $query->{$relation . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]);
199
    }
200
201
    /**
202
     * Patch for fix about ambiguous field.
203
     * Ambiguous field error will appear when query use join table and search with keyword.
204
     *
205
     * @param mixed  $query
206
     * @param string $column
207
     * @return string
208
     */
209
    protected function addTablePrefix($query, $column)
210
    {
211
        if (strpos($column, '.') === false) {
212
            $q = $this->getBaseQueryBuilder($query);
213
            if (!$q->from instanceof Expression) {
214
                $column = $q->from . '.' . $column;
215
            }
216
        }
217
218
        return $this->wrap($column);
219
    }
220
221
    /**
222
     * Wrap column with DB grammar.
223
     *
224
     * @param string $column
225
     * @return string
226
     */
227
    protected function wrap($column)
228
    {
229
        return $this->connection->getQueryGrammar()->wrap($column);
230
    }
231
232
    /**
233
     * Wrap a column and cast based on database driver.
234
     *
235
     * @param  string $column
236
     * @return string
237
     */
238
    protected function castColumn($column)
239
    {
240
        switch ($this->connection->getDriverName()) {
241
            case 'pgsql':
242
                return 'CAST(' . $column . ' as TEXT)';
243
            case 'firebird':
244
                return 'CAST(' . $column . ' as VARCHAR(255))';
245
            default:
246
                return $column;
247
        }
248
    }
249
250
    /**
251
     * Prepare search keyword based on configurations.
252
     *
253
     * @param string $keyword
254
     * @return string
255
     */
256
    protected function prepareKeyword($keyword)
257
    {
258
        if ($this->config()->isCaseInsensitive()) {
259
            $keyword = Str::lower($keyword);
260
        }
261
262
        if ($this->config()->isWildcard()) {
263
            $keyword = $this->wildcardLikeString($keyword);
264
        }
265
266
        if ($this->config()->isSmartSearch()) {
267
            $keyword = "%$keyword%";
268
        }
269
270
        return $keyword;
271
    }
272
273
    /**
274
     * Organizes works.
275
     *
276
     * @param bool $mDataSupport
277
     * @return \Illuminate\Http\JsonResponse
278
     * @throws \Exception
279
     */
280
    public function make($mDataSupport = false)
281
    {
282
        try {
283
            $this->totalRecords = $this->totalCount();
284
285
            if ($this->totalRecords) {
286
                $this->filterRecords();
287
                $this->ordering();
288
                $this->paginate();
289
            }
290
291
            $data = $this->transform($this->getProcessedData($mDataSupport));
292
293
            return $this->render($data);
294
        } catch (\Exception $exception) {
295
            return $this->errorResponse($exception);
296
        }
297
    }
298
299
    /**
300
     * Count total items.
301
     *
302
     * @return integer
303
     */
304
    public function totalCount()
305
    {
306
        return $this->totalRecords ? $this->totalRecords : $this->count();
307
    }
308
309
    /**
310
     * Counts current query.
311
     *
312
     * @return int
313
     */
314
    public function count()
315
    {
316
        $builder = $this->prepareCountQuery();
317
        $table   = $this->connection->raw('(' . $builder->toSql() . ') count_row_table');
1 ignored issue
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...
318
319
        return $this->connection->table($table)
320
                                ->setBindings($builder->getBindings())
1 ignored issue
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...
321
                                ->count();
322
    }
323
324
    /**
325
     * Prepare count query builder.
326
     *
327
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
328
     */
329
    protected function prepareCountQuery()
330
    {
331
        $builder = clone $this->query;
332
333
        if ($this->isComplexQuery($builder)) {
0 ignored issues
show
Bug introduced by
It seems like $builder defined by clone $this->query on line 331 can also be of type object<Illuminate\Database\Query\Builder>; however, Yajra\Datatables\Engines...ngine::isComplexQuery() does only seem to accept object<Illuminate\Database\Eloquent\Builder>, 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...
334
            $row_count = $this->wrap('row_count');
335
            $builder->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...
336
        }
337
338
        return $builder;
339
    }
340
341
    /**
342
     * Check if builder query uses complex sql.
343
     *
344
     * @param \Illuminate\Database\Eloquent\Builder $builder
345
     * @return bool
346
     */
347
    protected function isComplexQuery($builder)
348
    {
349
        return !Str::contains(Str::lower($builder->toSql()), ['union', 'having', 'distinct', 'order by', 'group by']);
350
    }
351
352
    /**
353
     * Perform sorting of columns.
354
     *
355
     * @return void
356
     */
357
    public function ordering()
358
    {
359
        if ($this->orderCallback) {
360
            call_user_func($this->orderCallback, $this->getBaseQueryBuilder());
361
362
            return;
363
        }
364
365
        foreach ($this->request->orderableColumns() as $orderable) {
366
            $column = $this->getColumnName($orderable['column'], true);
367
368
            if ($this->isBlacklisted($column) && !$this->hasCustomOrder($column)) {
369
                continue;
370
            }
371
372
            if ($this->hasCustomOrder($column)) {
373
                $sql      = $this->columnDef['order'][$column]['sql'];
374
                $sql      = str_replace('$1', $orderable['direction'], $sql);
375
                $bindings = $this->columnDef['order'][$column]['bindings'];
376
                $this->query->orderByRaw($sql, $bindings);
0 ignored issues
show
Bug introduced by
The method orderByRaw 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...
377
            } else {
378
                $valid = 1;
379
                if (count(explode('.', $column)) > 1) {
380
                    $eagerLoads     = $this->getEagerLoads();
381
                    $parts          = explode('.', $column);
382
                    $relationColumn = array_pop($parts);
383
                    $relation       = implode('.', $parts);
384
385
                    if (in_array($relation, $eagerLoads)) {
386
                        // Loop for nested relations
387
                        // This code is check morph many or not.
388
                        // If one of nested relation is MorphToMany
389
                        // we will call joinEagerLoadedColumn.
390
                        $lastQuery     = $this->query;
391
                        $isMorphToMany = false;
392
                        foreach (explode('.', $relation) as $eachRelation) {
393
                            $relationship = $lastQuery->getRelation($eachRelation);
394
                            if (!($relationship instanceof MorphToMany)) {
395
                                $isMorphToMany = true;
396
                            }
397
                            $lastQuery = $relationship;
398
                        }
399
                        if ($isMorphToMany) {
400
                            $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
401
                        } else {
402
                            $valid = 0;
403
                        }
404
                    }
405
                }
406
407
                if ($valid == 1) {
408
                    if ($this->nullsLast) {
409
                        $this->getBaseQueryBuilder()->orderByRaw($this->getNullsLastSql($column,
410
                            $orderable['direction']));
411
                    } else {
412
                        $this->getBaseQueryBuilder()->orderBy($column, $orderable['direction']);
0 ignored issues
show
Bug introduced by
The method orderBy 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...
413
                    }
414
                }
415
            }
416
        }
417
    }
418
419
    /**
420
     * Check if column has custom sort handler.
421
     *
422
     * @param string $column
423
     * @return bool
424
     */
425
    protected function hasCustomOrder($column)
426
    {
427
        return isset($this->columnDef['order'][$column]);
428
    }
429
430
    /**
431
     * Get eager loads keys if eloquent.
432
     *
433
     * @return array
434
     */
435
    protected function getEagerLoads()
436
    {
437
        if ($this->query instanceof EloquentBuilder) {
438
            return array_keys($this->query->getEagerLoads());
439
        }
440
441
        return [];
442
    }
443
444
    /**
445
     * Join eager loaded relation and get the related column name.
446
     *
447
     * @param string $relation
448
     * @param string $relationColumn
449
     * @return string
450
     */
451
    protected function joinEagerLoadedColumn($relation, $relationColumn)
452
    {
453
        $table = '';
454
        $lastQuery = $this->query;
455
        foreach (explode('.', $relation) as $eachRelation) {
456
            $model = $lastQuery->getRelation($eachRelation);
457
            switch (true) {
458
                case $model instanceof BelongsToMany:
459
                    $pivot   = $model->getTable();
460
                    $pivotPK = $model->getExistenceCompareKey();
461
                    $pivotFK = $model->getQualifiedParentKeyName();
462
                    $this->performJoin($pivot, $pivotPK, $pivotFK);
463
464
                    $related = $model->getRelated();
465
                    $table   = $related->getTable();
466
                    $tablePK = $related->getForeignKey();
467
                    $foreign = $pivot . '.' . $tablePK;
468
                    $other   = $related->getQualifiedKeyName();
469
470
                    $lastQuery->addSelect($table . '.' . $relationColumn);
471
                    $this->performJoin($table, $foreign, $other);
472
473
                    break;
474
475
                case $model instanceof HasOneOrMany:
476
                    $table   = $model->getRelated()->getTable();
477
                    $foreign = $model->getQualifiedForeignKeyName();
478
                    $other   = $model->getQualifiedParentKeyName();
479
                    break;
480
481
                case $model instanceof BelongsTo:
482
                    $table   = $model->getRelated()->getTable();
483
                    $foreign = $model->getQualifiedForeignKey();
484
                    $other   = $model->getQualifiedOwnerKeyName();
485
                    break;
486
487
                default:
488
                    $table   = $model->getRelated()->getTable();
489
                    $foreign = $model->getQualifiedForeignKey();
490
                    $other   = $model->getQualifiedOtherKeyName();
491
            }
492
            $this->performJoin($table, $foreign, $other);
493
            $lastQuery = $model->getQuery();
494
        }
495
496
        return $table . '.' . $relationColumn;
497
    }
498
499
    /**
500
     * Perform join query.
501
     *
502
     * @param string $table
503
     * @param string $foreign
504
     * @param string $other
505
     */
506
    protected function performJoin($table, $foreign, $other)
507
    {
508
        $joins = [];
509
        foreach ((array) $this->getBaseQueryBuilder()->joins as $key => $join) {
510
            $joins[] = $join->table;
511
        }
512
513
        if (!in_array($table, $joins)) {
514
            $this->getBaseQueryBuilder()->leftJoin($table, $foreign, '=', $other);
0 ignored issues
show
Bug introduced by
The method leftJoin 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...
515
        }
516
    }
517
518
    /**
519
     * Get NULLS LAST SQL.
520
     *
521
     * @param  string $column
522
     * @param  string $direction
523
     * @return string
524
     */
525
    protected function getNullsLastSql($column, $direction)
526
    {
527
        $sql = $this->config()->get('datatables.nulls_last_sql', '%s %s NULLS LAST');
528
529
        return sprintf($sql, $column, $direction);
530
    }
531
532
    /**
533
     * Perform column search.
534
     *
535
     * @return void
536
     */
537
    public function columnSearch()
538
    {
539
        $columns = $this->request->columns();
540
541
        foreach ($columns as $index => $column) {
542
            if (!$this->request->isColumnSearchable($index)) {
543
                continue;
544
            }
545
546
            $column = $this->getColumnName($index);
547
548
            if ($this->hasCustomFilter($column)) {
549
                $keyword  = $this->getColumnSearchKeyword($index, $raw = true);
550
                $this->applyFilterColumn($this->query, $column, $keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getColumnSearchKe...rd($index, $raw = true) on line 549 can also be of type array; however, Yajra\Datatables\Engines...ne::applyFilterColumn() 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...
551
            } else {
552
                if (count(explode('.', $column)) > 1) {
553
                    $eagerLoads     = $this->getEagerLoads();
554
                    $parts          = explode('.', $column);
555
                    $relationColumn = array_pop($parts);
556
                    $relation       = implode('.', $parts);
557
                    if (in_array($relation, $eagerLoads)) {
558
                        $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
559
                    }
560
                }
561
562
                $keyword = $this->getColumnSearchKeyword($index);
563
                $this->compileColumnSearch($index, $column, $keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getColumnSearchKeyword($index) on line 562 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...
564
            }
565
566
            $this->isFilterApplied = true;
567
        }
568
    }
569
570
    /**
571
     * Get column keyword to use for search.
572
     *
573
     * @param int  $i
574
     * @param bool $raw
575
     * @return string
576
     */
577
    protected function getColumnSearchKeyword($i, $raw = false)
578
    {
579
        $keyword = $this->request->columnKeyword($i);
580
        if ($raw || $this->request->isRegex($i)) {
581
            return $keyword;
582
        }
583
584
        return $this->setupKeyword($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->columnKeyword($i) on line 579 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...
585
    }
586
587
    /**
588
     * Compile queries for column search.
589
     *
590
     * @param int    $i
591
     * @param string $column
592
     * @param string $keyword
593
     */
594
    protected function compileColumnSearch($i, $column, $keyword)
595
    {
596
        if ($this->request->isRegex($i)) {
597
            $column = strstr($column, '(') ? $this->connection->raw($column) : $column;
598
            $this->regexColumnSearch($column, $keyword);
599
        } else {
600
            $this->compileQuerySearch($this->query, $column, $keyword, '');
601
        }
602
    }
603
604
    /**
605
     * Compile regex query column search.
606
     *
607
     * @param mixed  $column
608
     * @param string $keyword
609
     */
610
    protected function regexColumnSearch($column, $keyword)
611
    {
612
        switch ($this->connection->getDriverName()) {
613
            case 'oracle':
614
                $sql = !$this->config()->isCaseInsensitive() ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
615
                break;
616
617
            case 'pgsql':
618
                $sql = !$this->config()->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? ';
619
                break;
620
621
            default:
622
                $sql     = !$this->config()->isCaseInsensitive() ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
623
                $keyword = Str::lower($keyword);
624
        }
625
626
        $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...
627
    }
628
629
    /**
630
     * Perform pagination.
631
     *
632
     * @return void
633
     */
634
    public function paging()
635
    {
636
        $this->query->skip($this->request->input('start'))
0 ignored issues
show
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...
637
                    ->take((int) $this->request->input('length') > 0 ? $this->request->input('length') : 10);
638
    }
639
640
    /**
641
     * Get paginated results.
642
     *
643
     * @return \Illuminate\Support\Collection
644
     */
645
    public function results()
646
    {
647
        return $this->query->get();
648
    }
649
650
    /**
651
     * Add column in collection.
652
     *
653
     * @param string          $name
654
     * @param string|callable $content
655
     * @param bool|int        $order
656
     * @return \Yajra\Datatables\Engines\BaseEngine|\Yajra\Datatables\Engines\QueryBuilderEngine
657
     */
658
    public function addColumn($name, $content, $order = false)
659
    {
660
        $this->pushToBlacklist($name);
661
662
        return parent::addColumn($name, $content, $order);
663
    }
664
665
    /**
666
     * Get query builder instance.
667
     *
668
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
669
     */
670
    public function getQuery()
671
    {
672
        return $this->query;
673
    }
674
675
    /**
676
     * Append debug parameters on output.
677
     *
678
     * @param  array $output
679
     * @return array
680
     */
681
    protected function showDebugger(array $output)
682
    {
683
        $output['queries'] = $this->connection->getQueryLog();
684
        $output['input']   = $this->request->all();
0 ignored issues
show
Documentation Bug introduced by
The method all does not exist on object<Yajra\Datatables\Request>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
685
686
        return $output;
687
    }
688
}
689