Completed
Push — master ( da17c4...3a135f )
by Arjay
05:23
created

QueryBuilderEngine::paging()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 2
eloc 3
nc 1
nop 0
1
<?php
2
3
namespace Yajra\Datatables\Engines;
4
5
/**
6
 * Laravel Datatables Query Builder Engine
7
 *
8
 * @package  Laravel
9
 * @category Package
10
 * @author   Arjay Angeles <[email protected]>
11
 */
12
13
use Closure;
14
use Illuminate\Database\Query\Builder;
15
use Illuminate\Support\Str;
16
use Yajra\Datatables\Helper;
17
use Yajra\Datatables\Request;
18
19
class QueryBuilderEngine extends BaseEngine
20
{
21
    /**
22
     * @param \Illuminate\Database\Query\Builder $builder
23
     * @param \Yajra\Datatables\Request $request
24
     */
25
    public function __construct(Builder $builder, Request $request)
26
    {
27
        $this->query = $builder;
28
        $this->init($request, $builder);
29
    }
30
31
    /**
32
     * Initialize attributes.
33
     *
34
     * @param  \Yajra\Datatables\Request $request
35
     * @param  \Illuminate\Database\Query\Builder $builder
36
     * @param  string $type
37
     */
38
    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...
39
    {
40
        $this->request    = $request;
41
        $this->query_type = $type;
42
        $this->columns    = $builder->columns;
43
        $this->connection = $builder->getConnection();
44
        $this->prefix     = $this->connection->getTablePrefix();
45
        $this->database   = $this->connection->getDriverName();
46
        if ($this->isDebugging()) {
47
            $this->connection->enableQueryLog();
48
        }
49
    }
50
51
    /**
52
     * @inheritdoc
53
     */
54
    public function filter(Closure $callback)
55
    {
56
        $this->overrideGlobalSearch($callback, $this->query);
57
58
        return $this;
59
    }
60
61
    /**
62
     * @inheritdoc
63
     */
64
    public function make($mDataSupport = false, $orderFirst = false)
65
    {
66
        return parent::make($mDataSupport, $orderFirst);
67
    }
68
69
    /**
70
     * @inheritdoc
71
     */
72
    public function totalCount()
73
    {
74
        return $this->count();
75
    }
76
77
    /**
78
     * Counts current query.
79
     *
80
     * @return int
81
     */
82
    public function count()
83
    {
84
        $myQuery = clone $this->query;
85
        // if its a normal query ( no union, having and distinct word )
86
        // replace the select with static text to improve performance
87
        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...
88
            $row_count = $this->connection->getQueryGrammar()->wrap('row_count');
89
            $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...
90
        }
91
92
        return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table'))
93
                                ->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...
94
    }
95
96
    /**
97
     * Perform global search.
98
     *
99
     * @return void
100
     */
101
    public function filtering()
102
    {
103
        $this->query->where(
104
            function ($query) {
105
                $globalKeyword = $this->setupKeyword($this->request->keyword());
106
                $queryBuilder  = $this->getQueryBuilder($query);
107
108
                foreach ($this->request->searchableColumnIndex() as $index) {
109
                    $columnName = $this->getColumnName($index);
110
                    // check if custom column filtering is applied
111
                    if (isset($this->columnDef['filter'][$columnName])) {
112
                        $columnDef = $this->columnDef['filter'][$columnName];
113
                        // check if global search should be applied for the specific column
114
                        $applyGlobalSearch = count($columnDef['parameters']) == 0 || end($columnDef['parameters']) !== false;
115
                        if (! $applyGlobalSearch) {
116
                            continue;
117
                        }
118
119
                        if ($columnDef['method'] instanceof Closure) {
120
                            $whereQuery = $queryBuilder->newQuery();
121
                            call_user_func_array($columnDef['method'], [$whereQuery, $this->request->keyword()]);
122
                            $queryBuilder->addNestedWhereQuery($whereQuery, 'or');
123
                        } else {
124
                            $this->compileColumnQuery(
125
                                $queryBuilder,
126
                                Helper::getOrMethod($columnDef['method']),
127
                                $columnDef['parameters'],
128
                                $columnName,
129
                                $this->request->keyword()
130
                            );
131
                        }
132
                    } else {
133
                        if (count(explode('.', $columnName)) > 1) {
134
                            $eagerLoads     = $this->getEagerLoads();
135
                            $parts          = explode('.', $columnName);
136
                            $relationColumn = array_pop($parts);
137
                            $relation       = implode('.', $parts);
138
                            if (in_array($relation, $eagerLoads)) {
139
                                $this->compileRelationSearch(
140
                                    $queryBuilder,
141
                                    $relation,
142
                                    $relationColumn,
143
                                    $globalKeyword
144
                                );
145
                            } else {
146
                                $this->compileGlobalSearch($queryBuilder, $columnName, $globalKeyword);
147
                            }
148
                        } else {
149
                            $this->compileGlobalSearch($queryBuilder, $columnName, $globalKeyword);
150
                        }
151
                    }
152
153
                    $this->isFilterApplied = true;
154
                }
155
            }
156
        );
157
    }
158
159
    /**
160
     * Perform filter column on selected field.
161
     *
162
     * @param mixed $query
163
     * @param string|Closure $method
164
     * @param mixed $parameters
165
     * @param string $column
166
     * @param string $keyword
167
     */
168
    protected function compileColumnQuery($query, $method, $parameters, $column, $keyword)
169
    {
170
        if (method_exists($query, $method)
171
            && count($parameters) <= with(new \ReflectionMethod($query, $method))->getNumberOfParameters()
172
        ) {
173
            if (Str::contains(Str::lower($method), 'raw')
0 ignored issues
show
Bug introduced by
It seems like $method defined by parameter $method on line 168 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...
174
                || Str::contains(Str::lower($method), 'exists')
0 ignored issues
show
Bug introduced by
It seems like $method defined by parameter $method on line 168 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...
175
            ) {
176
                call_user_func_array(
177
                    [$query, $method],
178
                    $this->parameterize($parameters, $keyword)
179
                );
180
            } else {
181
                call_user_func_array(
182
                    [$query, $method],
183
                    $this->parameterize($column, $parameters, $keyword)
184
                );
185
            }
186
        }
187
    }
188
189
    /**
190
     * Build Query Builder Parameters.
191
     *
192
     * @return array
193
     */
194
    protected function parameterize()
195
    {
196
        $args       = func_get_args();
197
        $keyword    = count($args) > 2 ? $args[2] : $args[1];
198
        $parameters = Helper::buildParameters($args);
199
        $parameters = Helper::replacePatternWithKeyword($parameters, $keyword, '$1');
200
201
        return $parameters;
202
    }
203
204
    /**
205
     * Get eager loads keys if eloquent.
206
     *
207
     * @return array
208
     */
209
    protected function getEagerLoads()
210
    {
211
        if ($this->query_type == 'eloquent') {
212
            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...
213
        }
214
215
        return [];
216
    }
217
218
    /**
219
     * Add relation query on global search.
220
     *
221
     * @param mixed $query
222
     * @param string $relation
223
     * @param string $column
224
     * @param string $keyword
225
     */
226
    protected function compileRelationSearch($query, $relation, $column, $keyword)
227
    {
228
        $myQuery = clone $this->query;
229
        $myQuery->orWhereHas($relation, function ($q) use ($column, $keyword, $query) {
0 ignored issues
show
Bug introduced by
The method orWhereHas 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...
230
            $sql = $q->select($this->connection->raw('count(1)'))
231
                     ->where($column, 'like', $keyword)
232
                     ->toSql();
233
            $sql = "($sql) >= 1";
234
            $query->orWhereRaw($sql, [$keyword]);
235
        });
236
    }
237
238
    /**
239
     * Add a query on global search.
240
     *
241
     * @param mixed $query
242
     * @param string $column
243
     * @param string $keyword
244
     */
245
    protected function compileGlobalSearch($query, $column, $keyword)
246
    {
247
        $column = $this->castColumn($column);
248
        $sql    = $column . ' LIKE ?';
249
        if ($this->isCaseInsensitive()) {
250
            $sql     = 'LOWER(' . $column . ') LIKE ?';
251
            $keyword = Str::lower($keyword);
252
        }
253
254
        $query->orWhereRaw($sql, [$keyword]);
255
    }
256
257
    /**
258
     * Wrap a column and cast in pgsql.
259
     *
260
     * @param  string $column
261
     * @return string
262
     */
263
    public function castColumn($column)
264
    {
265
        $column = $this->connection->getQueryGrammar()->wrap($column);
266
        if ($this->database === 'pgsql') {
267
            $column = 'CAST(' . $column . ' as TEXT)';
268
        }
269
270
        return $column;
271
    }
272
273
    /**
274
     * Perform column search.
275
     *
276
     * @return void
277
     */
278
    public function columnSearch()
279
    {
280
        $columns = $this->request->get('columns');
281
282
        foreach ($columns as $index => $column) {
283
            if (! $this->request->isColumnSearchable($index)) {
284
                continue;
285
            }
286
287
            $column = $this->getColumnName($index);
288
289
            if (isset($this->columnDef['filter'][$column])) {
290
                $columnDef = $this->columnDef['filter'][$column];
291
                // get a raw keyword (without wildcards)
292
                $keyword = $this->getSearchKeyword($index, true);
293
                $builder = $this->getQueryBuilder();
294
295
                if ($columnDef['method'] instanceof Closure) {
296
                    $whereQuery = $builder->newQuery();
297
                    call_user_func_array($columnDef['method'], [$whereQuery, $keyword]);
298
                    $builder->addNestedWhereQuery($whereQuery);
299
                } else {
300
                    $this->compileColumnQuery(
301
                        $builder,
302
                        $columnDef['method'],
303
                        $columnDef['parameters'],
304
                        $column,
305
                        $keyword
306
                    );
307
                }
308
            } else {
309
                $column  = $this->castColumn($column);
310
                $keyword = $this->getSearchKeyword($index);
311
312
                if ($this->isCaseInsensitive()) {
313
                    $this->compileColumnSearch($index, $column, $keyword, false);
314
                } else {
315
                    $col = strstr($column, '(') ? $this->connection->raw($column) : $column;
316
                    $this->compileColumnSearch($index, $col, $keyword, true);
317
                }
318
            }
319
320
            $this->isFilterApplied = true;
321
        }
322
    }
323
324
    /**
325
     * Get proper keyword to use for search.
326
     *
327
     * @param int $i
328
     * @return string
329
     */
330
    private function getSearchKeyword($i, $raw = false)
331
    {
332
        $keyword = $this->request->columnKeyword($i);
333
        if ($raw || $this->request->isRegex($i)) {
334
            return $keyword;
335
        }
336
337
        return $this->setupKeyword($keyword);
338
    }
339
340
    /**
341
     * Compile queries for column search.
342
     *
343
     * @param int $i
344
     * @param mixed $column
345
     * @param string $keyword
346
     * @param bool $caseSensitive
347
     */
348
    protected function compileColumnSearch($i, $column, $keyword, $caseSensitive = true)
349
    {
350
        if ($this->request->isRegex($i)) {
351
            $this->regexColumnSearch($column, $keyword, $caseSensitive);
352
        } else {
353
            $sql     = $caseSensitive ? $column . ' LIKE ?' : 'LOWER(' . $column . ') LIKE ?';
354
            $keyword = $caseSensitive ? $keyword : Str::lower($keyword);
355
            $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...
356
        }
357
    }
358
359
    /**
360
     * Compile regex query column search.
361
     *
362
     * @param mixed $column
363
     * @param string $keyword
364
     * @param bool $caseSensitive
365
     */
366
    protected function regexColumnSearch($column, $keyword, $caseSensitive = true)
367
    {
368
        if ($this->isOracleSql()) {
369
            $sql = $caseSensitive ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
370
            $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...
371
        } else {
372
            $sql = $caseSensitive ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
373
            $this->query->whereRaw($sql, [Str::lower($keyword)]);
374
        }
375
    }
376
377
    /**
378
     * Check if the current sql language is based on oracle syntax.
379
     *
380
     * @return bool
381
     */
382
    protected function isOracleSql()
383
    {
384
        return $this->database === 'oracle';
385
    }
386
387
    /**
388
     * Perform sorting of columns.
389
     *
390
     * @return void
391
     */
392
    public function ordering()
393
    {
394
        if ($this->orderCallback) {
395
            call_user_func($this->orderCallback, $this->getQueryBuilder());
396
397
            return;
398
        }
399
400
        foreach ($this->request->orderableColumns() as $orderable) {
401
            $column = $this->getColumnName($orderable['column'], true);
402
            if (isset($this->columnDef['order'][$column])) {
403
                $method     = $this->columnDef['order'][$column]['method'];
404
                $parameters = $this->columnDef['order'][$column]['parameters'];
405
                $this->compileColumnQuery(
406
                    $this->getQueryBuilder(),
407
                    $method,
408
                    $parameters,
409
                    $column,
410
                    $orderable['direction']
411
                );
412
            } else {
413
                if (count(explode('.', $column)) > 1) {
414
                    $eagerLoads     = $this->getEagerLoads();
415
                    $parts          = explode('.', $column);
416
                    $relationColumn = array_pop($parts);
417
                    $relation       = implode('.', $parts);
418
419
                    if (in_array($relation, $eagerLoads)) {
420
                        $column = $this->joinEagerLoadedColumn($relation, $relationColumn);
421
                    }
422
                }
423
424
                $this->getQueryBuilder()->orderBy($column, $orderable['direction']);
425
            }
426
        }
427
    }
428
429
    /**
430
     * Join eager loaded relation and get the related column name.
431
     *
432
     * @param string $relation
433
     * @param string $relationColumn
434
     * @return string
435
     */
436
    protected function joinEagerLoadedColumn($relation, $relationColumn)
437
    {
438
        $table   = $this->query->getRelation($relation)->getRelated()->getTable();
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...
439
        $foreign = $this->query->getRelation($relation)->getQualifiedForeignKey();
440
        $other   = $this->query->getRelation($relation)->getQualifiedOtherKeyName();
441
        $column  = $table . '.' . $relationColumn;
442
443
        $joins = [];
444
        foreach ((array) $this->getQueryBuilder()->joins as $key => $join) {
445
            $joins[] = $join->table;
446
        }
447
448
        if (! in_array($table, $joins)) {
449
            $this->getQueryBuilder()
450
                 ->leftJoin($table, $foreign, '=', $other);
451
        }
452
453
        return $column;
454
    }
455
456
    /**
457
     * Perform pagination
458
     *
459
     * @return void
460
     */
461
    public function paging()
462
    {
463
        $this->query->skip($this->request['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...
464
                    ->take((int) $this->request['length'] > 0 ? $this->request['length'] : 10);
465
    }
466
467
    /**
468
     * Get results
469
     *
470
     * @return array|static[]
471
     */
472
    public function results()
473
    {
474
        return $this->query->get();
475
    }
476
}
477