Completed
Push — master ( 4cc8d8...e56a52 )
by Arjay
04:43
created

QueryBuilderEngine::filtering()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 23
rs 9.0857
cc 3
eloc 14
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\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
     * Counts current query.
72
     *
73
     * @return int
74
     */
75
    public function count()
76
    {
77
        $myQuery = clone $this->query;
78
        // if its a normal query ( no union, having and distinct word )
79
        // replace the select with static text to improve performance
80
        if (! Str::contains(Str::lower($myQuery->toSql()), ['union', 'having', 'distinct'])) {
81
            $row_count = $this->connection->getQueryGrammar()->wrap('row_count');
82
            $myQuery->select($this->connection->raw("'1' as {$row_count}"));
83
        }
84
85
        return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table'))
86
                                ->setBindings($myQuery->getBindings())->count();
87
    }
88
89
    /**
90
     * @inheritdoc
91
     */
92
    public function filtering()
93
    {
94
        $this->query->where(
95
            function ($query) {
96
                $keyword = $this->setupKeyword($this->request->keyword());
97
                foreach ($this->request->searchableColumnIndex() as $index) {
98
                    $columnName = $this->setupColumnName($index);
99
100
                    if (isset($this->columnDef['filter'][$columnName])) {
101
                        $method     = Helper::getOrMethod($this->columnDef['filter'][$columnName]['method']);
102
                        $parameters = $this->columnDef['filter'][$columnName]['parameters'];
103
                        $this->compileColumnQuery(
104
                            $this->getQueryBuilder($query), $method, $parameters, $columnName, $keyword
105
                        );
106
                    } else {
107
                        $this->compileGlobalSearch($this->getQueryBuilder($query), $columnName, $keyword);
108
                    }
109
110
                    $this->isFilterApplied = true;
111
                }
112
            }
113
        );
114
    }
115
116
    /**
117
     * Perform filter column on selected field.
118
     *
119
     * @param mixed $query
120
     * @param string $method
121
     * @param mixed $parameters
122
     * @param string $column
123
     * @param string $keyword
124
     */
125
    protected function compileColumnQuery($query, $method, $parameters, $column, $keyword)
126
    {
127
        if (method_exists($query, $method)
128
            && count($parameters) <= with(new \ReflectionMethod($query, $method))->getNumberOfParameters()
129
        ) {
130
            if (Str::contains(Str::lower($method), 'raw')
131
                || Str::contains(Str::lower($method), 'exists')
132
            ) {
133
                call_user_func_array(
134
                    [$query, $method],
135
                    $this->parameterize($parameters, $keyword)
136
                );
137
            } else {
138
                call_user_func_array(
139
                    [$query, $method],
140
                    $this->parameterize($column, $parameters, $keyword)
141
                );
142
            }
143
        }
144
    }
145
146
    /**
147
     * Build Query Builder Parameters.
148
     *
149
     * @return array
150
     */
151
    protected function parameterize()
152
    {
153
        $args       = func_get_args();
154
        $keyword    = count($args) > 2 ? $args[2] : $args[1];
155
        $parameters = Helper::buildParameters($args);
156
        $parameters = Helper::replacePatternWithKeyword($parameters, $keyword, '$1');
157
158
        return $parameters;
159
    }
160
161
    /**
162
     * Add a query on global search.
163
     *
164
     * @param mixed $query
165
     * @param string $column
166
     * @param string $keyword
167
     */
168
    protected function compileGlobalSearch($query, $column, $keyword)
169
    {
170
        $column = $this->castColumn($column);
171
        $sql    = $column . ' LIKE ?';
172
        if ($this->isCaseInsensitive()) {
173
            $sql     = 'LOWER(' . $column . ') LIKE ?';
174
            $keyword = Str::lower($keyword);
175
        }
176
177
        $query->orWhereRaw($sql, [$keyword]);
178
    }
179
180
    /**
181
     * Wrap a column and cast in pgsql
182
     *
183
     * @param  string $column
184
     * @return string
185
     */
186
    public function castColumn($column)
187
    {
188
        $column = $this->connection->getQueryGrammar()->wrap($column);
189
        if ($this->database === 'pgsql') {
190
            $column = 'CAST(' . $column . ' as TEXT)';
191
        }
192
193
        return $column;
194
    }
195
196
    /**
197
     * @inheritdoc
198
     */
199
    public function columnSearch()
200
    {
201
        $columns = $this->request->get('columns');
202
        for ($i = 0, $c = count($columns); $i < $c; $i++) {
203
            if ($this->request->isColumnSearchable($i)) {
204
                $column  = $this->setupColumnName($i);
205
                $keyword = $this->getSearchKeyword($i);
206
207
                if (isset($this->columnDef['filter'][$column])) {
208
                    $method     = $this->columnDef['filter'][$column]['method'];
209
                    $parameters = $this->columnDef['filter'][$column]['parameters'];
210
                    $this->compileColumnQuery($this->getQueryBuilder(), $method, $parameters, $column, $keyword);
211
                } else {
212
                    $column = $this->castColumn($column);
213
                    if ($this->isCaseInsensitive()) {
214
                        if ($this->request->isRegex($i)) {
215
                            $this->query->whereRaw('LOWER(' . $column . ') REGEXP ?', [Str::lower($keyword)]);
216
                        } else {
217
                            $this->query->whereRaw('LOWER(' . $column . ') LIKE ?', [Str::lower($keyword)]);
218
                        }
219
                    } else {
220
                        $col = strstr($column, '(') ? $this->connection->raw($column) : $column;
221
                        if ($this->request->isRegex($i)) {
222
                            $this->query->whereRaw($col . ' REGEXP ?', [$keyword]);
223
                        } else {
224
                            $this->query->whereRaw($col . ' LIKE ?', [$keyword]);
225
                        }
226
                    }
227
                }
228
229
                $this->isFilterApplied = true;
230
            }
231
        }
232
    }
233
234
    /**
235
     * Get proper keyword to use for search.
236
     *
237
     * @param int $i
238
     * @return string
239
     */
240
    private function getSearchKeyword($i)
241
    {
242
        if ($this->request->isRegex($i)) {
243
            return $this->request->columnKeyword($i);
244
        }
245
246
        return $this->setupKeyword($this->request->columnKeyword($i));
247
    }
248
249
    /**
250
     * @inheritdoc
251
     */
252
    public function ordering()
253
    {
254
        foreach ($this->request->orderableColumns() as $orderable) {
255
            $r_column = $this->request->input('columns')[$orderable['column']];
256
            $column   = $r_column['name'];
257
            $column   = $column ? $column : $r_column['data'];
258
            //$column = $this->setupColumnName($orderable['column'], true);
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
259
            if (isset($this->columnDef['order'][$column])) {
260
                $method     = $this->columnDef['order'][$column]['method'];
261
                $parameters = $this->columnDef['order'][$column]['parameters'];
262
                $this->compileColumnQuery(
263
                    $this->getQueryBuilder(), $method, $parameters, $column, $orderable['direction']
264
                );
265
            } else {
266
                /**
267
                 * If we perform a select("*"), the ORDER BY clause will look like this:
268
                 * ORDER BY * ASC
269
                 * which causes a query exception
270
                 * The temporary fix is modify `*` column to `id` column
271
                 */
272
                if ($column === '*') {
273
                    $column = 'id';
274
                }
275
                $this->getQueryBuilder()->orderBy($column, $orderable['direction']);
276
            }
277
        }
278
    }
279
280
    /**
281
     * @inheritdoc
282
     */
283
    public function paging()
284
    {
285
        $this->query->skip($this->request['start'])
286
                    ->take((int) $this->request['length'] > 0 ? $this->request['length'] : 10);
287
    }
288
289
    /**
290
     * Get results
291
     *
292
     * @return array|static[]
293
     */
294
    public function results()
295
    {
296
        return $this->query->get();
297
    }
298
}
299