Completed
Push — master ( 2cf285...52af1e )
by Arjay
07:49
created

QueryBuilderEngine::ordering()   C

Complexity

Conditions 13
Paths 16

Size

Total Lines 61
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 38
nc 16
nop 0
dl 0
loc 61
rs 6.2659
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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