Completed
Branch develop (779dea)
by Arjay
02:23
created

QueryBuilderEngine::parameterize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 9
rs 9.6666
cc 2
eloc 6
nc 2
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
        $eagerLoads = $this->getEagerLoads();
104
105
        $this->query->where(
106
            function ($query) use ($eagerLoads) {
107
                $keyword = $this->setupKeyword($this->request->keyword());
108
                foreach ($this->request->searchableColumnIndex() as $index) {
109
                    $columnName = $this->getColumnName($index);
110
111
                    if (isset($this->columnDef['filter'][$columnName])) {
112
                        $method     = Helper::getOrMethod($this->columnDef['filter'][$columnName]['method']);
113
                        $parameters = $this->columnDef['filter'][$columnName]['parameters'];
114
                        $this->compileColumnQuery(
115
                            $this->getQueryBuilder($query),
116
                            $method,
117
                            $parameters,
118
                            $columnName,
119
                            $keyword
120
                        );
121
                    } else {
122
                        if (count(explode('.', $columnName)) > 1) {
123
                            $parts          = explode('.', $columnName);
124
                            $relationColumn = array_pop($parts);
125
                            $relation       = implode('.', $parts);
126
                            if (in_array($relation, $eagerLoads)) {
127
                                $this->compileRelationSearch(
128
                                    $this->getQueryBuilder($query),
129
                                    $relation,
130
                                    $relationColumn,
131
                                    $keyword
132
                                );
133
                            } else {
134
                                $this->compileGlobalSearch($this->getQueryBuilder($query), $columnName, $keyword);
135
                            }
136
                        } else {
137
                            $this->compileGlobalSearch($this->getQueryBuilder($query), $columnName, $keyword);
138
                        }
139
                    }
140
141
                    $this->isFilterApplied = true;
142
                }
143
            }
144
        );
145
    }
146
147
    /**
148
     * Get eager loads keys if eloquent.
149
     *
150
     * @return array
151
     */
152
    private function getEagerLoads()
153
    {
154
        if ($this->query_type == 'eloquent') {
155
            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...
156
        }
157
158
        return [];
159
    }
160
161
    /**
162
     * Perform filter column on selected field.
163
     *
164
     * @param mixed $query
165
     * @param string $method
166
     * @param mixed $parameters
167
     * @param string $column
168
     * @param string $keyword
169
     */
170
    protected function compileColumnQuery($query, $method, $parameters, $column, $keyword)
171
    {
172
        if (method_exists($query, $method)
173
            && count($parameters) <= with(new \ReflectionMethod($query, $method))->getNumberOfParameters()
174
        ) {
175
            if (Str::contains(Str::lower($method), 'raw')
176
                || Str::contains(Str::lower($method), 'exists')
177
            ) {
178
                call_user_func_array(
179
                    [$query, $method],
180
                    $this->parameterize($parameters, $keyword)
181
                );
182
            } else {
183
                call_user_func_array(
184
                    [$query, $method],
185
                    $this->parameterize($column, $parameters, $keyword)
186
                );
187
            }
188
        }
189
    }
190
191
    /**
192
     * Build Query Builder Parameters.
193
     *
194
     * @return array
195
     */
196
    protected function parameterize()
197
    {
198
        $args       = func_get_args();
199
        $keyword    = count($args) > 2 ? $args[2] : $args[1];
200
        $parameters = Helper::buildParameters($args);
201
        $parameters = Helper::replacePatternWithKeyword($parameters, $keyword, '$1');
202
203
        return $parameters;
204
    }
205
206
    /**
207
     * Add relation query on global search.
208
     *
209
     * @param mixed $query
210
     * @param string $relation
211
     * @param string $column
212
     * @param string $keyword
213
     */
214
    protected function compileRelationSearch($query, $relation, $column, $keyword)
215
    {
216
        $myQuery = clone $this->query;
217
        $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...
218
            $sql = $q->select($this->connection->raw('count(1)'))
219
                     ->where($column, 'like', $keyword)
220
                     ->toSql();
221
            $sql = "($sql) >= 1";
222
            $query->orWhereRaw($sql, [$keyword]);
223
        });
224
    }
225
226
    /**
227
     * Add a query on global search.
228
     *
229
     * @param mixed $query
230
     * @param string $column
231
     * @param string $keyword
232
     */
233
    protected function compileGlobalSearch($query, $column, $keyword)
234
    {
235
        $column = $this->castColumn($column);
236
        $sql    = $column . ' LIKE ?';
237
        if ($this->isCaseInsensitive()) {
238
            $sql     = 'LOWER(' . $column . ') LIKE ?';
239
            $keyword = Str::lower($keyword);
240
        }
241
242
        $query->orWhereRaw($sql, [$keyword]);
243
    }
244
245
    /**
246
     * Wrap a column and cast in pgsql.
247
     *
248
     * @param  string $column
249
     * @return string
250
     */
251
    public function castColumn($column)
252
    {
253
        $column = $this->connection->getQueryGrammar()->wrap($column);
254
        if ($this->database === 'pgsql') {
255
            $column = 'CAST(' . $column . ' as TEXT)';
256
        }
257
258
        return $column;
259
    }
260
261
    /**
262
     * Perform column search.
263
     *
264
     * @return void
265
     */
266
    public function columnSearch()
267
    {
268
        $columns = $this->request->get('columns');
269
        for ($i = 0, $c = count($columns); $i < $c; $i++) {
270
            if ($this->request->isColumnSearchable($i)) {
271
                $column  = $this->getColumnName($i);
272
                $keyword = $this->getSearchKeyword($i);
273
274
                if (isset($this->columnDef['filter'][$column])) {
275
                    $method     = $this->columnDef['filter'][$column]['method'];
276
                    $parameters = $this->columnDef['filter'][$column]['parameters'];
277
                    $this->compileColumnQuery($this->getQueryBuilder(), $method, $parameters, $column, $keyword);
278
                } else {
279
                    $column = $this->castColumn($column);
280
                    if ($this->isCaseInsensitive()) {
281
                        $this->compileColumnSearch($i, $column, $keyword, false);
282
                    } else {
283
                        $col = strstr($column, '(') ? $this->connection->raw($column) : $column;
284
                        $this->compileColumnSearch($i, $col, $keyword, true);
285
                    }
286
                }
287
288
                $this->isFilterApplied = true;
289
            }
290
        }
291
    }
292
293
    /**
294
     * Get proper keyword to use for search.
295
     *
296
     * @param int $i
297
     * @return string
298
     */
299
    private function getSearchKeyword($i)
300
    {
301
        if ($this->request->isRegex($i)) {
302
            return $this->request->columnKeyword($i);
303
        }
304
305
        return $this->setupKeyword($this->request->columnKeyword($i));
306
    }
307
308
    /**
309
     * Compile queries for column search.
310
     *
311
     * @param int $i
312
     * @param mixed $column
313
     * @param string $keyword
314
     * @param bool $caseSensitive
315
     */
316
    protected function compileColumnSearch($i, $column, $keyword, $caseSensitive = true)
317
    {
318
        if ($this->request->isRegex($i)) {
319
            $this->regexColumnSearch($column, $keyword, $caseSensitive);
320
        } else {
321
            $sql     = $caseSensitive ? $column . ' LIKE ?' : 'LOWER(' . $column . ') LIKE ?';
322
            $keyword = $caseSensitive ? $keyword : Str::lower($keyword);
323
            $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...
324
        }
325
    }
326
327
    /**
328
     * Compile regex query column search.
329
     *
330
     * @param mixed $column
331
     * @param string $keyword
332
     * @param bool $caseSensitive
333
     */
334
    protected function regexColumnSearch($column, $keyword, $caseSensitive = true)
335
    {
336
        if ($this->isOracleSql()) {
337
            $sql = $caseSensitive ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )';
338
            $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...
339
        } else {
340
            $sql = $caseSensitive ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?';
341
            $this->query->whereRaw($sql, [Str::lower($keyword)]);
342
        }
343
    }
344
345
    /**
346
     * Check if the current sql language is based on oracle syntax.
347
     *
348
     * @return bool
349
     */
350
    protected function isOracleSql()
351
    {
352
        return $this->database === 'oracle';
353
    }
354
355
    /**
356
     * Perform sorting of columns.
357
     *
358
     * @return void
359
     */
360
    public function ordering()
361
    {
362
        if ($this->orderCallback) {
363
            call_user_func($this->orderCallback, $this->getQueryBuilder());
364
365
            return;
366
        }
367
368
        foreach ($this->request->orderableColumns() as $orderable) {
369
            $column = $this->getColumnName($orderable['column'], true);
370
            if (isset($this->columnDef['order'][$column])) {
371
                $method     = $this->columnDef['order'][$column]['method'];
372
                $parameters = $this->columnDef['order'][$column]['parameters'];
373
                $this->compileColumnQuery(
374
                    $this->getQueryBuilder(),
375
                    $method,
376
                    $parameters,
377
                    $column,
378
                    $orderable['direction']
379
                );
380
            } else {
381
                if (count(explode('.', $column)) > 1) {
382
                    $this->orderByEagerLoad($column, $orderable['direction']);
383
                } else {
384
                    $this->getQueryBuilder()->orderBy($column, $orderable['direction']);
385
                }
386
            }
387
        }
388
    }
389
390
    /**
391
     * Sort DataTables using eager load.
392
     *
393
     * @param string $column
394
     * @param string $direction
395
     */
396
    protected function orderByEagerLoad($column, $direction)
397
    {
398
        $eagerLoads     = $this->getEagerLoads();
399
        $parts          = explode('.', $column);
400
        $relationColumn = array_pop($parts);
401
        $relation       = implode('.', $parts);
402
403
        if (in_array($relation, $eagerLoads)) {
404
            $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...
405
            $foreign = $this->query->getRelation($relation)->getQualifiedForeignKey();
406
            $other   = $this->query->getRelation($relation)->getQualifiedOtherKeyName();
407
            $column  = $table . '.' . $relationColumn;
408
409
            $joins = [];
410
            foreach ((array) $this->getQueryBuilder()->joins as $key => $join) {
411
                $joins[] = $join->table;
412
            }
413
414
            if (! in_array($table, $joins)) {
415
                $this->getQueryBuilder()->leftJoin($table, $foreign, '=', $other);
416
            }
417
        }
418
419
        $this->getQueryBuilder()->orderBy($column, $direction);
420
    }
421
422
    /**
423
     * Perform pagination
424
     *
425
     * @return void
426
     */
427
    public function paging()
428
    {
429
        $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...
430
                    ->take((int) $this->request['length'] > 0 ? $this->request['length'] : 10);
431
    }
432
433
    /**
434
     * Get results
435
     *
436
     * @return array|static[]
437
     */
438
    public function results()
439
    {
440
        return $this->query->get();
441
    }
442
}
443