Completed
Push — master ( 5f4c96...da17c4 )
by Arjay
02:43
created

QueryBuilderEngine::joinEagerLoadedColumn()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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