Completed
Push — master ( c75345...2811f7 )
by Arjay
01:40
created

QueryBuilderEngine::compileColumnSearch()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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