These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Yajra\Datatables\Engines; |
||
4 | |||
5 | use Closure; |
||
6 | use Illuminate\Database\Eloquent\Relations\BelongsToMany; |
||
7 | use Illuminate\Database\Eloquent\Relations\HasOneOrMany; |
||
8 | use Illuminate\Database\Eloquent\Relations\MorphToMany; |
||
9 | use Illuminate\Database\Query\Builder; |
||
10 | use Illuminate\Database\Query\Expression; |
||
11 | use Illuminate\Support\Facades\Config; |
||
12 | use Illuminate\Support\Str; |
||
13 | use Yajra\Datatables\Helper; |
||
14 | use Yajra\Datatables\Request; |
||
15 | |||
16 | /** |
||
17 | * Class QueryBuilderEngine. |
||
18 | * |
||
19 | * @package Yajra\Datatables\Engines |
||
20 | * @author Arjay Angeles <[email protected]> |
||
21 | */ |
||
22 | class QueryBuilderEngine extends BaseEngine |
||
23 | { |
||
24 | /** |
||
25 | * @param \Illuminate\Database\Query\Builder $builder |
||
26 | * @param \Yajra\Datatables\Request $request |
||
27 | */ |
||
28 | public function __construct(Builder $builder, Request $request) |
||
29 | { |
||
30 | $this->query = $builder; |
||
31 | $this->init($request, $builder); |
||
32 | } |
||
33 | |||
34 | /** |
||
35 | * Initialize attributes. |
||
36 | * |
||
37 | * @param \Yajra\Datatables\Request $request |
||
38 | * @param \Illuminate\Database\Query\Builder $builder |
||
39 | * @param string $type |
||
40 | */ |
||
41 | protected function init($request, $builder, $type = 'builder') |
||
42 | { |
||
43 | $this->request = $request; |
||
44 | $this->query_type = $type; |
||
45 | $this->columns = $builder->columns; |
||
46 | $this->connection = $builder->getConnection(); |
||
47 | $this->prefix = $this->connection->getTablePrefix(); |
||
48 | $this->database = $this->connection->getDriverName(); |
||
49 | if ($this->isDebugging()) { |
||
50 | $this->connection->enableQueryLog(); |
||
51 | } |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Set auto filter off and run your own filter. |
||
56 | * Overrides global search |
||
57 | * |
||
58 | * @param \Closure $callback |
||
59 | * @param bool $globalSearch |
||
60 | * @return $this |
||
61 | */ |
||
62 | public function filter(Closure $callback, $globalSearch = false) |
||
63 | { |
||
64 | $this->overrideGlobalSearch($callback, $this->query, $globalSearch); |
||
65 | |||
66 | return $this; |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Organizes works |
||
71 | * |
||
72 | * @param bool $mDataSupport |
||
73 | * @param bool $orderFirst |
||
74 | * @return \Illuminate\Http\JsonResponse |
||
75 | */ |
||
76 | public function make($mDataSupport = false, $orderFirst = false) |
||
77 | { |
||
78 | return parent::make($mDataSupport, $orderFirst); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Count total items. |
||
83 | * |
||
84 | * @return integer |
||
85 | */ |
||
86 | public function totalCount() |
||
87 | { |
||
88 | return $this->totalRecords ? $this->totalRecords : $this->count(); |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * Counts current query. |
||
93 | * |
||
94 | * @return int |
||
95 | */ |
||
96 | public function count() |
||
97 | { |
||
98 | $myQuery = clone $this->query; |
||
99 | // if its a normal query ( no union, having and distinct word ) |
||
100 | // replace the select with static text to improve performance |
||
101 | if (! Str::contains(Str::lower($myQuery->toSql()), ['union', 'having', 'distinct', 'order by', 'group by'])) { |
||
102 | $row_count = $this->wrap('row_count'); |
||
103 | $myQuery->select($this->connection->raw("'1' as {$row_count}")); |
||
104 | } |
||
105 | |||
106 | // check for select soft deleted records |
||
107 | if (! $this->withTrashed && $this->modelUseSoftDeletes()) { |
||
108 | $myQuery->whereNull($myQuery->getModel()->getQualifiedDeletedAtColumn()); |
||
109 | } |
||
110 | |||
111 | return $this->connection->table($this->connection->raw('(' . $myQuery->toSql() . ') count_row_table')) |
||
112 | ->setBindings($myQuery->getBindings())->count(); |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Wrap column with DB grammar. |
||
117 | * |
||
118 | * @param string $column |
||
119 | * @return string |
||
120 | */ |
||
121 | protected function wrap($column) |
||
122 | { |
||
123 | return $this->connection->getQueryGrammar()->wrap($column); |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * Check if model use SoftDeletes trait |
||
128 | * |
||
129 | * @return boolean |
||
130 | */ |
||
131 | private function modelUseSoftDeletes() |
||
132 | { |
||
133 | if ($this->query_type == 'eloquent') { |
||
134 | return in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($this->query->getModel())); |
||
135 | } |
||
136 | |||
137 | return false; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Perform global search. |
||
142 | * |
||
143 | * @return void |
||
144 | */ |
||
145 | public function filtering() |
||
146 | { |
||
147 | $this->query->where( |
||
148 | function ($query) { |
||
149 | $globalKeyword = $this->request->keyword(); |
||
150 | $queryBuilder = $this->getQueryBuilder($query); |
||
151 | |||
152 | foreach ($this->request->searchableColumnIndex() as $index) { |
||
153 | $columnName = $this->getColumnName($index); |
||
154 | if ($this->isBlacklisted($columnName)) { |
||
155 | continue; |
||
156 | } |
||
157 | |||
158 | // check if custom column filtering is applied |
||
159 | if (isset($this->columnDef['filter'][$columnName])) { |
||
160 | $columnDef = $this->columnDef['filter'][$columnName]; |
||
161 | // check if global search should be applied for the specific column |
||
162 | $applyGlobalSearch = count($columnDef['parameters']) == 0 || end($columnDef['parameters']) !== false; |
||
163 | if (! $applyGlobalSearch) { |
||
164 | continue; |
||
165 | } |
||
166 | |||
167 | View Code Duplication | if ($columnDef['method'] instanceof Closure) { |
|
168 | $whereQuery = $queryBuilder->newQuery(); |
||
169 | call_user_func_array($columnDef['method'], [$whereQuery, $globalKeyword]); |
||
170 | $queryBuilder->addNestedWhereQuery($whereQuery, 'or'); |
||
171 | } else { |
||
172 | $this->compileColumnQuery( |
||
173 | $queryBuilder, |
||
174 | Helper::getOrMethod($columnDef['method']), |
||
175 | $columnDef['parameters'], |
||
176 | $columnName, |
||
177 | $globalKeyword |
||
178 | ); |
||
179 | } |
||
180 | View Code Duplication | } else { |
|
181 | if (count(explode('.', $columnName)) > 1) { |
||
182 | $eagerLoads = $this->getEagerLoads(); |
||
183 | $parts = explode('.', $columnName); |
||
184 | $relationColumn = array_pop($parts); |
||
185 | $relation = implode('.', $parts); |
||
186 | if (in_array($relation, $eagerLoads)) { |
||
187 | $this->compileRelationSearch( |
||
188 | $queryBuilder, |
||
189 | $relation, |
||
190 | $relationColumn, |
||
191 | $globalKeyword |
||
192 | ); |
||
193 | } else { |
||
194 | $this->compileQuerySearch($queryBuilder, $columnName, $globalKeyword); |
||
195 | } |
||
196 | } else { |
||
197 | $this->compileQuerySearch($queryBuilder, $columnName, $globalKeyword); |
||
198 | } |
||
199 | } |
||
200 | |||
201 | $this->isFilterApplied = true; |
||
202 | } |
||
203 | } |
||
204 | ); |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Perform filter column on selected field. |
||
209 | * |
||
210 | * @param mixed $query |
||
211 | * @param string|Closure $method |
||
212 | * @param mixed $parameters |
||
213 | * @param string $column |
||
214 | * @param string $keyword |
||
215 | */ |
||
216 | protected function compileColumnQuery($query, $method, $parameters, $column, $keyword) |
||
217 | { |
||
218 | if (method_exists($query, $method) |
||
219 | && count($parameters) <= with(new \ReflectionMethod($query, $method))->getNumberOfParameters() |
||
220 | ) { |
||
221 | if (Str::contains(Str::lower($method), 'raw') |
||
222 | || Str::contains(Str::lower($method), 'exists') |
||
223 | ) { |
||
224 | call_user_func_array( |
||
225 | [$query, $method], |
||
226 | $this->parameterize($parameters, $keyword) |
||
227 | ); |
||
228 | } else { |
||
229 | call_user_func_array( |
||
230 | [$query, $method], |
||
231 | $this->parameterize($column, $parameters, $keyword) |
||
232 | ); |
||
233 | } |
||
234 | } |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Build Query Builder Parameters. |
||
239 | * |
||
240 | * @return array |
||
241 | */ |
||
242 | protected function parameterize() |
||
243 | { |
||
244 | $args = func_get_args(); |
||
245 | $keyword = count($args) > 2 ? $args[2] : $args[1]; |
||
246 | $parameters = Helper::buildParameters($args); |
||
247 | $parameters = Helper::replacePatternWithKeyword($parameters, $keyword, '$1'); |
||
248 | |||
249 | return $parameters; |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Get eager loads keys if eloquent. |
||
254 | * |
||
255 | * @return array |
||
256 | */ |
||
257 | protected function getEagerLoads() |
||
258 | { |
||
259 | if ($this->query_type == 'eloquent') { |
||
260 | return array_keys($this->query->getEagerLoads()); |
||
261 | } |
||
262 | |||
263 | return []; |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * Add relation query on global search. |
||
268 | * |
||
269 | * @param mixed $query |
||
270 | * @param string $relation |
||
271 | * @param string $column |
||
272 | * @param string $keyword |
||
273 | */ |
||
274 | protected function compileRelationSearch($query, $relation, $column, $keyword) |
||
275 | { |
||
276 | $myQuery = clone $this->query; |
||
277 | |||
278 | /** |
||
279 | * For compile nested relation, we need store all nested relation as array |
||
280 | * and reverse order to apply where query. |
||
281 | * With this method we can create nested sub query with properly relation. |
||
282 | */ |
||
283 | |||
284 | /** |
||
285 | * Store all relation data that require in next step |
||
286 | */ |
||
287 | $relationChunk = []; |
||
288 | |||
289 | /** |
||
290 | * Store last eloquent query builder for get next relation. |
||
291 | */ |
||
292 | $lastQuery = $query; |
||
293 | |||
294 | $relations = explode('.', $relation); |
||
295 | $lastRelation = end($relations); |
||
296 | foreach ($relations as $relation) { |
||
297 | $relationType = $myQuery->getModel()->{$relation}(); |
||
298 | $myQuery->orWhereHas($relation, function ($builder) use ( |
||
299 | $column, |
||
300 | $keyword, |
||
301 | $query, |
||
302 | $relationType, |
||
303 | $relation, |
||
304 | $lastRelation, |
||
305 | &$relationChunk, |
||
306 | &$lastQuery |
||
307 | ) { |
||
308 | $builder->select($this->connection->raw('count(1)')); |
||
309 | |||
310 | // We will perform search on last relation only. |
||
311 | if ($relation == $lastRelation) { |
||
312 | $this->compileQuerySearch($builder, $column, $keyword, ''); |
||
313 | } |
||
314 | |||
315 | // Put require object to next step!! |
||
316 | $relationChunk[$relation] = [ |
||
317 | 'builder' => $builder, |
||
318 | 'relationType' => $relationType, |
||
319 | 'query' => $lastQuery, |
||
320 | ]; |
||
321 | |||
322 | // This is trick make sub query. |
||
323 | $lastQuery = $builder; |
||
324 | }); |
||
325 | |||
326 | // This is trick to make nested relation by pass previous relation to be next query eloquent builder |
||
327 | $myQuery = $relationType; |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Reverse them all |
||
332 | */ |
||
333 | $relationChunk = array_reverse($relationChunk, true); |
||
334 | |||
335 | /** |
||
336 | * Create valuable for use in check last relation |
||
337 | */ |
||
338 | end($relationChunk); |
||
339 | $lastRelation = key($relationChunk); |
||
340 | reset($relationChunk); |
||
341 | |||
342 | /** |
||
343 | * Walking ... |
||
344 | */ |
||
345 | foreach ($relationChunk as $relation => $chunk) { |
||
346 | // Prepare variables |
||
347 | $builder = $chunk['builder']; |
||
348 | $relationType = $chunk['relationType']; |
||
349 | $query = $chunk['query']; |
||
350 | $builder = "({$builder->toSql()}) >= 1"; |
||
351 | |||
352 | // Check if it last relation we will use orWhereRaw |
||
353 | if ($lastRelation == $relation) { |
||
354 | $relationMethod = "orWhereRaw"; |
||
355 | } else { |
||
356 | // For case parent relation of nested relation. |
||
357 | // We must use and for properly query and get correct result |
||
358 | $relationMethod = "whereRaw"; |
||
359 | } |
||
360 | |||
361 | if ($relationType instanceof MorphToMany) { |
||
362 | $query->{$relationMethod}($builder, [$relationType->getMorphClass(), $this->prepareKeyword($keyword)]); |
||
363 | } else { |
||
364 | $query->{$relationMethod}($builder, [$this->prepareKeyword($keyword)]); |
||
365 | } |
||
366 | } |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * Compile query builder where clause depending on configurations. |
||
371 | * |
||
372 | * @param mixed $query |
||
373 | * @param string $column |
||
374 | * @param string $keyword |
||
375 | * @param string $relation |
||
376 | */ |
||
377 | protected function compileQuerySearch($query, $column, $keyword, $relation = 'or') |
||
378 | { |
||
379 | $column = $this->addTablePrefix($query, $column); |
||
380 | $column = $this->castColumn($column); |
||
381 | $sql = $column . ' LIKE ?'; |
||
382 | |||
383 | if ($this->isCaseInsensitive()) { |
||
384 | $sql = 'LOWER(' . $column . ') LIKE ?'; |
||
385 | } |
||
386 | |||
387 | $query->{$relation . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]); |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * Patch for fix about ambiguous field. |
||
392 | * Ambiguous field error will appear when query use join table and search with keyword. |
||
393 | * |
||
394 | * @param mixed $query |
||
395 | * @param string $column |
||
396 | * @return string |
||
397 | */ |
||
398 | protected function addTablePrefix($query, $column) |
||
399 | { |
||
400 | // Check if field does not have a table prefix |
||
401 | if (strpos($column, '.') === false) { |
||
402 | // Alternative method to check instanceof \Illuminate\Database\Eloquent\Builder |
||
403 | if (method_exists($query, 'getQuery')) { |
||
404 | $q = $query->getQuery(); |
||
405 | } else { |
||
406 | $q = $query; |
||
407 | } |
||
408 | |||
409 | if (! $q->from instanceof Expression) { |
||
410 | // Get table from query and add it. |
||
411 | $column = $q->from . '.' . $column; |
||
412 | } |
||
413 | } |
||
414 | |||
415 | return $this->wrap($column); |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * Wrap a column and cast in pgsql. |
||
420 | * |
||
421 | * @param string $column |
||
422 | * @return string |
||
423 | */ |
||
424 | protected function castColumn($column) |
||
425 | { |
||
426 | if ($this->database === 'pgsql') { |
||
427 | $column = 'CAST(' . $column . ' as TEXT)'; |
||
428 | } elseif ($this->database === 'firebird') { |
||
429 | $column = 'CAST(' . $column . ' as VARCHAR(255))'; |
||
430 | } |
||
431 | |||
432 | return $column; |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * Prepare search keyword based on configurations. |
||
437 | * |
||
438 | * @param string $keyword |
||
439 | * @return string |
||
440 | */ |
||
441 | protected function prepareKeyword($keyword) |
||
442 | { |
||
443 | if ($this->isCaseInsensitive()) { |
||
444 | $keyword = Str::lower($keyword); |
||
445 | } |
||
446 | |||
447 | if ($this->isWildcard()) { |
||
448 | $keyword = $this->wildcardLikeString($keyword); |
||
449 | } |
||
450 | |||
451 | if ($this->isSmartSearch()) { |
||
452 | $keyword = "%$keyword%"; |
||
453 | } |
||
454 | |||
455 | return $keyword; |
||
456 | } |
||
457 | |||
458 | /** |
||
459 | * Perform column search. |
||
460 | * |
||
461 | * @return void |
||
462 | */ |
||
463 | public function columnSearch() |
||
464 | { |
||
465 | $columns = (array) $this->request->input('columns'); |
||
466 | |||
467 | foreach ($columns as $index => $column) { |
||
468 | if (! $this->request->isColumnSearchable($index)) { |
||
469 | continue; |
||
470 | } |
||
471 | |||
472 | $column = $this->getColumnName($index); |
||
473 | |||
474 | if (isset($this->columnDef['filter'][$column])) { |
||
475 | $columnDef = $this->columnDef['filter'][$column]; |
||
476 | // get a raw keyword (without wildcards) |
||
477 | $keyword = $this->getSearchKeyword($index, true); |
||
478 | $builder = $this->getQueryBuilder(); |
||
479 | |||
480 | View Code Duplication | if ($columnDef['method'] instanceof Closure) { |
|
481 | $whereQuery = $builder->newQuery(); |
||
482 | call_user_func_array($columnDef['method'], [$whereQuery, $keyword]); |
||
483 | $builder->addNestedWhereQuery($whereQuery); |
||
484 | } else { |
||
485 | $this->compileColumnQuery( |
||
486 | $builder, |
||
487 | $columnDef['method'], |
||
488 | $columnDef['parameters'], |
||
489 | $column, |
||
490 | $keyword |
||
491 | ); |
||
492 | } |
||
493 | View Code Duplication | } else { |
|
0 ignored issues
–
show
|
|||
494 | if (count(explode('.', $column)) > 1) { |
||
495 | $eagerLoads = $this->getEagerLoads(); |
||
496 | $parts = explode('.', $column); |
||
497 | $relationColumn = array_pop($parts); |
||
498 | $relation = implode('.', $parts); |
||
499 | if (in_array($relation, $eagerLoads)) { |
||
500 | $column = $this->joinEagerLoadedColumn($relation, $relationColumn); |
||
501 | } |
||
502 | } |
||
503 | |||
504 | $keyword = $this->getSearchKeyword($index); |
||
505 | $this->compileColumnSearch($index, $column, $keyword); |
||
506 | } |
||
507 | |||
508 | $this->isFilterApplied = true; |
||
509 | } |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * Get proper keyword to use for search. |
||
514 | * |
||
515 | * @param int $i |
||
516 | * @param bool $raw |
||
517 | * @return string |
||
518 | */ |
||
519 | protected function getSearchKeyword($i, $raw = false) |
||
520 | { |
||
521 | $keyword = $this->request->columnKeyword($i); |
||
522 | if ($raw || $this->request->isRegex($i)) { |
||
523 | return $keyword; |
||
524 | } |
||
525 | |||
526 | return $this->setupKeyword($keyword); |
||
527 | } |
||
528 | |||
529 | /** |
||
530 | * Join eager loaded relation and get the related column name. |
||
531 | * |
||
532 | * @param string $relation |
||
533 | * @param string $relationColumn |
||
534 | * @return string |
||
535 | */ |
||
536 | protected function joinEagerLoadedColumn($relation, $relationColumn) |
||
537 | { |
||
538 | $model = $this->query->getRelation($relation); |
||
539 | switch (true) { |
||
540 | case $model instanceof BelongsToMany: |
||
541 | $pivot = $model->getTable(); |
||
542 | $pivotPK = $model->getExistenceCompareKey(); |
||
543 | $pivotFK = $model->getQualifiedParentKeyName(); |
||
544 | $this->performJoin($pivot, $pivotPK, $pivotFK); |
||
545 | |||
546 | $related = $model->getRelated(); |
||
547 | $table = $related->getTable(); |
||
548 | $tablePK = $related->getForeignKey(); |
||
549 | $foreign = $pivot . '.' . $tablePK; |
||
550 | $other = $related->getQualifiedKeyName(); |
||
551 | break; |
||
552 | |||
553 | case $model instanceof HasOneOrMany: |
||
554 | $table = $model->getRelated()->getTable(); |
||
555 | $foreign = $model->getQualifiedForeignKeyName(); |
||
556 | $other = $model->getQualifiedParentKeyName(); |
||
557 | break; |
||
558 | |||
559 | default: |
||
560 | $table = $model->getRelated()->getTable(); |
||
561 | $foreign = $model->getQualifiedForeignKey(); |
||
562 | $other = $model->getQualifiedOtherKeyName(); |
||
563 | } |
||
564 | |||
565 | $this->performJoin($table, $foreign, $other); |
||
566 | |||
567 | return $table . '.' . $relationColumn; |
||
568 | } |
||
569 | |||
570 | /** |
||
571 | * Perform join query. |
||
572 | * |
||
573 | * @param string $table |
||
574 | * @param string $foreign |
||
575 | * @param string $other |
||
576 | */ |
||
577 | protected function performJoin($table, $foreign, $other) |
||
578 | { |
||
579 | $joins = []; |
||
580 | foreach ((array) $this->getQueryBuilder()->joins as $key => $join) { |
||
581 | $joins[] = $join->table; |
||
582 | } |
||
583 | |||
584 | if (! in_array($table, $joins)) { |
||
585 | $this->getQueryBuilder()->join($table, $foreign, '=', $other); |
||
586 | } |
||
587 | } |
||
588 | |||
589 | /** |
||
590 | * Compile queries for column search. |
||
591 | * |
||
592 | * @param int $i |
||
593 | * @param mixed $column |
||
594 | * @param string $keyword |
||
595 | */ |
||
596 | protected function compileColumnSearch($i, $column, $keyword) |
||
597 | { |
||
598 | if ($this->request->isRegex($i)) { |
||
599 | $column = strstr($column, '(') ? $this->connection->raw($column) : $column; |
||
600 | $this->regexColumnSearch($column, $keyword); |
||
601 | } else { |
||
602 | $this->compileQuerySearch($this->query, $column, $keyword, ''); |
||
603 | } |
||
604 | } |
||
605 | |||
606 | /** |
||
607 | * Compile regex query column search. |
||
608 | * |
||
609 | * @param mixed $column |
||
610 | * @param string $keyword |
||
611 | */ |
||
612 | protected function regexColumnSearch($column, $keyword) |
||
613 | { |
||
614 | if ($this->isOracleSql()) { |
||
615 | $sql = ! $this->isCaseInsensitive() ? 'REGEXP_LIKE( ' . $column . ' , ? )' : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )'; |
||
616 | $this->query->whereRaw($sql, [$keyword]); |
||
617 | } else { |
||
618 | $sql = ! $this->isCaseInsensitive() ? $column . ' REGEXP ?' : 'LOWER(' . $column . ') REGEXP ?'; |
||
619 | $this->query->whereRaw($sql, [Str::lower($keyword)]); |
||
620 | } |
||
621 | } |
||
622 | |||
623 | /** |
||
624 | * Perform sorting of columns. |
||
625 | * |
||
626 | * @return void |
||
627 | */ |
||
628 | public function ordering() |
||
629 | { |
||
630 | if ($this->orderCallback) { |
||
631 | call_user_func($this->orderCallback, $this->getQueryBuilder()); |
||
632 | |||
633 | return; |
||
634 | } |
||
635 | |||
636 | foreach ($this->request->orderableColumns() as $orderable) { |
||
637 | $column = $this->getColumnName($orderable['column'], true); |
||
638 | |||
639 | if ($this->isBlacklisted($column)) { |
||
640 | continue; |
||
641 | } |
||
642 | |||
643 | if (isset($this->columnDef['order'][$column])) { |
||
644 | $method = $this->columnDef['order'][$column]['method']; |
||
645 | $parameters = $this->columnDef['order'][$column]['parameters']; |
||
646 | $this->compileColumnQuery( |
||
647 | $this->getQueryBuilder(), |
||
648 | $method, |
||
649 | $parameters, |
||
650 | $column, |
||
651 | $orderable['direction'] |
||
652 | ); |
||
653 | } else { |
||
654 | $valid = 1; |
||
655 | if (count(explode('.', $column)) > 1) { |
||
656 | $eagerLoads = $this->getEagerLoads(); |
||
657 | $parts = explode('.', $column); |
||
658 | $relationColumn = array_pop($parts); |
||
659 | $relation = implode('.', $parts); |
||
660 | |||
661 | if (in_array($relation, $eagerLoads)) { |
||
662 | $relationship = $this->query->getRelation($relation); |
||
663 | if (! ($relationship instanceof MorphToMany)) { |
||
664 | $column = $this->joinEagerLoadedColumn($relation, $relationColumn); |
||
665 | } else { |
||
666 | $valid = 0; |
||
667 | } |
||
668 | } |
||
669 | } |
||
670 | |||
671 | if ($valid == 1) { |
||
672 | if ($this->nullsLast) { |
||
673 | $this->getQueryBuilder()->orderByRaw($this->getNullsLastSql($column, $orderable['direction'])); |
||
674 | } else { |
||
675 | $this->getQueryBuilder()->orderBy($column, $orderable['direction']); |
||
676 | } |
||
677 | } |
||
678 | } |
||
679 | } |
||
680 | } |
||
681 | |||
682 | /** |
||
683 | * Get NULLS LAST SQL. |
||
684 | * |
||
685 | * @param string $column |
||
686 | * @param string $direction |
||
687 | * @return string |
||
688 | */ |
||
689 | protected function getNullsLastSql($column, $direction) |
||
690 | { |
||
691 | $sql = Config::get('datatables.nulls_last_sql', '%s %s NULLS LAST'); |
||
692 | |||
693 | return sprintf($sql, $column, $direction); |
||
694 | } |
||
695 | |||
696 | /** |
||
697 | * Perform pagination |
||
698 | * |
||
699 | * @return void |
||
700 | */ |
||
701 | public function paging() |
||
702 | { |
||
703 | $this->query->skip($this->request->input('start')) |
||
704 | ->take((int) $this->request->input('length') > 0 ? $this->request->input('length') : 10); |
||
705 | } |
||
706 | |||
707 | /** |
||
708 | * Get results |
||
709 | * |
||
710 | * @return array|static[] |
||
711 | */ |
||
712 | public function results() |
||
713 | { |
||
714 | return $this->query->get(); |
||
715 | } |
||
716 | } |
||
717 |
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.