Completed
Push — master ( 618bf6...a8a183 )
by Arjay
01:59
created

QueryBuilderEngine::getQuery()   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
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
    private 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
                    $callback = $this->columnDef['filter'][$columnName]['method'];
125
                    $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...
126
                    $callback($builder, $keyword);
127
                    $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...
128
                } else {
129 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...
130
                        $eagerLoads     = $this->getEagerLoads();
131
                        $parts          = explode('.', $columnName);
132
                        $relationColumn = array_pop($parts);
133
                        $relation       = implode('.', $parts);
134
                        if (in_array($relation, $eagerLoads)) {
135
                            $this->compileRelationSearch(
136
                                $query,
137
                                $relation,
138
                                $relationColumn,
139
                                $keyword
140
                            );
141
                        } else {
142
                            $this->compileQuerySearch($query, $columnName, $keyword);
143
                        }
144
                    } else {
145
                        $this->compileQuerySearch($query, $columnName, $keyword);
146
                    }
147
                }
148
149
                $this->isFilterApplied = true;
150
            }
151
        });
152
    }
153
154
    /**
155
     * Get the base query builder instance.
156
     *
157
     * @param mixed $instance
158
     * @return \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
159
     */
160
    protected function getBaseQueryBuilder($instance = null)
161
    {
162
        if (!$instance) {
163
            $instance = $this->query;
164
        }
165
166
        if ($instance instanceof EloquentBuilder) {
167
            return $instance->getQuery();
168
        }
169
170
        return $instance;
171
    }
172
173
    /**
174
     * Check if column has custom filter handler.
175
     *
176
     * @param  string $columnName
177
     * @return bool
178
     */
179
    public function hasCustomFilter($columnName)
180
    {
181
        return isset($this->columnDef['filter'][$columnName]);
182
    }
183
184
    /**
185
     * Get eager loads keys if eloquent.
186
     *
187
     * @return array
188
     */
189
    protected function getEagerLoads()
190
    {
191
        if ($this->query instanceof EloquentBuilder) {
192
            return array_keys($this->query->getEagerLoads());
193
        }
194
195
        return [];
196
    }
197
198
    /**
199
     * Add relation query on global search.
200
     *
201
     * @param mixed  $query
202
     * @param string $relation
203
     * @param string $column
204
     * @param string $keyword
205
     */
206
    protected function compileRelationSearch($query, $relation, $column, $keyword)
207
    {
208
        $myQuery = clone $this->query;
209
210
        /**
211
         * For compile nested relation, we need store all nested relation as array
212
         * and reverse order to apply where query.
213
         * With this method we can create nested sub query with properly relation.
214
         */
215
216
        /**
217
         * Store all relation data that require in next step
218
         */
219
        $relationChunk = [];
220
221
        /**
222
         * Store last eloquent query builder for get next relation.
223
         */
224
        $lastQuery = $query;
225
226
        $relations    = explode('.', $relation);
227
        $lastRelation = end($relations);
228
        foreach ($relations as $relation) {
229
            $relationType = $myQuery->getModel()->{$relation}();
230
            $myQuery->orWhereHas($relation, function ($builder) use (
231
                $column,
232
                $keyword,
233
                $query,
234
                $relationType,
235
                $relation,
236
                $lastRelation,
237
                &$relationChunk,
238
                &$lastQuery
239
            ) {
240
                $builder->select($this->connection->raw('count(1)'));
241
242
                // We will perform search on last relation only.
243
                if ($relation == $lastRelation) {
244
                    $this->compileQuerySearch($builder, $column, $keyword, '');
245
                }
246
247
                // Put require object to next step!!
248
                $relationChunk[$relation] = [
249
                    'builder'      => $builder,
250
                    'relationType' => $relationType,
251
                    'query'        => $lastQuery,
252
                ];
253
254
                // This is trick make sub query.
255
                $lastQuery = $builder;
256
            });
257
258
            // This is trick to make nested relation by pass previous relation to be next query eloquent builder
259
            $myQuery = $relationType;
260
        }
261
262
        /**
263
         * Reverse them all
264
         */
265
        $relationChunk = array_reverse($relationChunk, true);
266
267
        /**
268
         * Create valuable for use in check last relation
269
         */
270
        end($relationChunk);
271
        $lastRelation = key($relationChunk);
272
        reset($relationChunk);
273
274
        /**
275
         * Walking ...
276
         */
277
        foreach ($relationChunk as $relation => $chunk) {
278
            // Prepare variables
279
            $builder  = $chunk['builder'];
280
            $query    = $chunk['query'];
281
            $bindings = $builder->getBindings();
282
            $builder  = "({$builder->toSql()}) >= 1";
283
284
            // Check if it last relation we will use orWhereRaw
285
            if ($lastRelation == $relation) {
286
                $relationMethod = "orWhereRaw";
287
            } else {
288
                // For case parent relation of nested relation.
289
                // We must use and for properly query and get correct result
290
                $relationMethod = "whereRaw";
291
            }
292
293
            $query->{$relationMethod}($builder, $bindings);
294
        }
295
    }
296
297
    /**
298
     * Compile query builder where clause depending on configurations.
299
     *
300
     * @param mixed  $query
301
     * @param string $column
302
     * @param string $keyword
303
     * @param string $relation
304
     */
305
    protected function compileQuerySearch($query, $column, $keyword, $relation = 'or')
306
    {
307
        $column = $this->addTablePrefix($query, $column);
308
        $column = $this->castColumn($column);
309
        $sql    = $column . ' LIKE ?';
310
311
        if ($this->isCaseInsensitive()) {
312
            $sql = 'LOWER(' . $column . ') LIKE ?';
313
        }
314
315
        $query->{$relation . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]);
316
    }
317
318
    /**
319
     * Patch for fix about ambiguous field.
320
     * Ambiguous field error will appear when query use join table and search with keyword.
321
     *
322
     * @param mixed  $query
323
     * @param string $column
324
     * @return string
325
     */
326
    protected function addTablePrefix($query, $column)
327
    {
328
        // Check if field does not have a table prefix
329
        if (strpos($column, '.') === false) {
330
            // Alternative method to check instanceof \Illuminate\Database\Eloquent\Builder
331
            if (method_exists($query, 'getQuery')) {
332
                $q = $query->getQuery();
333
            } else {
334
                $q = $query;
335
            }
336
337
            if (!$q->from instanceof Expression) {
338
                // Get table from query and add it.
339
                $column = $q->from . '.' . $column;
340
            }
341
        }
342
343
        return $this->wrap($column);
344
    }
345
346
    /**
347
     * Wrap column with DB grammar.
348
     *
349
     * @param string $column
350
     * @return string
351
     */
352
    protected function wrap($column)
353
    {
354
        return $this->connection->getQueryGrammar()->wrap($column);
355
    }
356
357
    /**
358
     * Wrap a column and cast based on database driver.
359
     *
360
     * @param  string $column
361
     * @return string
362
     */
363
    protected function castColumn($column)
364
    {
365
        $driver = $this->connection->getDriverName();
366
367
        if ($driver === 'pgsql') {
368
            $column = 'CAST(' . $column . ' as TEXT)';
369
        } elseif ($driver === '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
        $builder = $this->prepareCountQuery();
443
444
        return $this->connection->table($this->connection->raw('(' . $builder->toSql() . ') count_row_table'))
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...
445
                                ->setBindings($builder->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...
446
    }
447
448
    /**
449
     * Prepare count query builder.
450
     *
451
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
452
     */
453
    protected function prepareCountQuery()
454
    {
455
        $builder = clone $this->query;
456
457
        if ($this->isComplexQuery($builder)) {
0 ignored issues
show
Bug introduced by
It seems like $builder defined by clone $this->query on line 455 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...
458
            $row_count = $this->wrap('row_count');
459
            $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...
460
        }
461
462
        return $builder;
463
    }
464
465
    /**
466
     * Check if builder query uses complex sql.
467
     *
468
     * @param \Illuminate\Database\Eloquent\Builder $builder
469
     * @return bool
470
     */
471
    protected function isComplexQuery($builder)
472
    {
473
        return !Str::contains(Str::lower($builder->toSql()), ['union', 'having', 'distinct', 'order by', 'group by']);
474
    }
475
476
    /**
477
     * Perform sorting of columns.
478
     *
479
     * @return void
480
     */
481
    public function ordering()
482
    {
483
        if ($this->orderCallback) {
484
            call_user_func($this->orderCallback, $this->getBaseQueryBuilder());
485
486
            return;
487
        }
488
489
        foreach ($this->request->orderableColumns() as $orderable) {
490
            $column = $this->getColumnName($orderable['column'], true);
491
492
            if ($this->isBlacklisted($column) && !$this->hasCustomOrder($column)) {
493
                continue;
494
            }
495
496
            if ($this->hasCustomOrder($column)) {
497
                $sql      = $this->columnDef['order'][$column]['sql'];
498
                $sql      = str_replace('$1', $orderable['direction'], $sql);
499
                $bindings = $this->columnDef['order'][$column]['bindings'];
500
                $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...
501
            } else {
502
                $valid = 1;
503
                if (count(explode('.', $column)) > 1) {
504
                    $eagerLoads     = $this->getEagerLoads();
505
                    $parts          = explode('.', $column);
506
                    $relationColumn = array_pop($parts);
507
                    $relation       = implode('.', $parts);
508
509
                    if (in_array($relation, $eagerLoads)) {
510
                        // Loop for nested relations
511
                        // This code is check morph many or not.
512
                        // If one of nested relation is MorphToMany
513
                        // we will call joinEagerLoadedColumn.
514
                        $lastQuery     = $this->query;
515
                        $isMorphToMany = false;
516
                        foreach (explode('.', $relation) as $eachRelation) {
517
                            $relationship = $lastQuery->getRelation($eachRelation);
518
                            if (!($relationship instanceof MorphToMany)) {
519
                                $isMorphToMany = true;
520
                            }
521
                            $lastQuery = $relationship;
522
                        }
523
                        if ($isMorphToMany) {
524
                            $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
525
                        } else {
526
                            $valid = 0;
527
                        }
528
                    }
529
                }
530
531
                if ($valid == 1) {
532
                    if ($this->nullsLast) {
533
                        $this->getBaseQueryBuilder()->orderByRaw($this->getNullsLastSql($column,
534
                            $orderable['direction']));
535
                    } else {
536
                        $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...
537
                    }
538
                }
539
            }
540
        }
541
    }
542
543
    /**
544
     * Check if column has custom sort handler.
545
     *
546
     * @param string $column
547
     * @return bool
548
     */
549
    protected function hasCustomOrder($column)
550
    {
551
        return isset($this->columnDef['order'][$column]);
552
    }
553
554
    /**
555
     * Join eager loaded relation and get the related column name.
556
     *
557
     * @param string $relation
558
     * @param string $relationColumn
559
     * @return string
560
     */
561
    protected function joinEagerLoadedColumn($relation, $relationColumn)
562
    {
563
        $lastQuery = $this->query;
564
        foreach (explode('.', $relation) as $eachRelation) {
565
            $model = $lastQuery->getRelation($eachRelation);
566
            switch (true) {
567
                case $model instanceof BelongsToMany:
568
                    $pivot   = $model->getTable();
569
                    $pivotPK = $model->getExistenceCompareKey();
570
                    $pivotFK = $model->getQualifiedParentKeyName();
571
                    $this->performJoin($pivot, $pivotPK, $pivotFK);
572
573
                    $related = $model->getRelated();
574
                    $table   = $related->getTable();
575
                    $tablePK = $related->getForeignKey();
576
                    $foreign = $pivot . '.' . $tablePK;
577
                    $other   = $related->getQualifiedKeyName();
578
579
                    $lastQuery->addSelect($table . '.' . $relationColumn);
580
                    $this->performJoin($table, $foreign, $other);
581
582
                    break;
583
584
                case $model instanceof HasOneOrMany:
585
                    $table   = $model->getRelated()->getTable();
586
                    $foreign = $model->getQualifiedForeignKeyName();
587
                    $other   = $model->getQualifiedParentKeyName();
588
                    break;
589
590
                case $model instanceof BelongsTo:
591
                    $table   = $model->getRelated()->getTable();
592
                    $foreign = $model->getQualifiedForeignKey();
593
                    $other   = $model->getQualifiedOwnerKeyName();
594
                    break;
595
596
                default:
597
                    $table   = $model->getRelated()->getTable();
598
                    $foreign = $model->getQualifiedForeignKey();
599
                    $other   = $model->getQualifiedOtherKeyName();
600
            }
601
            $this->performJoin($table, $foreign, $other);
602
            $lastQuery = $model->getQuery();
603
        }
604
605
        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...
606
    }
607
608
    /**
609
     * Perform join query.
610
     *
611
     * @param string $table
612
     * @param string $foreign
613
     * @param string $other
614
     */
615
    protected function performJoin($table, $foreign, $other)
616
    {
617
        $joins = [];
618
        foreach ((array) $this->getBaseQueryBuilder()->joins as $key => $join) {
619
            $joins[] = $join->table;
620
        }
621
622
        if (!in_array($table, $joins)) {
623
            $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...
624
        }
625
    }
626
627
    /**
628
     * Get NULLS LAST SQL.
629
     *
630
     * @param  string $column
631
     * @param  string $direction
632
     * @return string
633
     */
634
    protected function getNullsLastSql($column, $direction)
635
    {
636
        $sql = config('datatables.nulls_last_sql', '%s %s NULLS LAST');
637
638
        return sprintf($sql, $column, $direction);
639
    }
640
641
    /**
642
     * Perform column search.
643
     *
644
     * @return void
645
     */
646
    public function columnSearch()
647
    {
648
        $columns = $this->request->columns();
649
650
        foreach ($columns as $index => $column) {
651
            if (!$this->request->isColumnSearchable($index)) {
652
                continue;
653
            }
654
655
            $column = $this->getColumnName($index);
656
657
            if ($this->hasCustomFilter($column)) {
658
                $keyword  = $this->getColumnSearchKeyword($index, $raw = true);
659
                $callback = $this->columnDef['filter'][$column]['method'];
660
                $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...
661
                $callback($builder, $keyword);
662
                $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...
663 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...
664
                if (count(explode('.', $column)) > 1) {
665
                    $eagerLoads     = $this->getEagerLoads();
666
                    $parts          = explode('.', $column);
667
                    $relationColumn = array_pop($parts);
668
                    $relation       = implode('.', $parts);
669
                    if (in_array($relation, $eagerLoads)) {
670
                        $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
671
                    }
672
                }
673
674
                $keyword = $this->getColumnSearchKeyword($index);
675
                $this->compileColumnSearch($index, $column, $keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getColumnSearchKeyword($index) on line 674 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...
676
            }
677
678
            $this->isFilterApplied = true;
679
        }
680
    }
681
682
    /**
683
     * Get column keyword to use for search.
684
     *
685
     * @param int  $i
686
     * @param bool $raw
687
     * @return string
688
     */
689
    protected function getColumnSearchKeyword($i, $raw = false)
690
    {
691
        $keyword = $this->request->columnKeyword($i);
692
        if ($raw || $this->request->isRegex($i)) {
693
            return $keyword;
694
        }
695
696
        return $this->setupKeyword($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->columnKeyword($i) on line 691 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...
697
    }
698
699
    /**
700
     * Compile queries for column search.
701
     *
702
     * @param int    $i
703
     * @param string $column
704
     * @param string $keyword
705
     */
706
    protected function compileColumnSearch($i, $column, $keyword)
707
    {
708
        if ($this->request->isRegex($i)) {
709
            $column = strstr($column, '(') ? $this->connection->raw($column) : $column;
710
            $this->regexColumnSearch($column, $keyword);
711
        } else {
712
            $this->compileQuerySearch($this->query, $column, $keyword, '');
713
        }
714
    }
715
716
    /**
717
     * Compile regex query column search.
718
     *
719
     * @param mixed  $column
720
     * @param string $keyword
721
     */
722
    protected function regexColumnSearch($column, $keyword)
723
    {
724
        switch ($this->connection->getDriverName()) {
725 View Code Duplication
            case 'oracle':
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...
726
                $sql = !$this->isCaseInsensitive() ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
727
                $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...
728
                break;
729
730 View Code Duplication
            case 'pgsql':
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...
731
                $sql = !$this->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? ';
732
                $this->query->whereRaw($sql, [$keyword]);
733
                break;
734
735
            default:
736
                $sql = !$this->isCaseInsensitive() ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
737
                $this->query->whereRaw($sql, [Str::lower($keyword)]);
738
        }
739
    }
740
741
    /**
742
     * Perform pagination.
743
     *
744
     * @return void
745
     */
746
    public function paging()
747
    {
748
        $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...
749
                    ->take((int) $this->request->input('length') > 0 ? $this->request->input('length') : 10);
750
    }
751
752
    /**
753
     * Get paginated results.
754
     *
755
     * @return \Illuminate\Support\Collection
756
     */
757
    public function results()
758
    {
759
        return $this->query->get();
760
    }
761
762
    /**
763
     * Add column in collection.
764
     *
765
     * @param string          $name
766
     * @param string|callable $content
767
     * @param bool|int        $order
768
     * @return \Yajra\Datatables\Engines\BaseEngine|\Yajra\Datatables\Engines\QueryBuilderEngine
769
     */
770
    public function addColumn($name, $content, $order = false)
771
    {
772
        $this->pushToBlacklist($name);
773
774
        return parent::addColumn($name, $content, $order);
775
    }
776
777
    /**
778
     * Get query builder instance.
779
     *
780
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
781
     */
782
    public function getQuery()
783
    {
784
        return $this->query;
785
    }
786
787
    /**
788
     * Append debug parameters on output.
789
     *
790
     * @param  array $output
791
     * @return array
792
     */
793
    protected function showDebugger(array $output)
794
    {
795
        $output['queries'] = $this->connection->getQueryLog();
796
        $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...
797
798
        return $output;
799
    }
800
}
801