Completed
Push — master ( bfa8d8...3a0338 )
by Arjay
02:05
created

QueryBuilderEngine::addColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 3
dl 0
loc 6
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->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
    public function filtering()
80
    {
81
        $keyword = $this->request->keyword();
82
83
        if ($this->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->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
        // Check if field does not have a table prefix
212
        if (strpos($column, '.') === false) {
213
            // Alternative method to check instanceof \Illuminate\Database\Eloquent\Builder
214
            if (method_exists($query, 'getQuery')) {
215
                $q = $query->getQuery();
216
            } else {
217
                $q = $query;
218
            }
219
220
            if (!$q->from instanceof Expression) {
221
                // Get table from query and add it.
222
                $column = $q->from . '.' . $column;
223
            }
224
        }
225
226
        return $this->wrap($column);
227
    }
228
229
    /**
230
     * Wrap column with DB grammar.
231
     *
232
     * @param string $column
233
     * @return string
234
     */
235
    protected function wrap($column)
236
    {
237
        return $this->connection->getQueryGrammar()->wrap($column);
238
    }
239
240
    /**
241
     * Wrap a column and cast based on database driver.
242
     *
243
     * @param  string $column
244
     * @return string
245
     */
246
    protected function castColumn($column)
247
    {
248
        $driver = $this->connection->getDriverName();
249
250
        if ($driver === 'pgsql') {
251
            $column = 'CAST(' . $column . ' as TEXT)';
252
        } elseif ($driver === 'firebird') {
253
            $column = 'CAST(' . $column . ' as VARCHAR(255))';
254
        }
255
256
        return $column;
257
    }
258
259
    /**
260
     * Prepare search keyword based on configurations.
261
     *
262
     * @param string $keyword
263
     * @return string
264
     */
265
    protected function prepareKeyword($keyword)
266
    {
267
        if ($this->isCaseInsensitive()) {
268
            $keyword = Str::lower($keyword);
269
        }
270
271
        if ($this->isWildcard()) {
272
            $keyword = $this->wildcardLikeString($keyword);
273
        }
274
275
        if ($this->isSmartSearch()) {
276
            $keyword = "%$keyword%";
277
        }
278
279
        return $keyword;
280
    }
281
282
    /**
283
     * Organizes works.
284
     *
285
     * @param bool $mDataSupport
286
     * @return \Illuminate\Http\JsonResponse
287
     * @throws \Exception
288
     */
289
    public function make($mDataSupport = false)
290
    {
291
        try {
292
            $this->totalRecords = $this->totalCount();
293
294
            if ($this->totalRecords) {
295
                $this->filterRecords();
296
                $this->ordering();
297
                $this->paginate();
298
            }
299
300
            $data = $this->transform($this->getProcessedData($mDataSupport));
301
302
            return $this->render($data);
303
        } catch (\Exception $exception) {
304
            return $this->errorResponse($exception);
305
        }
306
    }
307
308
    /**
309
     * Count total items.
310
     *
311
     * @return integer
312
     */
313
    public function totalCount()
314
    {
315
        return $this->totalRecords ? $this->totalRecords : $this->count();
316
    }
317
318
    /**
319
     * Counts current query.
320
     *
321
     * @return int
322
     */
323
    public function count()
324
    {
325
        $builder = $this->prepareCountQuery();
326
        $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...
327
328
        return $this->connection->table($table)
329
                                ->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...
330
                                ->count();
331
    }
332
333
    /**
334
     * Prepare count query builder.
335
     *
336
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
337
     */
338
    protected function prepareCountQuery()
339
    {
340
        $builder = clone $this->query;
341
342
        if ($this->isComplexQuery($builder)) {
0 ignored issues
show
Bug introduced by
It seems like $builder defined by clone $this->query on line 340 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...
343
            $row_count = $this->wrap('row_count');
344
            $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...
345
        }
346
347
        return $builder;
348
    }
349
350
    /**
351
     * Check if builder query uses complex sql.
352
     *
353
     * @param \Illuminate\Database\Eloquent\Builder $builder
354
     * @return bool
355
     */
356
    protected function isComplexQuery($builder)
357
    {
358
        return !Str::contains(Str::lower($builder->toSql()), ['union', 'having', 'distinct', 'order by', 'group by']);
359
    }
360
361
    /**
362
     * Perform sorting of columns.
363
     *
364
     * @return void
365
     */
366
    public function ordering()
367
    {
368
        if ($this->orderCallback) {
369
            call_user_func($this->orderCallback, $this->getBaseQueryBuilder());
370
371
            return;
372
        }
373
374
        foreach ($this->request->orderableColumns() as $orderable) {
375
            $column = $this->getColumnName($orderable['column'], true);
376
377
            if ($this->isBlacklisted($column) && !$this->hasCustomOrder($column)) {
378
                continue;
379
            }
380
381
            if ($this->hasCustomOrder($column)) {
382
                $sql      = $this->columnDef['order'][$column]['sql'];
383
                $sql      = str_replace('$1', $orderable['direction'], $sql);
384
                $bindings = $this->columnDef['order'][$column]['bindings'];
385
                $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...
386
            } else {
387
                $valid = 1;
388
                if (count(explode('.', $column)) > 1) {
389
                    $eagerLoads     = $this->getEagerLoads();
390
                    $parts          = explode('.', $column);
391
                    $relationColumn = array_pop($parts);
392
                    $relation       = implode('.', $parts);
393
394
                    if (in_array($relation, $eagerLoads)) {
395
                        // Loop for nested relations
396
                        // This code is check morph many or not.
397
                        // If one of nested relation is MorphToMany
398
                        // we will call joinEagerLoadedColumn.
399
                        $lastQuery     = $this->query;
400
                        $isMorphToMany = false;
401
                        foreach (explode('.', $relation) as $eachRelation) {
402
                            $relationship = $lastQuery->getRelation($eachRelation);
403
                            if (!($relationship instanceof MorphToMany)) {
404
                                $isMorphToMany = true;
405
                            }
406
                            $lastQuery = $relationship;
407
                        }
408
                        if ($isMorphToMany) {
409
                            $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
410
                        } else {
411
                            $valid = 0;
412
                        }
413
                    }
414
                }
415
416
                if ($valid == 1) {
417
                    if ($this->nullsLast) {
418
                        $this->getBaseQueryBuilder()->orderByRaw($this->getNullsLastSql($column,
419
                            $orderable['direction']));
420
                    } else {
421
                        $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...
422
                    }
423
                }
424
            }
425
        }
426
    }
427
428
    /**
429
     * Check if column has custom sort handler.
430
     *
431
     * @param string $column
432
     * @return bool
433
     */
434
    protected function hasCustomOrder($column)
435
    {
436
        return isset($this->columnDef['order'][$column]);
437
    }
438
439
    /**
440
     * Get eager loads keys if eloquent.
441
     *
442
     * @return array
443
     */
444
    protected function getEagerLoads()
445
    {
446
        if ($this->query instanceof EloquentBuilder) {
447
            return array_keys($this->query->getEagerLoads());
448
        }
449
450
        return [];
451
    }
452
453
    /**
454
     * Join eager loaded relation and get the related column name.
455
     *
456
     * @param string $relation
457
     * @param string $relationColumn
458
     * @return string
459
     */
460
    protected function joinEagerLoadedColumn($relation, $relationColumn)
461
    {
462
        $table = '';
463
        $lastQuery = $this->query;
464
        foreach (explode('.', $relation) as $eachRelation) {
465
            $model = $lastQuery->getRelation($eachRelation);
466
            switch (true) {
467
                case $model instanceof BelongsToMany:
468
                    $pivot   = $model->getTable();
469
                    $pivotPK = $model->getExistenceCompareKey();
470
                    $pivotFK = $model->getQualifiedParentKeyName();
471
                    $this->performJoin($pivot, $pivotPK, $pivotFK);
472
473
                    $related = $model->getRelated();
474
                    $table   = $related->getTable();
475
                    $tablePK = $related->getForeignKey();
476
                    $foreign = $pivot . '.' . $tablePK;
477
                    $other   = $related->getQualifiedKeyName();
478
479
                    $lastQuery->addSelect($table . '.' . $relationColumn);
480
                    $this->performJoin($table, $foreign, $other);
481
482
                    break;
483
484
                case $model instanceof HasOneOrMany:
485
                    $table   = $model->getRelated()->getTable();
486
                    $foreign = $model->getQualifiedForeignKeyName();
487
                    $other   = $model->getQualifiedParentKeyName();
488
                    break;
489
490
                case $model instanceof BelongsTo:
491
                    $table   = $model->getRelated()->getTable();
492
                    $foreign = $model->getQualifiedForeignKey();
493
                    $other   = $model->getQualifiedOwnerKeyName();
494
                    break;
495
496
                default:
497
                    $table   = $model->getRelated()->getTable();
498
                    $foreign = $model->getQualifiedForeignKey();
499
                    $other   = $model->getQualifiedOtherKeyName();
500
            }
501
            $this->performJoin($table, $foreign, $other);
502
            $lastQuery = $model->getQuery();
503
        }
504
505
        return $table . '.' . $relationColumn;
506
    }
507
508
    /**
509
     * Perform join query.
510
     *
511
     * @param string $table
512
     * @param string $foreign
513
     * @param string $other
514
     */
515
    protected function performJoin($table, $foreign, $other)
516
    {
517
        $joins = [];
518
        foreach ((array) $this->getBaseQueryBuilder()->joins as $key => $join) {
519
            $joins[] = $join->table;
520
        }
521
522
        if (!in_array($table, $joins)) {
523
            $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...
524
        }
525
    }
526
527
    /**
528
     * Get NULLS LAST SQL.
529
     *
530
     * @param  string $column
531
     * @param  string $direction
532
     * @return string
533
     */
534
    protected function getNullsLastSql($column, $direction)
535
    {
536
        $sql = config('datatables.nulls_last_sql', '%s %s NULLS LAST');
537
538
        return sprintf($sql, $column, $direction);
539
    }
540
541
    /**
542
     * Perform column search.
543
     *
544
     * @return void
545
     */
546
    public function columnSearch()
547
    {
548
        $columns = $this->request->columns();
549
550
        foreach ($columns as $index => $column) {
551
            if (!$this->request->isColumnSearchable($index)) {
552
                continue;
553
            }
554
555
            $column = $this->getColumnName($index);
556
557
            if ($this->hasCustomFilter($column)) {
558
                $keyword  = $this->getColumnSearchKeyword($index, $raw = true);
559
                $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 558 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...
560
            } else {
561
                if (count(explode('.', $column)) > 1) {
562
                    $eagerLoads     = $this->getEagerLoads();
563
                    $parts          = explode('.', $column);
564
                    $relationColumn = array_pop($parts);
565
                    $relation       = implode('.', $parts);
566
                    if (in_array($relation, $eagerLoads)) {
567
                        $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
568
                    }
569
                }
570
571
                $keyword = $this->getColumnSearchKeyword($index);
572
                $this->compileColumnSearch($index, $column, $keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getColumnSearchKeyword($index) on line 571 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...
573
            }
574
575
            $this->isFilterApplied = true;
576
        }
577
    }
578
579
    /**
580
     * Get column keyword to use for search.
581
     *
582
     * @param int  $i
583
     * @param bool $raw
584
     * @return string
585
     */
586
    protected function getColumnSearchKeyword($i, $raw = false)
587
    {
588
        $keyword = $this->request->columnKeyword($i);
589
        if ($raw || $this->request->isRegex($i)) {
590
            return $keyword;
591
        }
592
593
        return $this->setupKeyword($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->columnKeyword($i) on line 588 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...
594
    }
595
596
    /**
597
     * Compile queries for column search.
598
     *
599
     * @param int    $i
600
     * @param string $column
601
     * @param string $keyword
602
     */
603
    protected function compileColumnSearch($i, $column, $keyword)
604
    {
605
        if ($this->request->isRegex($i)) {
606
            $column = strstr($column, '(') ? $this->connection->raw($column) : $column;
607
            $this->regexColumnSearch($column, $keyword);
608
        } else {
609
            $this->compileQuerySearch($this->query, $column, $keyword, '');
610
        }
611
    }
612
613
    /**
614
     * Compile regex query column search.
615
     *
616
     * @param mixed  $column
617
     * @param string $keyword
618
     */
619
    protected function regexColumnSearch($column, $keyword)
620
    {
621
        switch ($this->connection->getDriverName()) {
622
            case 'oracle':
623
                $sql = !$this->isCaseInsensitive() ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
624
                break;
625
626
            case 'pgsql':
627
                $sql = !$this->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? ';
628
                break;
629
630
            default:
631
                $sql     = !$this->isCaseInsensitive() ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
632
                $keyword = Str::lower($keyword);
633
        }
634
635
        $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...
636
    }
637
638
    /**
639
     * Perform pagination.
640
     *
641
     * @return void
642
     */
643
    public function paging()
644
    {
645
        $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...
646
                    ->take((int) $this->request->input('length') > 0 ? $this->request->input('length') : 10);
647
    }
648
649
    /**
650
     * Get paginated results.
651
     *
652
     * @return \Illuminate\Support\Collection
653
     */
654
    public function results()
655
    {
656
        return $this->query->get();
657
    }
658
659
    /**
660
     * Add column in collection.
661
     *
662
     * @param string          $name
663
     * @param string|callable $content
664
     * @param bool|int        $order
665
     * @return \Yajra\Datatables\Engines\BaseEngine|\Yajra\Datatables\Engines\QueryBuilderEngine
666
     */
667
    public function addColumn($name, $content, $order = false)
668
    {
669
        $this->pushToBlacklist($name);
670
671
        return parent::addColumn($name, $content, $order);
672
    }
673
674
    /**
675
     * Get query builder instance.
676
     *
677
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
678
     */
679
    public function getQuery()
680
    {
681
        return $this->query;
682
    }
683
684
    /**
685
     * Append debug parameters on output.
686
     *
687
     * @param  array $output
688
     * @return array
689
     */
690
    protected function showDebugger(array $output)
691
    {
692
        $output['queries'] = $this->connection->getQueryLog();
693
        $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...
694
695
        return $output;
696
    }
697
}
698