Completed
Pull Request — master (#1488)
by Elf
01:39
created

DataTableAbstract   C

Complexity

Total Complexity 69

Size/Duplication

Total Lines 764
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Importance

Changes 0
Metric Value
dl 0
loc 764
rs 5
c 0
b 0
f 0
wmc 69
lcom 2
cbo 10

44 Methods

Rating   Name   Duplication   Size   Complexity  
A addColumn() 0 8 1
A addIndexColumn() 0 6 1
A editColumn() 0 6 1
A removeColumn() 0 7 1
A escapeColumns() 0 6 1
A rawColumns() 0 6 1
A setRowClass() 0 6 1
A setRowId() 0 6 1
A setRowData() 0 6 1
A addRowData() 0 6 1
A setRowAttr() 0 6 1
A addRowAttr() 0 6 1
A with() 0 12 3
A order() 0 6 1
A blacklist() 0 6 1
A whitelist() 0 6 1
A smart() 0 6 1
A setTotalRecords() 0 6 1
A skipPaging() 0 6 1
A pushToBlacklist() 0 8 2
A isBlacklisted() 0 14 4
A getColumnsDefinition() 0 7 1
A ordering() 0 8 2
resolveCallbackParameter() 0 1 ?
defaultOrdering() 0 1 ?
A filter() 0 8 1
A toArray() 0 4 1
A toJson() 0 8 2
B filterRecords() 0 13 5
A filtering() 0 12 2
A smartGlobalSearch() 0 10 1
globalSearch() 0 1 ?
A paginate() 0 6 3
A transform() 0 12 3
A processResults() 0 11 1
A render() 0 20 2
A showDebugger() 0 6 1
A errorResponse() 0 17 3
A getLogger() 0 6 2
A setLogger() 0 6 1
A setupKeyword() 0 15 3
A getColumnName() 0 15 3
A getColumnNameByIndex() 0 7 4
A getPrimaryKeyName() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like DataTableAbstract often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DataTableAbstract, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Yajra\DataTables;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Http\JsonResponse;
7
use Illuminate\Contracts\Logging\Log;
8
use Yajra\DataTables\Utilities\Helper;
9
use Illuminate\Support\Traits\Macroable;
10
use Yajra\DataTables\Contracts\DataTable;
11
use Illuminate\Contracts\Support\Jsonable;
12
use Yajra\DataTables\Exceptions\Exception;
13
use Illuminate\Contracts\Support\Arrayable;
14
use Yajra\DataTables\Processors\DataProcessor;
15
16
/**
17
 * @method DataTableAbstract setTransformer($transformer)
18
 * @method DataTableAbstract setSerializer($transformer)
19
 * @property mixed transformer
20
 * @property mixed serializer
21
 * @see     https://github.com/yajra/laravel-datatables-fractal for transformer related methods.
22
 */
23
abstract class DataTableAbstract implements DataTable, Arrayable, Jsonable
24
{
25
    use Macroable;
26
27
    /**
28
     * DataTables Request object.
29
     *
30
     * @var \Yajra\DataTables\Utilities\Request
31
     */
32
    public $request;
33
34
    /**
35
     * @var \Illuminate\Contracts\Logging\Log
36
     */
37
    protected $logger;
38
39
    /**
40
     * Array of result columns/fields.
41
     *
42
     * @var array
43
     */
44
    protected $columns = [];
45
46
    /**
47
     * DT columns definitions container (add/edit/remove/filter/order/escape).
48
     *
49
     * @var array
50
     */
51
    protected $columnDef = [
52
        'index'  => false,
53
        'append' => [],
54
        'edit'   => [],
55
        'filter' => [],
56
        'order'  => [],
57
    ];
58
59
    /**
60
     * Extra/Added columns.
61
     *
62
     * @var array
63
     */
64
    protected $extraColumns = [];
65
66
    /**
67
     * Total records.
68
     *
69
     * @var int
70
     */
71
    protected $totalRecords = 0;
72
73
    /**
74
     * Total filtered records.
75
     *
76
     * @var int
77
     */
78
    protected $filteredRecords = 0;
79
80
    /**
81
     * Auto-filter flag.
82
     *
83
     * @var bool
84
     */
85
    protected $autoFilter = true;
86
87
    /**
88
     * Callback to override global search.
89
     *
90
     * @var callable
91
     */
92
    protected $filterCallback;
93
94
    /**
95
     * DT row templates container.
96
     *
97
     * @var array
98
     */
99
    protected $templates = [
100
        'DT_RowId'    => '',
101
        'DT_RowClass' => '',
102
        'DT_RowData'  => [],
103
        'DT_RowAttr'  => [],
104
    ];
105
106
    /**
107
     * [internal] Track if any filter was applied for at least one column.
108
     *
109
     * @var bool
110
     */
111
    protected $isFilterApplied = false;
112
113
    /**
114
     * Custom ordering callback.
115
     *
116
     * @var callable
117
     */
118
    protected $orderCallback;
119
120
    /**
121
     * Skip paginate as needed.
122
     *
123
     * @var bool
124
     */
125
    protected $skipPaging = false;
126
127
    /**
128
     * Array of data to append on json response.
129
     *
130
     * @var array
131
     */
132
    protected $appends = [];
133
134
    /**
135
     * @var \Yajra\DataTables\Utilities\Config
136
     */
137
    protected $config;
138
139
    /**
140
     * Add column in collection.
141
     *
142
     * @param string          $name
143
     * @param string|callable $content
144
     * @param bool|int        $order
145
     * @return $this
146
     */
147
    public function addColumn($name, $content, $order = false)
148
    {
149
        $this->extraColumns[] = $name;
150
151
        $this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order];
152
153
        return $this;
154
    }
155
156
    /**
157
     * Add DT row index column on response.
158
     *
159
     * @return $this
160
     */
161
    public function addIndexColumn()
162
    {
163
        $this->columnDef['index'] = true;
164
165
        return $this;
166
    }
167
168
    /**
169
     * Edit column's content.
170
     *
171
     * @param string          $name
172
     * @param string|callable $content
173
     * @return $this
174
     */
175
    public function editColumn($name, $content)
176
    {
177
        $this->columnDef['edit'][] = ['name' => $name, 'content' => $content];
178
179
        return $this;
180
    }
181
182
    /**
183
     * Remove column from collection.
184
     *
185
     * @return $this
186
     */
187
    public function removeColumn()
188
    {
189
        $names                     = func_get_args();
190
        $this->columnDef['excess'] = array_merge($this->getColumnsDefinition()['excess'], $names);
191
192
        return $this;
193
    }
194
195
    /**
196
     * Declare columns to escape values.
197
     *
198
     * @param string|array $columns
199
     * @return $this
200
     */
201
    public function escapeColumns($columns = '*')
202
    {
203
        $this->columnDef['escape'] = $columns;
204
205
        return $this;
206
    }
207
208
    /**
209
     * Set columns that should not be escaped.
210
     *
211
     * @param array $columns
212
     * @return $this
213
     */
214
    public function rawColumns(array $columns)
215
    {
216
        $this->columnDef['raw'] = $columns;
217
218
        return $this;
219
    }
220
221
    /**
222
     * Sets DT_RowClass template.
223
     * result: <tr class="output_from_your_template">.
224
     *
225
     * @param string|callable $content
226
     * @return $this
227
     */
228
    public function setRowClass($content)
229
    {
230
        $this->templates['DT_RowClass'] = $content;
231
232
        return $this;
233
    }
234
235
    /**
236
     * Sets DT_RowId template.
237
     * result: <tr id="output_from_your_template">.
238
     *
239
     * @param string|callable $content
240
     * @return $this
241
     */
242
    public function setRowId($content)
243
    {
244
        $this->templates['DT_RowId'] = $content;
245
246
        return $this;
247
    }
248
249
    /**
250
     * Set DT_RowData templates.
251
     *
252
     * @param array $data
253
     * @return $this
254
     */
255
    public function setRowData(array $data)
256
    {
257
        $this->templates['DT_RowData'] = $data;
258
259
        return $this;
260
    }
261
262
    /**
263
     * Add DT_RowData template.
264
     *
265
     * @param string          $key
266
     * @param string|callable $value
267
     * @return $this
268
     */
269
    public function addRowData($key, $value)
270
    {
271
        $this->templates['DT_RowData'][$key] = $value;
272
273
        return $this;
274
    }
275
276
    /**
277
     * Set DT_RowAttr templates.
278
     * result: <tr attr1="attr1" attr2="attr2">.
279
     *
280
     * @param array $data
281
     * @return $this
282
     */
283
    public function setRowAttr(array $data)
284
    {
285
        $this->templates['DT_RowAttr'] = $data;
286
287
        return $this;
288
    }
289
290
    /**
291
     * Add DT_RowAttr template.
292
     *
293
     * @param string          $key
294
     * @param string|callable $value
295
     * @return $this
296
     */
297
    public function addRowAttr($key, $value)
298
    {
299
        $this->templates['DT_RowAttr'][$key] = $value;
300
301
        return $this;
302
    }
303
304
    /**
305
     * Append data on json response.
306
     *
307
     * @param mixed $key
308
     * @param mixed $value
309
     * @return $this
310
     */
311
    public function with($key, $value = '')
312
    {
313
        if (is_array($key)) {
314
            $this->appends = $key;
315
        } elseif (is_callable($value)) {
316
            $this->appends[$key] = value($value);
317
        } else {
318
            $this->appends[$key] = value($value);
319
        }
320
321
        return $this;
322
    }
323
324
    /**
325
     * Override default ordering method with a closure callback.
326
     *
327
     * @param callable $closure
328
     * @return $this
329
     */
330
    public function order(callable $closure)
331
    {
332
        $this->orderCallback = $closure;
333
334
        return $this;
335
    }
336
337
    /**
338
     * Update list of columns that is not allowed for search/sort.
339
     *
340
     * @param  array $blacklist
341
     * @return $this
342
     */
343
    public function blacklist(array $blacklist)
344
    {
345
        $this->columnDef['blacklist'] = $blacklist;
346
347
        return $this;
348
    }
349
350
    /**
351
     * Update list of columns that is allowed for search/sort.
352
     *
353
     * @param  string|array $whitelist
354
     * @return $this
355
     */
356
    public function whitelist($whitelist = '*')
357
    {
358
        $this->columnDef['whitelist'] = $whitelist;
359
360
        return $this;
361
    }
362
363
    /**
364
     * Set smart search config at runtime.
365
     *
366
     * @param bool $bool
367
     * @return $this
368
     */
369
    public function smart($bool = true)
370
    {
371
        $this->config->set(['datatables.search.smart' => $bool]);
372
373
        return $this;
374
    }
375
376
    /**
377
     * Set total records manually.
378
     *
379
     * @param int $total
380
     * @return $this
381
     */
382
    public function setTotalRecords($total)
383
    {
384
        $this->totalRecords = $total;
385
386
        return $this;
387
    }
388
389
    /**
390
     * Skip pagination as needed.
391
     *
392
     * @return $this
393
     */
394
    public function skipPaging()
395
    {
396
        $this->skipPaging = true;
397
398
        return $this;
399
    }
400
401
    /**
402
     * Push a new column name to blacklist.
403
     *
404
     * @param string $column
405
     * @return $this
406
     */
407
    public function pushToBlacklist($column)
408
    {
409
        if (! $this->isBlacklisted($column)) {
410
            $this->columnDef['blacklist'][] = $column;
411
        }
412
413
        return $this;
414
    }
415
416
    /**
417
     * Check if column is blacklisted.
418
     *
419
     * @param string $column
420
     * @return bool
421
     */
422
    protected function isBlacklisted($column)
423
    {
424
        $colDef = $this->getColumnsDefinition();
425
426
        if (in_array($column, $colDef['blacklist'])) {
427
            return true;
428
        }
429
430
        if ($colDef['whitelist'] === '*' || in_array($column, $colDef['whitelist'])) {
431
            return false;
432
        }
433
434
        return true;
435
    }
436
437
    /**
438
     * Get columns definition.
439
     *
440
     * @return array
441
     */
442
    protected function getColumnsDefinition()
443
    {
444
        $config  = $this->config->get('datatables.columns');
445
        $allowed = ['excess', 'escape', 'raw', 'blacklist', 'whitelist'];
446
447
        return array_replace_recursive(array_only($config, $allowed), $this->columnDef);
448
    }
449
450
    /**
451
     * Perform sorting of columns.
452
     */
453
    public function ordering()
454
    {
455
        if ($this->orderCallback) {
456
            return call_user_func($this->orderCallback, $this->resolveCallbackParameter());
457
        }
458
459
        return $this->defaultOrdering();
460
    }
461
462
    /**
463
     * Resolve callback parameter instance.
464
     *
465
     * @return mixed
466
     */
467
    abstract protected function resolveCallbackParameter();
468
469
    /**
470
     * Perform default query orderBy clause.
471
     */
472
    abstract protected function defaultOrdering();
473
474
    /**
475
     * Set auto filter off and run your own filter.
476
     * Overrides global search.
477
     *
478
     * @param callable $callback
479
     * @param bool     $globalSearch
480
     * @return $this
481
     */
482
    public function filter(callable $callback, $globalSearch = false)
483
    {
484
        $this->autoFilter      = $globalSearch;
485
        $this->isFilterApplied = true;
486
        $this->filterCallback  = $callback;
487
488
        return $this;
489
    }
490
491
    /**
492
     * Convert instance to array.
493
     *
494
     * @return array
495
     */
496
    public function toArray()
497
    {
498
        return $this->make()->getData(true);
499
    }
500
501
    /**
502
     * Convert the object to its JSON representation.
503
     *
504
     * @param  int $options
505
     * @return \Illuminate\Http\JsonResponse
506
     */
507
    public function toJson($options = 0)
508
    {
509
        if ($options) {
510
            $this->config->set('datatables.json.options', $options);
511
        }
512
513
        return $this->make();
514
    }
515
516
    /**
517
     * Perform necessary filters.
518
     *
519
     * @return void
520
     */
521
    protected function filterRecords()
522
    {
523
        if ($this->autoFilter && $this->request->isSearchable()) {
524
            $this->filtering();
525
        }
526
527
        if (is_callable($this->filterCallback)) {
528
            call_user_func($this->filterCallback, $this->resolveCallbackParameter());
529
        }
530
531
        $this->columnSearch();
532
        $this->filteredRecords = $this->isFilterApplied ? $this->count() : $this->totalRecords;
533
    }
534
535
    /**
536
     * Perform global search.
537
     *
538
     * @return void
539
     */
540
    public function filtering()
541
    {
542
        $keyword = $this->request->keyword();
543
544
        if ($this->config->isMultiTerm()) {
545
            $this->smartGlobalSearch($keyword);
546
547
            return;
548
        }
549
550
        $this->globalSearch($keyword);
551
    }
552
553
    /**
554
     * Perform multi-term search by splitting keyword into
555
     * individual words and searches for each of them.
556
     *
557
     * @param string $keyword
558
     */
559
    protected function smartGlobalSearch($keyword)
560
    {
561
        collect(explode(' ', $keyword))
562
            ->reject(function ($keyword) {
563
                return trim($keyword) === '';
564
            })
565
            ->each(function ($keyword) {
566
                $this->globalSearch($keyword);
567
            });
568
    }
569
570
    /**
571
     * Perform global search for the given keyword.
572
     *
573
     * @param string $keyword
574
     */
575
    abstract protected function globalSearch($keyword);
576
577
    /**
578
     * Apply pagination.
579
     *
580
     * @return void
581
     */
582
    protected function paginate()
583
    {
584
        if ($this->request->isPaginationable() && ! $this->skipPaging) {
585
            $this->paging();
586
        }
587
    }
588
589
    /**
590
     * Transform output.
591
     *
592
     * @param mixed $results
593
     * @param mixed $processed
594
     * @return array
595
     */
596
    protected function transform($results, $processed)
597
    {
598
        if (isset($this->transformer) && class_exists('Yajra\\DataTables\\Transformers\\FractalTransformer')) {
599
            return app('datatables.transformer')->transform(
600
                $results,
601
                $this->transformer,
602
                $this->serializer ?? null
603
            );
604
        }
605
606
        return Helper::transform($processed);
607
    }
608
609
    /**
610
     * Get processed data.
611
     *
612
     * @param mixed $results
613
     * @param bool  $object
614
     * @return array
615
     */
616
    protected function processResults($results, $object = false)
617
    {
618
        $processor = new DataProcessor(
619
            $results,
620
            $this->getColumnsDefinition(),
621
            $this->templates,
622
            $this->request->input('start')
623
        );
624
625
        return $processor->process($object);
626
    }
627
628
    /**
629
     * Render json response.
630
     *
631
     * @param array $data
632
     * @return \Illuminate\Http\JsonResponse
633
     */
634
    protected function render(array $data)
635
    {
636
        $output = array_merge([
637
            'draw'            => (int) $this->request->input('draw'),
638
            'recordsTotal'    => $this->totalRecords,
639
            'recordsFiltered' => $this->filteredRecords,
640
            'data'            => $data,
641
        ], $this->appends);
642
643
        if ($this->config->isDebugging()) {
644
            $output = $this->showDebugger($output);
645
        }
646
647
        return new JsonResponse(
648
            $output,
649
            200,
650
            $this->config->get('datatables.json.header', []),
651
            $this->config->get('datatables.json.options', 0)
652
        );
653
    }
654
655
    /**
656
     * Append debug parameters on output.
657
     *
658
     * @param  array $output
659
     * @return array
660
     */
661
    protected function showDebugger(array $output)
662
    {
663
        $output['input'] = $this->request->all();
664
665
        return $output;
666
    }
667
668
    /**
669
     * Return an error json response.
670
     *
671
     * @param \Exception $exception
672
     * @return \Illuminate\Http\JsonResponse
673
     * @throws \Yajra\DataTables\Exceptions\Exception
674
     */
675
    protected function errorResponse(\Exception $exception)
676
    {
677
        $error = $this->config->get('datatables.error');
678
        if ($error === 'throw') {
679
            throw new Exception($exception->getMessage(), $code = 0, $exception);
680
        }
681
682
        $this->getLogger()->error($exception);
683
684
        return new JsonResponse([
685
            'draw'            => (int) $this->request->input('draw'),
686
            'recordsTotal'    => (int) $this->totalRecords,
687
            'recordsFiltered' => 0,
688
            'data'            => [],
689
            'error'           => $error ? __($error) : "Exception Message:\n\n" . $exception->getMessage(),
690
        ]);
691
    }
692
693
    /**
694
     * Get monolog/logger instance.
695
     *
696
     * @return \Illuminate\Contracts\Logging\Log
697
     */
698
    public function getLogger()
699
    {
700
        $this->logger = $this->logger ?: app(Log::class);
701
702
        return $this->logger;
703
    }
704
705
    /**
706
     * Set monolog/logger instance.
707
     *
708
     * @param \Illuminate\Contracts\Logging\Log $logger
709
     * @return $this
710
     */
711
    public function setLogger(Log $logger)
712
    {
713
        $this->logger = $logger;
714
715
        return $this;
716
    }
717
718
    /**
719
     * Setup search keyword.
720
     *
721
     * @param  string $value
722
     * @return string
723
     */
724
    protected function setupKeyword($value)
725
    {
726
        if ($this->config->isSmartSearch()) {
727
            $keyword = '%' . $value . '%';
728
            if ($this->config->isWildcard()) {
729
                $keyword = Helper::wildcardLikeString($value);
730
            }
731
            // remove escaping slash added on js script request
732
            $keyword = str_replace('\\', '%', $keyword);
733
734
            return $keyword;
735
        }
736
737
        return $value;
738
    }
739
740
    /**
741
     * Get column name to be use for filtering and sorting.
742
     *
743
     * @param int $index
744
     * @param bool    $wantsAlias
745
     * @return string
746
     */
747
    protected function getColumnName($index, $wantsAlias = false)
748
    {
749
        $column = $this->request->columnName($index);
750
751
        // DataTables is using make(false)
752
        if (is_numeric($column)) {
753
            $column = $this->getColumnNameByIndex($index);
754
        }
755
756
        if (Str::contains(Str::upper($column), ' AS ')) {
757
            $column = Helper::extractColumnName($column, $wantsAlias);
758
        }
759
760
        return $column;
761
    }
762
763
    /**
764
     * Get column name by order column index.
765
     *
766
     * @param int $index
767
     * @return string
768
     */
769
    protected function getColumnNameByIndex($index)
770
    {
771
        $name = (isset($this->columns[$index]) && $this->columns[$index] != '*')
772
            ? $this->columns[$index] : $this->getPrimaryKeyName();
773
774
        return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name;
775
    }
776
777
    /**
778
     * If column name could not be resolved then use primary key.
779
     *
780
     * @return string
781
     */
782
    protected function getPrimaryKeyName()
783
    {
784
        return 'id';
785
    }
786
}
787