Completed
Branch develop (683ecc)
by Arjay
02:21
created

QueryBuilderEngine::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
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\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'])) {
88
            $row_count = $this->connection->getQueryGrammar()->wrap('row_count');
89
            $myQuery->select($this->connection->raw("'1' as {$row_count}"));
90
        }
91
92
        return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table'))
93
                                ->setBindings($myQuery->getBindings())->count();
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());
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) {
218
            $q->where($column, 'like', $keyword);
219
            $sql = $q->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]);
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]);
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(), $method, $parameters, $column, $orderable['direction']
374
                );
375
            } else {
376
                $this->getQueryBuilder()->orderBy($column, $orderable['direction']);
377
            }
378
        }
379
    }
380
381
    /**
382
     * Perform pagination
383
     *
384
     * @return void
385
     */
386
    public function paging()
387
    {
388
        $this->query->skip($this->request['start'])
389
                    ->take((int) $this->request['length'] > 0 ? $this->request['length'] : 10);
390
    }
391
392
    /**
393
     * Get results
394
     *
395
     * @return array|static[]
396
     */
397
    public function results()
398
    {
399
        return $this->query->get();
400
    }
401
}
402