Completed
Push — master ( f1ca9b...c69c44 )
by Arjay
01:41
created

BaseEngine   C

Complexity

Total Complexity 67

Size/Duplication

Total Lines 768
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 9

Importance

Changes 0
Metric Value
dl 0
loc 768
rs 5
c 0
b 0
f 0
wmc 67
lcom 3
cbo 9

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 setTransformer() 0 6 1
A setSerializer() 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 ?
B filterRecords() 0 13 5
A filtering() 0 12 2
A smartGlobalSearch() 0 8 2
globalSearch() 0 1 ?
A paginate() 0 6 3
A transform() 0 8 2
A getProcessedData() 0 11 1
A render() 0 20 2
showDebugger() 0 1 ?
A errorResponse() 0 17 3
A getLogger() 0 6 2
A setLogger() 0 6 1
A setupKeyword() 0 15 3
A overrideGlobalSearch() 0 7 1
A getColumnName() 0 15 3
A getColumnNameByIndex() 0 6 4
A getPrimaryKeyName() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like BaseEngine 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 BaseEngine, and based on these observations, apply Extract Interface, too.

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