Completed
Push — master ( 05fe51...2a9bf3 )
by Arjay
01:59
created

QueryBuilderEngine::hasCustomFilter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
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
        $this->prefix     = $this->connection->getTablePrefix();
55
        $this->database   = $this->connection->getDriverName();
56
        if ($this->isDebugging()) {
57
            $this->connection->enableQueryLog();
58
        }
59
    }
60
61
    /**
62
     * Set auto filter off and run your own filter.
63
     * Overrides global search.
64
     *
65
     * @param callable $callback
66
     * @param bool     $globalSearch
67
     * @return $this
68
     */
69
    public function filter(callable $callback, $globalSearch = false)
70
    {
71
        $this->overrideGlobalSearch($callback, $this->query, $globalSearch);
72
73
        return $this;
74
    }
75
76
    /**
77
     * Perform global search.
78
     *
79
     * @return void
80
     */
81
    public function filtering()
82
    {
83
        $keyword = $this->request->keyword();
84
85
        if ($this->isSmartSearch()) {
86
            $this->smartGlobalSearch($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->keyword() on line 83 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...
87
88
            return;
89
        }
90
91
        $this->globalSearch($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->keyword() on line 83 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...
92
    }
93
94
    /**
95
     * Perform multi-term search by splitting keyword into
96
     * individual words and searches for each of them.
97
     *
98
     * @param string $keyword
99
     */
100
    private function smartGlobalSearch($keyword)
101
    {
102
        $keywords = array_filter(explode(' ', $keyword));
103
104
        foreach ($keywords as $keyword) {
105
            $this->globalSearch($keyword);
106
        }
107
    }
108
109
    /**
110
     * Perform global search for the given keyword.
111
     *
112
     * @param string $keyword
113
     */
114
    private function globalSearch($keyword)
115
    {
116
        $this->query->where(function ($query) use ($keyword) {
117
            $query = $this->getBaseQueryBuilder($query);
118
119
            foreach ($this->request->searchableColumnIndex() as $index) {
120
                $columnName = $this->getColumnName($index);
121
                if ($this->isBlacklisted($columnName) && !$this->hasCustomFilter($columnName)) {
122
                    continue;
123
                }
124
125
                if ($this->hasCustomFilter($columnName)) {
126
                    $callback = $this->columnDef['filter'][$columnName]['method'];
127
                    $builder  = $query->newQuery();
0 ignored issues
show
Bug introduced by
The method newQuery 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...
128
                    $callback($builder, $keyword);
129
                    $query->addNestedWhereQuery($builder, 'or');
0 ignored issues
show
Bug introduced by
The method addNestedWhereQuery 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...
130
                } else {
131 View Code Duplication
                    if (count(explode('.', $columnName)) > 1) {
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...
132
                        $eagerLoads     = $this->getEagerLoads();
133
                        $parts          = explode('.', $columnName);
134
                        $relationColumn = array_pop($parts);
135
                        $relation       = implode('.', $parts);
136
                        if (in_array($relation, $eagerLoads)) {
137
                            $this->compileRelationSearch(
138
                                $query,
139
                                $relation,
140
                                $relationColumn,
141
                                $keyword
142
                            );
143
                        } else {
144
                            $this->compileQuerySearch($query, $columnName, $keyword);
145
                        }
146
                    } else {
147
                        $this->compileQuerySearch($query, $columnName, $keyword);
148
                    }
149
                }
150
151
                $this->isFilterApplied = true;
152
            }
153
        });
154
    }
155
156
    /**
157
     * Get the base query builder instance.
158
     *
159
     * @param mixed $instance
160
     * @return \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
161
     */
162
    protected function getBaseQueryBuilder($instance = null)
163
    {
164
        if (!$instance) {
165
            $instance = $this->query;
166
        }
167
168
        if ($instance instanceof EloquentBuilder) {
169
            return $instance->getQuery();
170
        }
171
172
        return $instance;
173
    }
174
175
    /**
176
     * Check if column has custom filter handler.
177
     *
178
     * @param  string $columnName
179
     * @return bool
180
     */
181
    public function hasCustomFilter($columnName)
182
    {
183
        return isset($this->columnDef['filter'][$columnName]);
184
    }
185
186
    /**
187
     * Get eager loads keys if eloquent.
188
     *
189
     * @return array
190
     */
191
    protected function getEagerLoads()
192
    {
193
        if ($this->query instanceof EloquentBuilder) {
194
            return array_keys($this->query->getEagerLoads());
195
        }
196
197
        return [];
198
    }
199
200
    /**
201
     * Add relation query on global search.
202
     *
203
     * @param mixed  $query
204
     * @param string $relation
205
     * @param string $column
206
     * @param string $keyword
207
     */
208
    protected function compileRelationSearch($query, $relation, $column, $keyword)
209
    {
210
        $myQuery = clone $this->query;
211
212
        /**
213
         * For compile nested relation, we need store all nested relation as array
214
         * and reverse order to apply where query.
215
         * With this method we can create nested sub query with properly relation.
216
         */
217
218
        /**
219
         * Store all relation data that require in next step
220
         */
221
        $relationChunk = [];
222
223
        /**
224
         * Store last eloquent query builder for get next relation.
225
         */
226
        $lastQuery = $query;
227
228
        $relations    = explode('.', $relation);
229
        $lastRelation = end($relations);
230
        foreach ($relations as $relation) {
231
            $relationType = $myQuery->getModel()->{$relation}();
232
            $myQuery->orWhereHas($relation, function ($builder) use (
233
                $column,
234
                $keyword,
235
                $query,
236
                $relationType,
237
                $relation,
238
                $lastRelation,
239
                &$relationChunk,
240
                &$lastQuery
241
            ) {
242
                $builder->select($this->connection->raw('count(1)'));
243
244
                // We will perform search on last relation only.
245
                if ($relation == $lastRelation) {
246
                    $this->compileQuerySearch($builder, $column, $keyword, '');
247
                }
248
249
                // Put require object to next step!!
250
                $relationChunk[$relation] = [
251
                    'builder'      => $builder,
252
                    'relationType' => $relationType,
253
                    'query'        => $lastQuery,
254
                ];
255
256
                // This is trick make sub query.
257
                $lastQuery = $builder;
258
            });
259
260
            // This is trick to make nested relation by pass previous relation to be next query eloquent builder
261
            $myQuery = $relationType;
262
        }
263
264
        /**
265
         * Reverse them all
266
         */
267
        $relationChunk = array_reverse($relationChunk, true);
268
269
        /**
270
         * Create valuable for use in check last relation
271
         */
272
        end($relationChunk);
273
        $lastRelation = key($relationChunk);
274
        reset($relationChunk);
275
276
        /**
277
         * Walking ...
278
         */
279
        foreach ($relationChunk as $relation => $chunk) {
280
            // Prepare variables
281
            $builder  = $chunk['builder'];
282
            $query    = $chunk['query'];
283
            $bindings = $builder->getBindings();
284
            $builder  = "({$builder->toSql()}) >= 1";
285
286
            // Check if it last relation we will use orWhereRaw
287
            if ($lastRelation == $relation) {
288
                $relationMethod = "orWhereRaw";
289
            } else {
290
                // For case parent relation of nested relation.
291
                // We must use and for properly query and get correct result
292
                $relationMethod = "whereRaw";
293
            }
294
295
            $query->{$relationMethod}($builder, $bindings);
296
        }
297
    }
298
299
    /**
300
     * Compile query builder where clause depending on configurations.
301
     *
302
     * @param mixed  $query
303
     * @param string $column
304
     * @param string $keyword
305
     * @param string $relation
306
     */
307
    protected function compileQuerySearch($query, $column, $keyword, $relation = 'or')
308
    {
309
        $column = $this->addTablePrefix($query, $column);
310
        $column = $this->castColumn($column);
311
        $sql    = $column . ' LIKE ?';
312
313
        if ($this->isCaseInsensitive()) {
314
            $sql = 'LOWER(' . $column . ') LIKE ?';
315
        }
316
317
        $query->{$relation . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]);
318
    }
319
320
    /**
321
     * Patch for fix about ambiguous field.
322
     * Ambiguous field error will appear when query use join table and search with keyword.
323
     *
324
     * @param mixed  $query
325
     * @param string $column
326
     * @return string
327
     */
328
    protected function addTablePrefix($query, $column)
329
    {
330
        // Check if field does not have a table prefix
331
        if (strpos($column, '.') === false) {
332
            // Alternative method to check instanceof \Illuminate\Database\Eloquent\Builder
333
            if (method_exists($query, 'getQuery')) {
334
                $q = $query->getQuery();
335
            } else {
336
                $q = $query;
337
            }
338
339
            if (!$q->from instanceof Expression) {
340
                // Get table from query and add it.
341
                $column = $q->from . '.' . $column;
342
            }
343
        }
344
345
        return $this->wrap($column);
346
    }
347
348
    /**
349
     * Wrap column with DB grammar.
350
     *
351
     * @param string $column
352
     * @return string
353
     */
354
    protected function wrap($column)
355
    {
356
        return $this->connection->getQueryGrammar()->wrap($column);
357
    }
358
359
    /**
360
     * Wrap a column and cast based on database driver.
361
     *
362
     * @param  string $column
363
     * @return string
364
     */
365
    protected function castColumn($column)
366
    {
367
        if ($this->database === 'pgsql') {
368
            $column = 'CAST(' . $column . ' as TEXT)';
369
        } elseif ($this->database === 'firebird') {
370
            $column = 'CAST(' . $column . ' as VARCHAR(255))';
371
        }
372
373
        return $column;
374
    }
375
376
    /**
377
     * Prepare search keyword based on configurations.
378
     *
379
     * @param string $keyword
380
     * @return string
381
     */
382
    protected function prepareKeyword($keyword)
383
    {
384
        if ($this->isCaseInsensitive()) {
385
            $keyword = Str::lower($keyword);
386
        }
387
388
        if ($this->isWildcard()) {
389
            $keyword = $this->wildcardLikeString($keyword);
390
        }
391
392
        if ($this->isSmartSearch()) {
393
            $keyword = "%$keyword%";
394
        }
395
396
        return $keyword;
397
    }
398
399
    /**
400
     * Organizes works.
401
     *
402
     * @param bool $mDataSupport
403
     * @return \Illuminate\Http\JsonResponse
404
     * @throws \Exception
405
     */
406
    public function make($mDataSupport = false)
407
    {
408
        try {
409
            $this->totalRecords = $this->totalCount();
410
411
            if ($this->totalRecords) {
412
                $this->filterRecords();
413
                $this->ordering();
414
                $this->paginate();
415
            }
416
417
            $data = $this->transform($this->getProcessedData($mDataSupport));
418
419
            return $this->render($data);
420
        } catch (\Exception $exception) {
421
            return $this->errorResponse($exception);
422
        }
423
    }
424
425
    /**
426
     * Count total items.
427
     *
428
     * @return integer
429
     */
430
    public function totalCount()
431
    {
432
        return $this->totalRecords ? $this->totalRecords : $this->count();
433
    }
434
435
    /**
436
     * Counts current query.
437
     *
438
     * @return int
439
     */
440
    public function count()
441
    {
442
        $myQuery = clone $this->query;
443
        // if its a normal query ( no union, having and distinct word )
444
        // replace the select with static text to improve performance
445 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...
446
            $row_count = $this->wrap('row_count');
447
            $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...
448
        }
449
450
        return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table'))
451
                                ->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...
452
    }
453
454
    /**
455
     * Perform sorting of columns.
456
     *
457
     * @return void
458
     */
459
    public function ordering()
460
    {
461
        if ($this->orderCallback) {
462
            call_user_func($this->orderCallback, $this->getBaseQueryBuilder());
463
464
            return;
465
        }
466
467
        foreach ($this->request->orderableColumns() as $orderable) {
468
            $column = $this->getColumnName($orderable['column'], true);
469
470
            if ($this->isBlacklisted($column) && !$this->hasCustomOrder($column)) {
471
                continue;
472
            }
473
474
            if ($this->hasCustomOrder($column)) {
475
                $sql      = $this->columnDef['order'][$column]['sql'];
476
                $sql      = str_replace('$1', $orderable['direction'], $sql);
477
                $bindings = $this->columnDef['order'][$column]['bindings'];
478
                $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...
479
            } else {
480
                $valid = 1;
481
                if (count(explode('.', $column)) > 1) {
482
                    $eagerLoads     = $this->getEagerLoads();
483
                    $parts          = explode('.', $column);
484
                    $relationColumn = array_pop($parts);
485
                    $relation       = implode('.', $parts);
486
487
                    if (in_array($relation, $eagerLoads)) {
488
                        // Loop for nested relations
489
                        // This code is check morph many or not.
490
                        // If one of nested relation is MorphToMany
491
                        // we will call joinEagerLoadedColumn.
492
                        $lastQuery     = $this->query;
493
                        $isMorphToMany = false;
494
                        foreach (explode('.', $relation) as $eachRelation) {
495
                            $relationship = $lastQuery->getRelation($eachRelation);
496
                            if (!($relationship instanceof MorphToMany)) {
497
                                $isMorphToMany = true;
498
                            }
499
                            $lastQuery = $relationship;
500
                        }
501
                        if ($isMorphToMany) {
502
                            $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
503
                        } else {
504
                            $valid = 0;
505
                        }
506
                    }
507
                }
508
509
                if ($valid == 1) {
510
                    if ($this->nullsLast) {
511
                        $this->getBaseQueryBuilder()->orderByRaw($this->getNullsLastSql($column,
512
                            $orderable['direction']));
513
                    } else {
514
                        $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...
515
                    }
516
                }
517
            }
518
        }
519
    }
520
521
    /**
522
     * Check if column has custom sort handler.
523
     *
524
     * @param string $column
525
     * @return bool
526
     */
527
    protected function hasCustomOrder($column)
528
    {
529
        return isset($this->columnDef['order'][$column]);
530
    }
531
532
    /**
533
     * Join eager loaded relation and get the related column name.
534
     *
535
     * @param string $relation
536
     * @param string $relationColumn
537
     * @return string
538
     */
539
    protected function joinEagerLoadedColumn($relation, $relationColumn)
540
    {
541
        $lastQuery = $this->query;
542
        foreach (explode('.', $relation) as $eachRelation) {
543
            $model = $lastQuery->getRelation($eachRelation);
544
            switch (true) {
545
                case $model instanceof BelongsToMany:
546
                    $pivot   = $model->getTable();
547
                    $pivotPK = $model->getExistenceCompareKey();
548
                    $pivotFK = $model->getQualifiedParentKeyName();
549
                    $this->performJoin($pivot, $pivotPK, $pivotFK);
550
551
                    $related = $model->getRelated();
552
                    $table   = $related->getTable();
553
                    $tablePK = $related->getForeignKey();
554
                    $foreign = $pivot . '.' . $tablePK;
555
                    $other   = $related->getQualifiedKeyName();
556
557
                    $lastQuery->addSelect($table . '.' . $relationColumn);
558
                    $this->performJoin($table, $foreign, $other);
559
560
                    break;
561
562
                case $model instanceof HasOneOrMany:
563
                    $table   = $model->getRelated()->getTable();
564
                    $foreign = $model->getQualifiedForeignKeyName();
565
                    $other   = $model->getQualifiedParentKeyName();
566
                    break;
567
568
                case $model instanceof BelongsTo:
569
                    $table   = $model->getRelated()->getTable();
570
                    $foreign = $model->getQualifiedForeignKey();
571
                    $other   = $model->getQualifiedOwnerKeyName();
572
                    break;
573
574
                default:
575
                    $table = $model->getRelated()->getTable();
576
                    if ($model instanceof HasOneOrMany) {
577
                        $foreign = $model->getForeignKey();
0 ignored issues
show
Bug introduced by
The method getForeignKey() does not exist on Illuminate\Database\Eloq...\Relations\HasOneOrMany. Did you maybe mean getForeignKeyName()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
578
                        $other   = $model->getQualifiedParentKeyName();
579
                    } else {
580
                        $foreign = $model->getQualifiedForeignKey();
581
                        $other   = $model->getQualifiedOtherKeyName();
582
                    }
583
            }
584
            $this->performJoin($table, $foreign, $other);
585
            $lastQuery = $model->getQuery();
586
        }
587
588
        return $table . '.' . $relationColumn;
0 ignored issues
show
Bug introduced by
The variable $table does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
589
    }
590
591
    /**
592
     * Perform join query.
593
     *
594
     * @param string $table
595
     * @param string $foreign
596
     * @param string $other
597
     */
598
    protected function performJoin($table, $foreign, $other)
599
    {
600
        $joins = [];
601
        foreach ((array) $this->getBaseQueryBuilder()->joins as $key => $join) {
602
            $joins[] = $join->table;
603
        }
604
605
        if (!in_array($table, $joins)) {
606
            $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...
607
        }
608
    }
609
610
    /**
611
     * Get NULLS LAST SQL.
612
     *
613
     * @param  string $column
614
     * @param  string $direction
615
     * @return string
616
     */
617
    protected function getNullsLastSql($column, $direction)
618
    {
619
        $sql = config('datatables.nulls_last_sql', '%s %s NULLS LAST');
620
621
        return sprintf($sql, $column, $direction);
622
    }
623
624
    /**
625
     * Perform column search.
626
     *
627
     * @return void
628
     */
629
    public function columnSearch()
630
    {
631
        $columns = $this->request->columns();
632
633
        foreach ($columns as $index => $column) {
634
            if (!$this->request->isColumnSearchable($index)) {
635
                continue;
636
            }
637
638
            $column = $this->getColumnName($index);
639
640
            if ($this->hasCustomFilter($column)) {
641
                // get a raw keyword (without wildcards)
642
                $keyword  = $this->getColumnSearchKeyword($index, true);
643
                $callback = $this->columnDef['filter'][$column]['method'];
644
                $builder  = $this->query->newQuery();
0 ignored issues
show
Bug introduced by
The method newQuery 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...
645
                $callback($builder, $keyword);
646
                $this->query->addNestedWhereQuery($builder);
0 ignored issues
show
Bug introduced by
The method addNestedWhereQuery 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...
647 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...
648
                if (count(explode('.', $column)) > 1) {
649
                    $eagerLoads     = $this->getEagerLoads();
650
                    $parts          = explode('.', $column);
651
                    $relationColumn = array_pop($parts);
652
                    $relation       = implode('.', $parts);
653
                    if (in_array($relation, $eagerLoads)) {
654
                        $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
655
                    }
656
                }
657
658
                $keyword = $this->getColumnSearchKeyword($index);
659
                $this->compileColumnSearch($index, $column, $keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getColumnSearchKeyword($index) on line 658 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...
660
            }
661
662
            $this->isFilterApplied = true;
663
        }
664
    }
665
666
    /**
667
     * Get column keyword to use for search.
668
     *
669
     * @param int  $i
670
     * @param bool $raw
671
     * @return string
672
     */
673
    protected function getColumnSearchKeyword($i, $raw = false)
674
    {
675
        $keyword = $this->request->columnKeyword($i);
676
        if ($raw || $this->request->isRegex($i)) {
677
            return $keyword;
678
        }
679
680
        return $this->setupKeyword($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->columnKeyword($i) on line 675 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...
681
    }
682
683
    /**
684
     * Compile queries for column search.
685
     *
686
     * @param int    $i
687
     * @param string $column
688
     * @param string $keyword
689
     */
690
    protected function compileColumnSearch($i, $column, $keyword)
691
    {
692
        if ($this->request->isRegex($i)) {
693
            $column = strstr($column, '(') ? $this->connection->raw($column) : $column;
694
            $this->regexColumnSearch($column, $keyword);
695
        } else {
696
            $this->compileQuerySearch($this->query, $column, $keyword, '');
697
        }
698
    }
699
700
    /**
701
     * Compile regex query column search.
702
     *
703
     * @param mixed  $column
704
     * @param string $keyword
705
     */
706
    protected function regexColumnSearch($column, $keyword)
707
    {
708
        if ($this->isOracleSql()) {
709
            $sql = !$this->isCaseInsensitive() ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
710
            $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...
711
        } elseif ($this->database == 'pgsql') {
712
            $sql = !$this->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? ';
713
            $this->query->whereRaw($sql, [$keyword]);
714
        } else {
715
            $sql = !$this->isCaseInsensitive() ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
716
            $this->query->whereRaw($sql, [Str::lower($keyword)]);
717
        }
718
    }
719
720
    /**
721
     * Perform pagination.
722
     *
723
     * @return void
724
     */
725
    public function paging()
726
    {
727
        $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...
728
                    ->take((int) $this->request->input('length') > 0 ? $this->request->input('length') : 10);
729
    }
730
731
    /**
732
     * Get paginated results.
733
     *
734
     * @return \Illuminate\Support\Collection
735
     */
736
    public function results()
737
    {
738
        return $this->query->get();
739
    }
740
741
    /**
742
     * Add column in collection.
743
     *
744
     * @param string          $name
745
     * @param string|callable $content
746
     * @param bool|int        $order
747
     * @return \Yajra\Datatables\Engines\BaseEngine|\Yajra\Datatables\Engines\QueryBuilderEngine
748
     */
749
    public function addColumn($name, $content, $order = false)
750
    {
751
        $this->pushToBlacklist($name);
752
753
        return parent::addColumn($name, $content, $order);
754
    }
755
756
    /**
757
     * Get query builder instance.
758
     *
759
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
760
     */
761
    public function getQuery()
762
    {
763
        return $this->query;
764
    }
765
766
    /**
767
     * Append debug parameters on output.
768
     *
769
     * @param  array $output
770
     * @return array
771
     */
772
    protected function showDebugger(array $output)
773
    {
774
        $output['queries'] = $this->connection->getQueryLog();
775
        $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...
776
777
        return $output;
778
    }
779
}
780