Completed
Push — master ( 0e7834...54777e )
by Arjay
01:58
created

QueryBuilderEngine::addTablePrefix()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 2
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\Datatables\Engines;
4
5
use Closure;
6
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
8
use Illuminate\Database\Eloquent\Relations\MorphToMany;
9
use Illuminate\Database\Query\Builder;
10
use Illuminate\Support\Facades\Config;
11
use Illuminate\Support\Str;
12
use Yajra\Datatables\Helper;
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
     * @param \Illuminate\Database\Query\Builder $builder
25
     * @param \Yajra\Datatables\Request $request
26
     */
27
    public function __construct(Builder $builder, Request $request)
28
    {
29
        $this->query = $builder;
30
        $this->init($request, $builder);
31
    }
32
33
    /**
34
     * Initialize attributes.
35
     *
36
     * @param  \Yajra\Datatables\Request $request
37
     * @param  \Illuminate\Database\Query\Builder $builder
38
     * @param  string $type
39
     */
40
    protected function init($request, $builder, $type = 'builder')
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
41
    {
42
        $this->request    = $request;
43
        $this->query_type = $type;
44
        $this->columns    = $builder->columns;
45
        $this->connection = $builder->getConnection();
46
        $this->prefix     = $this->connection->getTablePrefix();
47
        $this->database   = $this->connection->getDriverName();
48
        if ($this->isDebugging()) {
49
            $this->connection->enableQueryLog();
50
        }
51
    }
52
53
    /**
54
     * Set auto filter off and run your own filter.
55
     * Overrides global search
56
     *
57
     * @param \Closure $callback
58
     * @param bool $globalSearch
59
     * @return $this
60
     */
61
    public function filter(Closure $callback, $globalSearch = false)
62
    {
63
        $this->overrideGlobalSearch($callback, $this->query, $globalSearch);
64
65
        return $this;
66
    }
67
68
    /**
69
     * Organizes works
70
     *
71
     * @param bool $mDataSupport
72
     * @param bool $orderFirst
73
     * @return \Illuminate\Http\JsonResponse
74
     */
75
    public function make($mDataSupport = false, $orderFirst = false)
76
    {
77
        return parent::make($mDataSupport, $orderFirst);
78
    }
79
80
    /**
81
     * Count total items.
82
     *
83
     * @return integer
84
     */
85
    public function totalCount()
86
    {
87
        return $this->totalRecords ? $this->totalRecords : $this->count();
88
    }
89
90
    /**
91
     * Counts current query.
92
     *
93
     * @return int
94
     */
95
    public function count()
96
    {
97
        $myQuery = clone $this->query;
98
        // if its a normal query ( no union, having and distinct word )
99
        // replace the select with static text to improve performance
100
        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...
101
            $row_count = $this->wrap('row_count');
102
            $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...
103
        }
104
105
        // check for select soft deleted records
106
        if (! $this->withTrashed && $this->modelUseSoftDeletes()) {
107
            $myQuery->whereNull($myQuery->getModel()->getQualifiedDeletedAtColumn());
0 ignored issues
show
Bug introduced by
The method getModel does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\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...
Bug introduced by
The method whereNull 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...
108
        }
109
110
        return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table'))
111
                                ->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...
112
    }
113
114
    /**
115
     * Wrap column with DB grammar.
116
     *
117
     * @param string $column
118
     * @return string
119
     */
120
    protected function wrap($column)
121
    {
122
        return $this->connection->getQueryGrammar()->wrap($column);
123
    }
124
125
    /**
126
     * Check if model use SoftDeletes trait
127
     *
128
     * @return boolean
129
     */
130
    private function modelUseSoftDeletes()
131
    {
132
        if ($this->query_type == 'eloquent') {
133
            return in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($this->query->getModel()));
0 ignored issues
show
Bug introduced by
The method getModel does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\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...
134
        }
135
136
        return false;
137
    }
138
139
    /**
140
     * Perform global search.
141
     *
142
     * @return void
143
     */
144
    public function filtering()
145
    {
146
        $this->query->where(
147
            function ($query) {
148
                $globalKeyword = $this->request->keyword();
149
                $queryBuilder  = $this->getQueryBuilder($query);
150
151
                foreach ($this->request->searchableColumnIndex() as $index) {
152
                    $columnName = $this->getColumnName($index);
153
                    if ($this->isBlacklisted($columnName)) {
154
                        continue;
155
                    }
156
157
                    // check if custom column filtering is applied
158
                    if (isset($this->columnDef['filter'][$columnName])) {
159
                        $columnDef = $this->columnDef['filter'][$columnName];
160
                        // check if global search should be applied for the specific column
161
                        $applyGlobalSearch = count($columnDef['parameters']) == 0 || end($columnDef['parameters']) !== false;
162
                        if (! $applyGlobalSearch) {
163
                            continue;
164
                        }
165
166 View Code Duplication
                        if ($columnDef['method'] instanceof Closure) {
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...
167
                            $whereQuery = $queryBuilder->newQuery();
168
                            call_user_func_array($columnDef['method'], [$whereQuery, $globalKeyword]);
169
                            $queryBuilder->addNestedWhereQuery($whereQuery, 'or');
170
                        } else {
171
                            $this->compileColumnQuery(
172
                                $queryBuilder,
173
                                Helper::getOrMethod($columnDef['method']),
174
                                $columnDef['parameters'],
175
                                $columnName,
176
                                $globalKeyword
0 ignored issues
show
Bug introduced by
It seems like $globalKeyword defined by $this->request->keyword() on line 148 can also be of type array; however, Yajra\Datatables\Engines...e::compileColumnQuery() 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...
177
                            );
178
                        }
179 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...
180
                        if (count(explode('.', $columnName)) > 1) {
181
                            $eagerLoads     = $this->getEagerLoads();
182
                            $parts          = explode('.', $columnName);
183
                            $relationColumn = array_pop($parts);
184
                            $relation       = implode('.', $parts);
185
                            if (in_array($relation, $eagerLoads)) {
186
                                $this->compileRelationSearch(
187
                                    $queryBuilder,
188
                                    $relation,
189
                                    $relationColumn,
190
                                    $globalKeyword
0 ignored issues
show
Bug introduced by
It seems like $globalKeyword defined by $this->request->keyword() on line 148 can also be of type array; however, Yajra\Datatables\Engines...compileRelationSearch() 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...
191
                                );
192
                            } else {
193
                                $this->compileQuerySearch($queryBuilder, $columnName, $globalKeyword);
0 ignored issues
show
Bug introduced by
It seems like $globalKeyword defined by $this->request->keyword() on line 148 can also be of type array; however, Yajra\Datatables\Engines...e::compileQuerySearch() 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...
194
                            }
195
                        } else {
196
                            $this->compileQuerySearch($queryBuilder, $columnName, $globalKeyword);
0 ignored issues
show
Bug introduced by
It seems like $globalKeyword defined by $this->request->keyword() on line 148 can also be of type array; however, Yajra\Datatables\Engines...e::compileQuerySearch() 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...
197
                        }
198
                    }
199
200
                    $this->isFilterApplied = true;
201
                }
202
            }
203
        );
204
    }
205
206
    /**
207
     * Perform filter column on selected field.
208
     *
209
     * @param mixed $query
210
     * @param string|Closure $method
211
     * @param mixed $parameters
212
     * @param string $column
213
     * @param string $keyword
214
     */
215
    protected function compileColumnQuery($query, $method, $parameters, $column, $keyword)
216
    {
217
        if (method_exists($query, $method)
218
            && count($parameters) <= with(new \ReflectionMethod($query, $method))->getNumberOfParameters()
219
        ) {
220
            if (Str::contains(Str::lower($method), 'raw')
0 ignored issues
show
Bug introduced by
It seems like $method defined by parameter $method on line 215 can also be of type object<Closure>; however, Illuminate\Support\Str::lower() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
221
                || Str::contains(Str::lower($method), 'exists')
0 ignored issues
show
Bug introduced by
It seems like $method defined by parameter $method on line 215 can also be of type object<Closure>; however, Illuminate\Support\Str::lower() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
222
            ) {
223
                call_user_func_array(
224
                    [$query, $method],
225
                    $this->parameterize($parameters, $keyword)
226
                );
227
            } else {
228
                call_user_func_array(
229
                    [$query, $method],
230
                    $this->parameterize($column, $parameters, $keyword)
231
                );
232
            }
233
        }
234
    }
235
236
    /**
237
     * Build Query Builder Parameters.
238
     *
239
     * @return array
240
     */
241
    protected function parameterize()
242
    {
243
        $args       = func_get_args();
244
        $keyword    = count($args) > 2 ? $args[2] : $args[1];
245
        $parameters = Helper::buildParameters($args);
246
        $parameters = Helper::replacePatternWithKeyword($parameters, $keyword, '$1');
247
248
        return $parameters;
249
    }
250
251
    /**
252
     * Get eager loads keys if eloquent.
253
     *
254
     * @return array
255
     */
256
    protected function getEagerLoads()
257
    {
258
        if ($this->query_type == 'eloquent') {
259
            return array_keys($this->query->getEagerLoads());
0 ignored issues
show
Bug introduced by
The method getEagerLoads does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\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...
260
        }
261
262
        return [];
263
    }
264
265
    /**
266
     * Add relation query on global search.
267
     *
268
     * @param mixed $query
269
     * @param string $relation
270
     * @param string $column
271
     * @param string $keyword
272
     */
273
    protected function compileRelationSearch($query, $relation, $column, $keyword)
274
    {
275
        $myQuery = clone $this->query;
276
277
        /**
278
         * For compile nested relation, we need store all nested relation as array
279
         * and reverse order to apply where query.
280
         * With this method we can create nested sub query with properly relation.
281
         */
282
283
        /**
284
         * Store all relation data that require in next step
285
         */
286
        $relationChunk = [];
287
288
        /**
289
         * Store last eloquent query builder for get next relation.
290
         */
291
        $lastQuery = $query;
292
293
        $relations    = explode('.', $relation);
294
        $lastRelation = end($relations);
295
        foreach ($relations as $relation) {
296
            $relationType = $myQuery->getModel()->{$relation}();
297
            $myQuery->orWhereHas($relation, function ($builder) use (
298
                $column,
299
                $keyword,
300
                $query,
301
                $relationType,
302
                $relation,
303
                $lastRelation,
304
                &$relationChunk,
305
                &$lastQuery
306
            ) {
307
                $builder->select($this->connection->raw('count(1)'));
308
309
                // We will perform search on last relation only.
310
                if ($relation == $lastRelation) {
311
                    $this->compileQuerySearch($builder, $column, $keyword, '');
312
                }
313
314
                // Put require object to next step!!
315
                $relationChunk[$relation] = [
316
                    'builder'      => $builder,
317
                    'relationType' => $relationType,
318
                    'query'        => $lastQuery,
319
                ];
320
321
                // This is trick make sub query.
322
                $lastQuery = $builder;
323
            });
324
325
            // This is trick to make nested relation by pass previous relation to be next query eloquent builder
326
            $myQuery = $relationType;
327
        }
328
329
        /**
330
         * Reverse them all
331
         */
332
        $relationChunk = array_reverse($relationChunk, true);
333
334
        /**
335
         * Create valuable for use in check last relation
336
         */
337
        end($relationChunk);
338
        $lastRelation = key($relationChunk);
339
        reset($relationChunk);
340
341
        /**
342
         * Walking ...
343
         */
344
        foreach ($relationChunk as $relation => $chunk) {
345
            // Prepare variables
346
            $builder      = $chunk['builder'];
347
            $relationType = $chunk['relationType'];
348
            $query        = $chunk['query'];
349
            $builder      = "({$builder->toSql()}) >= 1";
350
351
            // Check if it last relation we will use orWhereRaw
352
            if ($lastRelation == $relation) {
353
                $relationMethod = "orWhereRaw";
354
            } else {
355
                // For case parent relation of nested relation.
356
                // We must use and for properly query and get correct result
357
                $relationMethod = "whereRaw";
358
            }
359
360
            if ($relationType instanceof MorphToMany) {
361
                $query->{$relationMethod}($builder, [$relationType->getMorphClass(), $this->prepareKeyword($keyword)]);
362
            } else {
363
                $query->{$relationMethod}($builder, [$this->prepareKeyword($keyword)]);
364
            }
365
        }
366
    }
367
368
    /**
369
     * Compile query builder where clause depending on configurations.
370
     *
371
     * @param mixed $query
372
     * @param string $column
373
     * @param string $keyword
374
     * @param string $relation
375
     */
376
    protected function compileQuerySearch($query, $column, $keyword, $relation = 'or')
377
    {
378
        $column = $this->addTablePrefix($query, $column);
379
        $column = $this->castColumn($column);
380
        $sql    = $column . ' LIKE ?';
381
382
        if ($this->isCaseInsensitive()) {
383
            $sql = 'LOWER(' . $column . ') LIKE ?';
384
        }
385
386
        $query->{$relation . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]);
387
    }
388
389
    /**
390
     * Patch for fix about ambiguous field.
391
     * Ambiguous field error will appear when query use join table and search with keyword.
392
     *
393
     * @param mixed $query
394
     * @param string $column
395
     * @return string
396
     */
397
    protected function addTablePrefix($query, $column)
398
    {
399
        // Remove column delimiters that appear from DB query.
400
        $column = str_replace(['`', '"', '[', ']'], '', $column);
401
402
        // Check if field does not have a table prefix
403
        if (strpos($column, '.') === false) {
404
            // Alternative method to check instanceof \Illuminate\Database\Eloquent\Builder
405
            if (method_exists($query, 'getQuery')) {
406
                $q = $query->getQuery();
407
            } else {
408
                $q = $query;
409
            }
410
411
            // Get table from query and add it.
412
            $column = $q->from . '.' . $column;
413
        }
414
415
        // Add wrap cover table and field name.
416
        $column = $this->wrap($column);
417
418
        return $column;
419
    }
420
421
    /**
422
     * Wrap a column and cast in pgsql.
423
     *
424
     * @param  string $column
425
     * @return string
426
     */
427
    protected function castColumn($column)
428
    {
429
        $column = $this->wrap($column);
430
        if ($this->database === 'pgsql') {
431
            $column = 'CAST(' . $column . ' as TEXT)';
432
        } elseif ($this->database === 'firebird') {
433
            $column = 'CAST(' . $column . ' as VARCHAR(255))';
434
        }
435
436
        return $column;
437
    }
438
439
    /**
440
     * Prepare search keyword based on configurations.
441
     *
442
     * @param string $keyword
443
     * @return string
444
     */
445
    protected function prepareKeyword($keyword)
446
    {
447
        if ($this->isCaseInsensitive()) {
448
            $keyword = Str::lower($keyword);
449
        }
450
451
        if ($this->isWildcard()) {
452
            $keyword = $this->wildcardLikeString($keyword);
453
        }
454
455
        if ($this->isSmartSearch()) {
456
            $keyword = "%$keyword%";
457
        }
458
459
        return $keyword;
460
    }
461
462
    /**
463
     * Perform column search.
464
     *
465
     * @return void
466
     */
467
    public function columnSearch()
468
    {
469
        $columns = (array) $this->request->input('columns');
470
471
        foreach ($columns as $index => $column) {
472
            if (! $this->request->isColumnSearchable($index)) {
473
                continue;
474
            }
475
476
            $column = $this->getColumnName($index);
477
478
            if (isset($this->columnDef['filter'][$column])) {
479
                $columnDef = $this->columnDef['filter'][$column];
480
                // get a raw keyword (without wildcards)
481
                $keyword = $this->getSearchKeyword($index, true);
482
                $builder = $this->getQueryBuilder();
483
484 View Code Duplication
                if ($columnDef['method'] instanceof Closure) {
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...
485
                    $whereQuery = $builder->newQuery();
486
                    call_user_func_array($columnDef['method'], [$whereQuery, $keyword]);
487
                    $builder->addNestedWhereQuery($whereQuery);
488
                } else {
489
                    $this->compileColumnQuery(
490
                        $builder,
491
                        $columnDef['method'],
492
                        $columnDef['parameters'],
493
                        $column,
494
                        $keyword
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getSearchKeyword($index, true) on line 481 can also be of type array; however, Yajra\Datatables\Engines...e::compileColumnQuery() 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...
495
                    );
496
                }
497 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...
498
                if (count(explode('.', $column)) > 1) {
499
                    $eagerLoads     = $this->getEagerLoads();
500
                    $parts          = explode('.', $column);
501
                    $relationColumn = array_pop($parts);
502
                    $relation       = implode('.', $parts);
503
                    if (in_array($relation, $eagerLoads)) {
504
                        $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
505
                    }
506
                }
507
508
                $keyword = $this->getSearchKeyword($index);
509
                $this->compileColumnSearch($index, $column, $keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->getSearchKeyword($index) on line 508 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...
510
            }
511
512
            $this->isFilterApplied = true;
513
        }
514
    }
515
516
    /**
517
     * Get proper keyword to use for search.
518
     *
519
     * @param int $i
520
     * @param bool $raw
521
     * @return string
522
     */
523
    private function getSearchKeyword($i, $raw = false)
524
    {
525
        $keyword = $this->request->columnKeyword($i);
526
        if ($raw || $this->request->isRegex($i)) {
527
            return $keyword;
528
        }
529
530
        return $this->setupKeyword($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword defined by $this->request->columnKeyword($i) on line 525 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...
531
    }
532
533
    /**
534
     * Join eager loaded relation and get the related column name.
535
     *
536
     * @param string $relation
537
     * @param string $relationColumn
538
     * @return string
539
     */
540
    protected function joinEagerLoadedColumn($relation, $relationColumn)
541
    {
542
        $joins = [];
543
        foreach ((array) $this->getQueryBuilder()->joins as $key => $join) {
544
            $joins[] = $join->table;
545
        }
546
547
        $model = $this->query->getRelation($relation);
0 ignored issues
show
Bug introduced by
The method getRelation does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\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...
548
        if ($model instanceof BelongsToMany) {
549
            $pivot   = $model->getTable();
550
            $pivotPK = $model->getForeignKey();
551
            $pivotFK = $model->getQualifiedParentKeyName();
552
553
            if (! in_array($pivot, $joins)) {
554
                $this->getQueryBuilder()->leftJoin($pivot, $pivotPK, '=', $pivotFK);
555
            }
556
557
            $related = $model->getRelated();
558
            $table   = $related->getTable();
559
            $tablePK = $related->getForeignKey();
560
            $tableFK = $related->getQualifiedKeyName();
561
562
            if (! in_array($table, $joins)) {
563
                $this->getQueryBuilder()->leftJoin($table, $pivot . '.' . $tablePK, '=', $tableFK);
564
            }
565
        } else {
566
            $table = $model->getRelated()->getTable();
567
            if ($model instanceof HasOneOrMany) {
568
                $foreign = $model->getForeignKey();
569
                $other   = $model->getQualifiedParentKeyName();
570
            } else {
571
                $foreign = $model->getQualifiedForeignKey();
572
                $other   = $model->getQualifiedOtherKeyName();
573
            }
574
575
            if (! in_array($table, $joins)) {
576
                $this->getQueryBuilder()->leftJoin($table, $foreign, '=', $other);
577
            }
578
        }
579
580
        $column = $table . '.' . $relationColumn;
581
582
        return $column;
583
    }
584
585
    /**
586
     * Compile queries for column search.
587
     *
588
     * @param int $i
589
     * @param mixed $column
590
     * @param string $keyword
591
     */
592
    protected function compileColumnSearch($i, $column, $keyword)
593
    {
594
        if ($this->request->isRegex($i)) {
595
            $column = strstr($column, '(') ? $this->connection->raw($column) : $column;
596
            $this->regexColumnSearch($column, $keyword);
597
        } else {
598
            $this->compileQuerySearch($this->query, $column, $keyword, '');
599
        }
600
    }
601
602
    /**
603
     * Compile regex query column search.
604
     *
605
     * @param mixed $column
606
     * @param string $keyword
607
     */
608
    protected function regexColumnSearch($column, $keyword)
609
    {
610
        if ($this->isOracleSql()) {
611
            $sql = ! $this->isCaseInsensitive() ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
612
            $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...
613
        } else {
614
            $sql = ! $this->isCaseInsensitive() ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
615
            $this->query->whereRaw($sql, [Str::lower($keyword)]);
616
        }
617
    }
618
619
    /**
620
     * Perform sorting of columns.
621
     *
622
     * @return void
623
     */
624
    public function ordering()
625
    {
626
        if ($this->orderCallback) {
627
            call_user_func($this->orderCallback, $this->getQueryBuilder());
628
629
            return;
630
        }
631
632
        foreach ($this->request->orderableColumns() as $orderable) {
633
            $column = $this->getColumnName($orderable['column'], true);
634
635
            if ($this->isBlacklisted($column)) {
636
                continue;
637
            }
638
639
            if (isset($this->columnDef['order'][$column])) {
640
                $method     = $this->columnDef['order'][$column]['method'];
641
                $parameters = $this->columnDef['order'][$column]['parameters'];
642
                $this->compileColumnQuery(
643
                    $this->getQueryBuilder(),
644
                    $method,
645
                    $parameters,
646
                    $column,
647
                    $orderable['direction']
648
                );
649
            } else {
650
                $valid = 1;
651
                if (count(explode('.', $column)) > 1) {
652
                    $eagerLoads     = $this->getEagerLoads();
653
                    $parts          = explode('.', $column);
654
                    $relationColumn = array_pop($parts);
655
                    $relation       = implode('.', $parts);
656
657
                    if (in_array($relation, $eagerLoads)) {
658
                        $relationship = $this->query->getRelation($relation);
0 ignored issues
show
Bug introduced by
The method getRelation does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Query\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...
659
                        if (! ($relationship instanceof MorphToMany)) {
660
                            $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
661
                        } else {
662
                            $valid = 0;
663
                        }
664
                    }
665
                }
666
667
                if ($valid == 1) {
668
                    if ($this->nullsLast) {
669
                        $this->getQueryBuilder()->orderByRaw($this->getNullsLastSql($column, $orderable['direction']));
670
                    } else {
671
                        $this->getQueryBuilder()->orderBy($column, $orderable['direction']);
672
                    }
673
                }
674
            }
675
        }
676
    }
677
678
    /**
679
     * Get NULLS LAST SQL.
680
     *
681
     * @param  string $column
682
     * @param  string $direction
683
     * @return string
684
     */
685
    protected function getNullsLastSql($column, $direction)
686
    {
687
        $sql = Config::get('datatables.nulls_last_sql', '%s %s NULLS LAST');
688
689
        return sprintf($sql, $column, $direction);
690
    }
691
692
    /**
693
     * Perform pagination
694
     *
695
     * @return void
696
     */
697
    public function paging()
698
    {
699
        $this->query->skip($this->request->input('start'))
0 ignored issues
show
Documentation introduced by
$this->request->input('start') is of type string|array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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...
700
                    ->take((int) $this->request->input('length') > 0 ? $this->request->input('length') : 10);
701
    }
702
703
    /**
704
     * Get results
705
     *
706
     * @return array|static[]
707
     */
708
    public function results()
709
    {
710
        return $this->query->get();
711
    }
712
}
713