Completed
Push — master ( b1bb78...82c1ec )
by Arjay
01:57 queued 12s
created

DataTableAbstract::formatColumn()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
c 0
b 0
f 0
cc 5
nc 6
nop 2
rs 9.4222
1
<?php
2
3
namespace Yajra\DataTables;
4
5
use Illuminate\Contracts\Support\Arrayable;
6
use Illuminate\Contracts\Support\Jsonable;
7
use Illuminate\Http\JsonResponse;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Str;
10
use Illuminate\Support\Traits\Macroable;
11
use Psr\Log\LoggerInterface;
12
use Yajra\DataTables\Contracts\DataTable;
13
use Yajra\DataTables\Contracts\Formatter;
14
use Yajra\DataTables\Exceptions\Exception;
15
use Yajra\DataTables\Processors\DataProcessor;
16
use Yajra\DataTables\Utilities\Helper;
17
18
/**
19
 * @method DataTableAbstract setTransformer($transformer)
20
 * @method DataTableAbstract setSerializer($transformer)
21
 * @property mixed transformer
22
 * @property mixed serializer
23
 * @see     https://github.com/yajra/laravel-datatables-fractal for transformer related methods.
24
 */
25
abstract class DataTableAbstract implements DataTable, Arrayable, Jsonable
26
{
27
    use Macroable;
28
29
    /**
30
     * DataTables Request object.
31
     *
32
     * @var \Yajra\DataTables\Utilities\Request
33
     */
34
    public $request;
35
36
    /**
37
     * @var \Psr\Log\LoggerInterface
38
     */
39
    protected $logger;
40
41
    /**
42
     * Array of result columns/fields.
43
     *
44
     * @var array
45
     */
46
    protected $columns = [];
47
48
    /**
49
     * DT columns definitions container (add/edit/remove/filter/order/escape).
50
     *
51
     * @var array
52
     */
53
    protected $columnDef = [
54
        'index'       => false,
55
        'append'      => [],
56
        'edit'        => [],
57
        'filter'      => [],
58
        'order'       => [],
59
        'only'        => null,
60
        'hidden'      => [],
61
        'visible'     => [],
62
    ];
63
64
    /**
65
     * Extra/Added columns.
66
     *
67
     * @var array
68
     */
69
    protected $extraColumns = [];
70
71
    /**
72
     * Total records.
73
     *
74
     * @var int
75
     */
76
    protected $totalRecords = 0;
77
78
    /**
79
     * Total filtered records.
80
     *
81
     * @var int
82
     */
83
    protected $filteredRecords = 0;
84
85
    /**
86
     * Auto-filter flag.
87
     *
88
     * @var bool
89
     */
90
    protected $autoFilter = true;
91
92
    /**
93
     * Callback to override global search.
94
     *
95
     * @var callable
96
     */
97
    protected $filterCallback;
98
99
    /**
100
     * DT row templates container.
101
     *
102
     * @var array
103
     */
104
    protected $templates = [
105
        'DT_RowId'    => '',
106
        'DT_RowClass' => '',
107
        'DT_RowData'  => [],
108
        'DT_RowAttr'  => [],
109
    ];
110
111
    /**
112
     * [internal] Track if any filter was applied for at least one column.
113
     *
114
     * @var bool
115
     */
116
    protected $isFilterApplied = false;
117
118
    /**
119
     * Custom ordering callback.
120
     *
121
     * @var callable
122
     */
123
    protected $orderCallback;
124
125
    /**
126
     * Skip paginate as needed.
127
     *
128
     * @var bool
129
     */
130
    protected $skipPaging = false;
131
132
    /**
133
     * Array of data to append on json response.
134
     *
135
     * @var array
136
     */
137
    protected $appends = [];
138
139
    /**
140
     * @var \Yajra\DataTables\Utilities\Config
141
     */
142
    protected $config;
143
144
    /**
145
     * @var mixed
146
     */
147
    protected $serializer;
148
149
    /**
150
     * @var array
151
     */
152
    protected $searchPanes = [];
153
154
    /**
155
     * Can the DataTable engine be created with these parameters.
156
     *
157
     * @param mixed $source
158
     * @return bool
159
     */
160
    public static function canCreate($source)
161
    {
162
        return false;
163
    }
164
165
    /**
166
     * Factory method, create and return an instance for the DataTable engine.
167
     *
168
     * @param mixed $source
169
     * @return DataTableAbstract
170
     */
171
    public static function create($source)
172
    {
173
        return new static($source);
0 ignored issues
show
Unused Code introduced by
The call to DataTableAbstract::__construct() has too many arguments starting with $source.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
174
    }
175
176
    /**
177
     * Add column in collection.
178
     *
179
     * @param string          $name
180
     * @param string|callable $content
181
     * @param bool|int        $order
182
     * @return $this
183
     */
184
    public function addColumn($name, $content, $order = false)
185
    {
186
        $this->extraColumns[] = $name;
187
188
        $this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order];
189
190
        return $this;
191
    }
192
193
    /**
194
     * @param string|array $columns
195
     * @param mixed|\Yajra\DataTables\Contracts\Formatter $formatter
196
     * @return $this
197
     * @throws \Exception
198
     */
199
    public function formatColumn($columns, $formatter)
200
    {
201
        if (is_string($formatter) && class_exists($formatter)) {
202
            $formatter = app($formatter);
203
        }
204
205
        if (! $formatter instanceof Formatter) {
206
            throw new \Exception('$formatter must be an instance of '. Formatter::class);
207
        }
208
209
        foreach ((array) $columns as $column) {
210
            $this->addColumn($column . '_formatted', $formatter);
0 ignored issues
show
Documentation introduced by
$formatter is of type object<Yajra\DataTables\Contracts\Formatter>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
211
        }
212
213
        return $this;
214
    }
215
216
    /**
217
     * Add DT row index column on response.
218
     *
219
     * @return $this
220
     */
221
    public function addIndexColumn()
222
    {
223
        $this->columnDef['index'] = true;
224
225
        return $this;
226
    }
227
228
    /**
229
     * Edit column's content.
230
     *
231
     * @param string          $name
232
     * @param string|callable $content
233
     * @return $this
234
     */
235
    public function editColumn($name, $content)
236
    {
237
        $this->columnDef['edit'][] = ['name' => $name, 'content' => $content];
238
239
        return $this;
240
    }
241
242
    /**
243
     * Remove column from collection.
244
     *
245
     * @return $this
246
     */
247
    public function removeColumn()
248
    {
249
        $names                     = func_get_args();
250
        $this->columnDef['excess'] = array_merge($this->getColumnsDefinition()['excess'], $names);
251
252
        return $this;
253
    }
254
255
    /**
256
     * Get only selected columns in response.
257
     *
258
     * @param array $columns
259
     * @return $this
260
     */
261
    public function only(array $columns = [])
262
    {
263
        $this->columnDef['only'] = $columns;
264
265
        return $this;
266
    }
267
268
    /**
269
     * Declare columns to escape values.
270
     *
271
     * @param string|array $columns
272
     * @return $this
273
     */
274
    public function escapeColumns($columns = '*')
275
    {
276
        $this->columnDef['escape'] = $columns;
277
278
        return $this;
279
    }
280
281
    /**
282
     * Add a makeHidden() to the row object.
283
     *
284
     * @param array          $attributes
285
     * @return $this
286
     */
287
    public function makeHidden(array $attributes = [])
288
    {
289
        $this->columnDef['hidden'] = array_merge_recursive(Arr::get($this->columnDef, 'hidden', []), $attributes);
290
291
        return $this;
292
    }
293
294
    /**
295
     * Add a makeVisible() to the row object.
296
     *
297
     * @param array          $attributes
298
     * @return $this
299
     */
300
    public function makeVisible(array $attributes = [])
301
    {
302
        $this->columnDef['visible'] = array_merge_recursive(Arr::get($this->columnDef, 'visible', []), $attributes);
303
304
        return $this;
305
    }
306
307
    /**
308
     * Set columns that should not be escaped.
309
     * Optionally merge the defaults from config.
310
     *
311
     * @param array $columns
312
     * @param bool $merge
313
     * @return $this
314
     */
315
    public function rawColumns(array $columns, $merge = false)
316
    {
317
        if ($merge) {
318
            $config = $this->config->get('datatables.columns');
319
320
            $this->columnDef['raw'] = array_merge($config['raw'], $columns);
321
        } else {
322
            $this->columnDef['raw'] = $columns;
323
        }
324
325
        return $this;
326
    }
327
328
    /**
329
     * Sets DT_RowClass template.
330
     * result: <tr class="output_from_your_template">.
331
     *
332
     * @param string|callable $content
333
     * @return $this
334
     */
335
    public function setRowClass($content)
336
    {
337
        $this->templates['DT_RowClass'] = $content;
338
339
        return $this;
340
    }
341
342
    /**
343
     * Sets DT_RowId template.
344
     * result: <tr id="output_from_your_template">.
345
     *
346
     * @param string|callable $content
347
     * @return $this
348
     */
349
    public function setRowId($content)
350
    {
351
        $this->templates['DT_RowId'] = $content;
352
353
        return $this;
354
    }
355
356
    /**
357
     * Set DT_RowData templates.
358
     *
359
     * @param array $data
360
     * @return $this
361
     */
362
    public function setRowData(array $data)
363
    {
364
        $this->templates['DT_RowData'] = $data;
365
366
        return $this;
367
    }
368
369
    /**
370
     * Add DT_RowData template.
371
     *
372
     * @param string          $key
373
     * @param string|callable $value
374
     * @return $this
375
     */
376
    public function addRowData($key, $value)
377
    {
378
        $this->templates['DT_RowData'][$key] = $value;
379
380
        return $this;
381
    }
382
383
    /**
384
     * Set DT_RowAttr templates.
385
     * result: <tr attr1="attr1" attr2="attr2">.
386
     *
387
     * @param array $data
388
     * @return $this
389
     */
390
    public function setRowAttr(array $data)
391
    {
392
        $this->templates['DT_RowAttr'] = $data;
393
394
        return $this;
395
    }
396
397
    /**
398
     * Add DT_RowAttr template.
399
     *
400
     * @param string          $key
401
     * @param string|callable $value
402
     * @return $this
403
     */
404
    public function addRowAttr($key, $value)
405
    {
406
        $this->templates['DT_RowAttr'][$key] = $value;
407
408
        return $this;
409
    }
410
411
    /**
412
     * Append data on json response.
413
     *
414
     * @param mixed $key
415
     * @param mixed $value
416
     * @return $this
417
     */
418
    public function with($key, $value = '')
419
    {
420
        if (is_array($key)) {
421
            $this->appends = $key;
422
        } elseif (is_callable($value)) {
423
            $this->appends[$key] = value($value);
424
        } else {
425
            $this->appends[$key] = value($value);
426
        }
427
428
        return $this;
429
    }
430
431
    /**
432
     * Add with query callback value on response.
433
     *
434
     * @param string   $key
435
     * @param callable $value
436
     * @return $this
437
     */
438
    public function withQuery($key, callable $value)
439
    {
440
        $this->appends[$key] = $value;
441
442
        return $this;
443
    }
444
445
    /**
446
     * Override default ordering method with a closure callback.
447
     *
448
     * @param callable $closure
449
     * @return $this
450
     */
451
    public function order(callable $closure)
452
    {
453
        $this->orderCallback = $closure;
454
455
        return $this;
456
    }
457
458
    /**
459
     * Update list of columns that is not allowed for search/sort.
460
     *
461
     * @param  array $blacklist
462
     * @return $this
463
     */
464
    public function blacklist(array $blacklist)
465
    {
466
        $this->columnDef['blacklist'] = $blacklist;
467
468
        return $this;
469
    }
470
471
    /**
472
     * Update list of columns that is allowed for search/sort.
473
     *
474
     * @param  string|array $whitelist
475
     * @return $this
476
     */
477
    public function whitelist($whitelist = '*')
478
    {
479
        $this->columnDef['whitelist'] = $whitelist;
480
481
        return $this;
482
    }
483
484
    /**
485
     * Set smart search config at runtime.
486
     *
487
     * @param bool $state
488
     * @return $this
489
     */
490
    public function smart($state = true)
491
    {
492
        $this->config->set('datatables.search.smart', $state);
493
494
        return $this;
495
    }
496
497
    /**
498
     * Set starts_with search config at runtime.
499
     *
500
     * @param bool $state
501
     * @return $this
502
     */
503
    public function startsWithSearch($state = true)
504
    {
505
        $this->config->set('datatables.search.starts_with', $state);
506
507
        return $this;
508
    }
509
510
    /**
511
     * Set total records manually.
512
     *
513
     * @param int $total
514
     * @return $this
515
     */
516
    public function setTotalRecords($total)
517
    {
518
        $this->totalRecords = $total;
519
520
        return $this;
521
    }
522
523
    /**
524
     * Set filtered records manually.
525
     *
526
     * @param int $total
527
     * @return $this
528
     */
529
    public function setFilteredRecords($total)
530
    {
531
        $this->filteredRecords = $total;
532
533
        return $this;
534
    }
535
536
    /**
537
     * Skip pagination as needed.
538
     *
539
     * @return $this
540
     */
541
    public function skipPaging()
542
    {
543
        $this->skipPaging = true;
544
545
        return $this;
546
    }
547
548
    /**
549
     * Push a new column name to blacklist.
550
     *
551
     * @param string $column
552
     * @return $this
553
     */
554
    public function pushToBlacklist($column)
555
    {
556
        if (! $this->isBlacklisted($column)) {
557
            $this->columnDef['blacklist'][] = $column;
558
        }
559
560
        return $this;
561
    }
562
563
    /**
564
     * Check if column is blacklisted.
565
     *
566
     * @param string $column
567
     * @return bool
568
     */
569
    protected function isBlacklisted($column)
570
    {
571
        $colDef = $this->getColumnsDefinition();
572
573
        if (in_array($column, $colDef['blacklist'])) {
574
            return true;
575
        }
576
577
        if ($colDef['whitelist'] === '*' || in_array($column, $colDef['whitelist'])) {
578
            return false;
579
        }
580
581
        return true;
582
    }
583
584
    /**
585
     * Get columns definition.
586
     *
587
     * @return array
588
     */
589
    protected function getColumnsDefinition()
590
    {
591
        $config  = $this->config->get('datatables.columns');
592
        $allowed = ['excess', 'escape', 'raw', 'blacklist', 'whitelist'];
593
594
        return array_replace_recursive(Arr::only($config, $allowed), $this->columnDef);
595
    }
596
597
    /**
598
     * Perform sorting of columns.
599
     */
600
    public function ordering()
601
    {
602
        if ($this->orderCallback) {
603
            return call_user_func($this->orderCallback, $this->resolveCallbackParameter());
604
        }
605
606
        return $this->defaultOrdering();
607
    }
608
609
    /**
610
     * Resolve callback parameter instance.
611
     *
612
     * @return mixed
613
     */
614
    abstract protected function resolveCallbackParameter();
615
616
    /**
617
     * Perform default query orderBy clause.
618
     */
619
    abstract protected function defaultOrdering();
620
621
    /**
622
     * Set auto filter off and run your own filter.
623
     * Overrides global search.
624
     *
625
     * @param callable $callback
626
     * @param bool     $globalSearch
627
     * @return $this
628
     */
629
    public function filter(callable $callback, $globalSearch = false)
630
    {
631
        $this->autoFilter      = $globalSearch;
632
        $this->isFilterApplied = true;
633
        $this->filterCallback  = $callback;
634
635
        return $this;
636
    }
637
638
    /**
639
     * Convert instance to array.
640
     *
641
     * @return array
642
     */
643
    public function toArray()
644
    {
645
        return $this->make()->getData(true);
646
    }
647
648
    /**
649
     * Convert the object to its JSON representation.
650
     *
651
     * @param  int $options
652
     * @return \Illuminate\Http\JsonResponse
653
     */
654
    public function toJson($options = 0)
655
    {
656
        if ($options) {
657
            $this->config->set('datatables.json.options', $options);
658
        }
659
660
        return $this->make();
661
    }
662
663
    /**
664
     * Count filtered items.
665
     *
666
     * @return int
667
     */
668
    protected function filteredCount()
669
    {
670
        return $this->filteredRecords ? $this->filteredRecords : $this->count();
671
    }
672
673
    /**
674
     * Perform necessary filters.
675
     *
676
     * @return void
677
     */
678
    protected function filterRecords()
679
    {
680
        if ($this->autoFilter && $this->request->isSearchable()) {
681
            $this->filtering();
682
        }
683
684
        if (is_callable($this->filterCallback)) {
685
            call_user_func($this->filterCallback, $this->resolveCallbackParameter());
686
        }
687
688
        $this->columnSearch();
689
        $this->searchPanesSearch();
690
        $this->filteredRecords = $this->isFilterApplied ? $this->filteredCount() : $this->totalRecords;
691
    }
692
693
    /**
694
     * Perform search using search pane values.
695
     */
696
    protected function searchPanesSearch()
697
    {
698
        // Add support for search pane.
699
    }
700
701
    /**
702
     * Perform global search.
703
     *
704
     * @return void
705
     */
706
    public function filtering()
707
    {
708
        $keyword = $this->request->keyword();
709
710
        if ($this->config->isMultiTerm()) {
711
            $this->smartGlobalSearch($keyword);
712
713
            return;
714
        }
715
716
        $this->globalSearch($keyword);
717
    }
718
719
    /**
720
     * Perform multi-term search by splitting keyword into
721
     * individual words and searches for each of them.
722
     *
723
     * @param string $keyword
724
     */
725
    protected function smartGlobalSearch($keyword)
726
    {
727
        collect(explode(' ', $keyword))
728
            ->reject(function ($keyword) {
729
                return trim($keyword) === '';
730
            })
731
            ->each(function ($keyword) {
732
                $this->globalSearch($keyword);
733
            });
734
    }
735
736
    /**
737
     * Perform global search for the given keyword.
738
     *
739
     * @param string $keyword
740
     */
741
    abstract protected function globalSearch($keyword);
742
743
    /**
744
     * Apply pagination.
745
     *
746
     * @return void
747
     */
748
    protected function paginate()
749
    {
750
        if ($this->request->isPaginationable() && ! $this->skipPaging) {
751
            $this->paging();
752
        }
753
    }
754
755
    /**
756
     * Transform output.
757
     *
758
     * @param mixed $results
759
     * @param mixed $processed
760
     * @return array
761
     */
762
    protected function transform($results, $processed)
763
    {
764
        if (isset($this->transformer) && class_exists('Yajra\\DataTables\\Transformers\\FractalTransformer')) {
765
            return app('datatables.transformer')->transform(
766
                $results,
767
                $this->transformer,
768
                $this->serializer ?? null
769
            );
770
        }
771
772
        return Helper::transform($processed);
773
    }
774
775
    /**
776
     * Get processed data.
777
     *
778
     * @param mixed $results
779
     * @param bool  $object
780
     * @return array
781
     */
782
    protected function processResults($results, $object = false)
783
    {
784
        $processor = new DataProcessor(
785
            $results,
786
            $this->getColumnsDefinition(),
787
            $this->templates,
788
            $this->request->input('start')
789
        );
790
791
        return $processor->process($object);
792
    }
793
794
    /**
795
     * Render json response.
796
     *
797
     * @param array $data
798
     * @return \Illuminate\Http\JsonResponse
799
     */
800
    protected function render(array $data)
801
    {
802
        $output = $this->attachAppends([
803
            'draw'            => (int) $this->request->input('draw'),
804
            'recordsTotal'    => $this->totalRecords,
805
            'recordsFiltered' => $this->filteredRecords,
806
            'data'            => $data,
807
        ]);
808
809
        if ($this->config->isDebugging()) {
810
            $output = $this->showDebugger($output);
811
        }
812
813
        foreach ($this->searchPanes as $column => $searchPane) {
814
            $output['searchPanes']['options'][$column] = $searchPane['options'];
815
        }
816
817
        return new JsonResponse(
818
            $output,
819
            200,
820
            $this->config->get('datatables.json.header', []),
821
            $this->config->get('datatables.json.options', 0)
822
        );
823
    }
824
825
    /**
826
     * Attach custom with meta on response.
827
     *
828
     * @param array $data
829
     * @return array
830
     */
831
    protected function attachAppends(array $data)
832
    {
833
        return array_merge($data, $this->appends);
834
    }
835
836
    /**
837
     * Append debug parameters on output.
838
     *
839
     * @param  array $output
840
     * @return array
841
     */
842
    protected function showDebugger(array $output)
843
    {
844
        $output['input'] = $this->request->all();
845
846
        return $output;
847
    }
848
849
    /**
850
     * Return an error json response.
851
     *
852
     * @param \Exception $exception
853
     * @return \Illuminate\Http\JsonResponse
854
     * @throws \Yajra\DataTables\Exceptions\Exception
855
     */
856
    protected function errorResponse(\Exception $exception)
857
    {
858
        $error = $this->config->get('datatables.error');
859
        $debug = $this->config->get('app.debug');
860
861
        if ($error === 'throw' || (! $error && ! $debug)) {
862
            throw new Exception($exception->getMessage(), $code = 0, $exception);
863
        }
864
865
        $this->getLogger()->error($exception);
866
867
        return new JsonResponse([
868
            'draw'            => (int) $this->request->input('draw'),
869
            'recordsTotal'    => $this->totalRecords,
870
            'recordsFiltered' => 0,
871
            'data'            => [],
872
            'error'           => $error ? __($error) : "Exception Message:\n\n".$exception->getMessage(),
873
        ]);
874
    }
875
876
    /**
877
     * Get monolog/logger instance.
878
     *
879
     * @return \Psr\Log\LoggerInterface
880
     */
881
    public function getLogger()
882
    {
883
        $this->logger = $this->logger ?: app(LoggerInterface::class);
884
885
        return $this->logger;
886
    }
887
888
    /**
889
     * Set monolog/logger instance.
890
     *
891
     * @param \Psr\Log\LoggerInterface $logger
892
     * @return $this
893
     */
894
    public function setLogger(LoggerInterface $logger)
895
    {
896
        $this->logger = $logger;
897
898
        return $this;
899
    }
900
901
    /**
902
     * Setup search keyword.
903
     *
904
     * @param  string $value
905
     * @return string
906
     */
907
    protected function setupKeyword($value)
908
    {
909
        if ($this->config->isSmartSearch()) {
910
            $keyword = '%'.$value.'%';
911
            if ($this->config->isWildcard()) {
912
                $keyword = Helper::wildcardLikeString($value);
913
            }
914
            // remove escaping slash added on js script request
915
            $keyword = str_replace('\\', '%', $keyword);
916
917
            return $keyword;
918
        }
919
920
        return $value;
921
    }
922
923
    /**
924
     * Get column name to be use for filtering and sorting.
925
     *
926
     * @param int  $index
927
     * @param bool $wantsAlias
928
     * @return string
929
     */
930
    protected function getColumnName($index, $wantsAlias = false)
931
    {
932
        $column = $this->request->columnName($index);
933
934
        // DataTables is using make(false)
935
        if (is_numeric($column)) {
936
            $column = $this->getColumnNameByIndex($index);
937
        }
938
939
        if (Str::contains(Str::upper($column), ' AS ')) {
940
            $column = Helper::extractColumnName($column, $wantsAlias);
941
        }
942
943
        return $column;
944
    }
945
946
    /**
947
     * Get column name by order column index.
948
     *
949
     * @param int $index
950
     * @return string
951
     */
952
    protected function getColumnNameByIndex($index)
953
    {
954
        $name = (isset($this->columns[$index]) && $this->columns[$index] != '*')
955
            ? $this->columns[$index] : $this->getPrimaryKeyName();
956
957
        return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name;
958
    }
959
960
    /**
961
     * If column name could not be resolved then use primary key.
962
     *
963
     * @return string
964
     */
965
    protected function getPrimaryKeyName()
966
    {
967
        return 'id';
968
    }
969
970
    /**
971
     * Add a search pane options on response.
972
     *
973
     * @param string $column
974
     * @param mixed $options
975
     * @param callable|null $builder
976
     * @return $this
977
     */
978
    public function searchPane($column, $options, callable $builder = null)
979
    {
980
        $options = value($options);
981
982
        if ($options instanceof Arrayable) {
983
            $options = $options->toArray();
984
        }
985
986
        $this->searchPanes[$column]['options'] = $options;
987
        $this->searchPanes[$column]['builder'] = $builder;
988
989
        return $this;
990
    }
991
}
992