Completed
Push — master ( a794d7...04d8d3 )
by Arjay
02:24
created

QueryBuilderEngine::make()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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\Contracts\DataTableEngineContract;
17
use Yajra\Datatables\Helper;
18
use Yajra\Datatables\Request;
19
20
class QueryBuilderEngine extends BaseEngine implements DataTableEngineContract
21
{
22
    /**
23
     * @param \Illuminate\Database\Query\Builder $builder
24
     * @param \Yajra\Datatables\Request $request
25
     */
26
    public function __construct(Builder $builder, Request $request)
27
    {
28
        $this->query = $builder;
29
        $this->init($request, $builder);
30
    }
31
32
    /**
33
     * Initialize attributes.
34
     *
35
     * @param  \Yajra\Datatables\Request $request
36
     * @param  \Illuminate\Database\Query\Builder $builder
37
     * @param  string $type
38
     */
39
    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...
40
    {
41
        $this->request    = $request;
42
        $this->query_type = $type;
43
        $this->columns    = $builder->columns;
44
        $this->connection = $builder->getConnection();
45
        $this->prefix     = $this->connection->getTablePrefix();
46
        $this->database   = $this->connection->getDriverName();
47
        if ($this->isDebugging()) {
48
            $this->connection->enableQueryLog();
49
        }
50
    }
51
52
    /**
53
     * @inheritdoc
54
     */
55
    public function filter(Closure $callback)
56
    {
57
        $this->overrideGlobalSearch($callback, $this->query);
58
59
        return $this;
60
    }
61
62
    /**
63
     * @inheritdoc
64
     */
65
    public function make($mDataSupport = false, $orderFirst = false)
66
    {
67
        return parent::make($mDataSupport, $orderFirst);
68
    }
69
70
    /**
71
     * @inheritdoc
72
     */
73
    public function totalCount()
74
    {
75
        return $this->count();
76
    }
77
78
    /**
79
     * Counts current query.
80
     *
81
     * @return int
82
     */
83
    public function count()
84
    {
85
        $myQuery = clone $this->query;
86
        // if its a normal query ( no union, having and distinct word )
87
        // replace the select with static text to improve performance
88
        if (! Str::contains(Str::lower($myQuery->toSql()), ['union', 'having', 'distinct', 'order by', 'group by'])) {
89
            $row_count = $this->connection->getQueryGrammar()->wrap('row_count');
90
            $myQuery->select($this->connection->raw("'1' as {$row_count}"));
91
        }
92
93
        return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table'))
94
                                ->setBindings($myQuery->getBindings())->count();
95
    }
96
97
    /**
98
     * @inheritdoc
99
     */
100
    public function filtering()
101
    {
102
        $eagerLoads = $this->getEagerLoads();
103
104
        $this->query->where(
105
            function ($query) use ($eagerLoads) {
106
                $keyword = $this->setupKeyword($this->request->keyword());
107
                foreach ($this->request->searchableColumnIndex() as $index) {
108
                    $columnName = $this->setupColumnName($index);
109
110
                    if (isset($this->columnDef['filter'][$columnName])) {
111
                        $method     = Helper::getOrMethod($this->columnDef['filter'][$columnName]['method']);
112
                        $parameters = $this->columnDef['filter'][$columnName]['parameters'];
113
                        $this->compileColumnQuery(
114
                            $this->getQueryBuilder($query), $method, $parameters, $columnName, $keyword
115
                        );
116
                    } else {
117
                        if (count(explode('.', $columnName)) > 1) {
118
                            $parts          = explode('.', $columnName);
119
                            $relationColumn = array_pop($parts);
120
                            $relation       = implode('.', $parts);
121
                            if (in_array($relation, $eagerLoads)) {
122
                                $this->compileRelationSearch(
123
                                    $this->getQueryBuilder($query),
124
                                    $relation,
125
                                    $relationColumn,
126
                                    $keyword
127
                                );
128
                            } else {
129
                                $this->compileGlobalSearch($this->getQueryBuilder($query), $columnName, $keyword);
130
                            }
131
                        } else {
132
                            $this->compileGlobalSearch($this->getQueryBuilder($query), $columnName, $keyword);
133
                        }
134
                    }
135
136
                    $this->isFilterApplied = true;
137
                }
138
            }
139
        );
140
    }
141
142
    /**
143
     * Get eager loads keys if eloquent.
144
     *
145
     * @return array
146
     */
147
    private function getEagerLoads()
148
    {
149
        if ($this->query_type == 'eloquent') {
150
            return array_keys($this->query->getEagerLoads());
151
        }
152
153
        return [];
154
    }
155
156
    /**
157
     * Perform filter column on selected field.
158
     *
159
     * @param mixed $query
160
     * @param string $method
161
     * @param mixed $parameters
162
     * @param string $column
163
     * @param string $keyword
164
     */
165
    protected function compileColumnQuery($query, $method, $parameters, $column, $keyword)
166
    {
167
        if (method_exists($query, $method)
168
            && count($parameters) <= with(new \ReflectionMethod($query, $method))->getNumberOfParameters()
169
        ) {
170
            if (Str::contains(Str::lower($method), 'raw')
171
                || Str::contains(Str::lower($method), 'exists')
172
            ) {
173
                call_user_func_array(
174
                    [$query, $method],
175
                    $this->parameterize($parameters, $keyword)
176
                );
177
            } else {
178
                call_user_func_array(
179
                    [$query, $method],
180
                    $this->parameterize($column, $parameters, $keyword)
181
                );
182
            }
183
        }
184
    }
185
186
    /**
187
     * Build Query Builder Parameters.
188
     *
189
     * @return array
190
     */
191
    protected function parameterize()
192
    {
193
        $args       = func_get_args();
194
        $keyword    = count($args) > 2 ? $args[2] : $args[1];
195
        $parameters = Helper::buildParameters($args);
196
        $parameters = Helper::replacePatternWithKeyword($parameters, $keyword, '$1');
197
198
        return $parameters;
199
    }
200
201
    /**
202
     * Add relation query on global search.
203
     *
204
     * @param mixed $query
205
     * @param string $column
206
     * @param string $keyword
207
     */
208
    protected function compileRelationSearch($query, $relation, $column, $keyword)
209
    {
210
        $myQuery = clone $this->query;
211
        $myQuery->orWhereHas($relation, function ($q) use ($column, $keyword, $query) {
212
            $q->where($column, 'like', $keyword);
213
            $sql = $q->toSql();
214
            $sql = "($sql) >= 1";
215
            $query->orWhereRaw($sql, [$keyword]);
216
        });
217
    }
218
219
    /**
220
     * Add a query on global search.
221
     *
222
     * @param mixed $query
223
     * @param string $column
224
     * @param string $keyword
225
     */
226
    protected function compileGlobalSearch($query, $column, $keyword)
227
    {
228
        $column = $this->castColumn($column);
229
        $sql    = $column . ' LIKE ?';
230
        if ($this->isCaseInsensitive()) {
231
            $sql     = 'LOWER(' . $column . ') LIKE ?';
232
            $keyword = Str::lower($keyword);
233
        }
234
235
        $query->orWhereRaw($sql, [$keyword]);
236
    }
237
238
    /**
239
     * Wrap a column and cast in pgsql.
240
     *
241
     * @param  string $column
242
     * @return string
243
     */
244
    public function castColumn($column)
245
    {
246
        $column = $this->connection->getQueryGrammar()->wrap($column);
247
        if ($this->database === 'pgsql') {
248
            $column = 'CAST(' . $column . ' as TEXT)';
249
        }
250
251
        return $column;
252
    }
253
254
    /**
255
     * @inheritdoc
256
     */
257
    public function columnSearch()
258
    {
259
        $columns = $this->request->get('columns');
260
        for ($i = 0, $c = count($columns); $i < $c; $i++) {
261
            if ($this->request->isColumnSearchable($i)) {
262
                $column  = $this->setupColumnName($i);
263
                $keyword = $this->getSearchKeyword($i);
264
265
                if (isset($this->columnDef['filter'][$column])) {
266
                    $method     = $this->columnDef['filter'][$column]['method'];
267
                    $parameters = $this->columnDef['filter'][$column]['parameters'];
268
                    $this->compileColumnQuery($this->getQueryBuilder(), $method, $parameters, $column, $keyword);
269
                } else {
270
                    $column = $this->castColumn($column);
271
                    if ($this->isCaseInsensitive()) {
272
                        if ($this->request->isRegex($i)) {
273 View Code Duplication
							if($this->isOracleSql()){
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...
274
								$this->query->whereRaw(' REGEXP_LIKE( LOWER('.$column.') , ?, \'i\' )', [$keyword]);
275
							}else{
276
								$this->query->whereRaw('LOWER(' . $column . ') REGEXP ?', [Str::lower($keyword)]);
277
							}
278
                        } else {
279
                            $this->query->whereRaw('LOWER(' . $column . ') LIKE ?', [Str::lower($keyword)]);
280
                        }
281
                    } else {
282
                        $col = strstr($column, '(') ? $this->connection->raw($column) : $column;
283
                        if ($this->request->isRegex($i)) {
284 View Code Duplication
							if($this->isOracleSql()){
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...
285
								$this->query->whereRaw(' REGEXP_LIKE( '.$col.' , ? )', [$keyword]);
286
							}else{
287
								$this->query->whereRaw($col . ' REGEXP ?', [$keyword]);
288
							}
289
                        } else {
290
                            $this->query->whereRaw($col . ' LIKE ?', [$keyword]);
291
                        }
292
                    }
293
                }
294
295
                $this->isFilterApplied = true;
296
            }
297
        }
298
    }
299
300
    /**
301
     * Get proper keyword to use for search.
302
     *
303
     * @param int $i
304
     * @return string
305
     */
306
    private function getSearchKeyword($i)
307
    {
308
        if ($this->request->isRegex($i)) {
309
            return $this->request->columnKeyword($i);
310
        }
311
312
        return $this->setupKeyword($this->request->columnKeyword($i));
313
    }
314
315
    /**
316
     * @inheritdoc
317
     */
318
    public function ordering()
319
    {
320
        if ($this->orderCallback) {
321
            call_user_func($this->orderCallback, $this->getQueryBuilder());
322
323
            return;
324
        }
325
326
        foreach ($this->request->orderableColumns() as $orderable) {
327
            $column = $this->setupOrderColumn($orderable);
328
            if (isset($this->columnDef['order'][$column])) {
329
                $method     = $this->columnDef['order'][$column]['method'];
330
                $parameters = $this->columnDef['order'][$column]['parameters'];
331
                $this->compileColumnQuery(
332
                    $this->getQueryBuilder(), $method, $parameters, $column, $orderable['direction']
333
                );
334
            } else {
335
                /**
336
                 * If we perform a select("*"), the ORDER BY clause will look like this:
337
                 * ORDER BY * ASC
338
                 * which causes a query exception
339
                 * The temporary fix is modify `*` column to `id` column
340
                 */
341
                if ($column === '*') {
342
                    $column = 'id';
343
                }
344
                $this->getQueryBuilder()->orderBy($column, $orderable['direction']);
345
            }
346
        }
347
    }
348
349
    /**
350
     * Get order by column name.
351
     *
352
     * @param array $orderable
353
     * @return string
354
     */
355
    private function setupOrderColumn(array $orderable)
356
    {
357
        $r_column = $this->request->input('columns')[$orderable['column']];
358
        $column   = isset($r_column['name']) ? $r_column['name'] : $r_column['data'];
359
        if ($column >= 0) {
360
            $column = $this->setupColumnName($orderable['column'], true);
361
362
            return $column;
363
        }
364
365
        return $column;
366
    }
367
368
    /**
369
     * @inheritdoc
370
     */
371
    public function paging()
372
    {
373
        $this->query->skip($this->request['start'])
374
                    ->take((int) $this->request['length'] > 0 ? $this->request['length'] : 10);
375
    }
376
377
    /**
378
     * Get results
379
     *
380
     * @return array|static[]
381
     */
382
    public function results()
383
    {
384
        return $this->query->get();
385
    }
386
}
387