Completed
Push — master ( 2cf285...52af1e )
by Arjay
07:49
created

BaseEngine::smart()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
rs 9.4285
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\DataTableEngineContract;
9
use Yajra\Datatables\Exception;
10
use Yajra\Datatables\Helper;
11
use Yajra\Datatables\Processors\DataProcessor;
12
13
/**
14
 * Class BaseEngine.
15
 *
16
 * @package Yajra\Datatables\Engines
17
 * @author  Arjay Angeles <[email protected]>
18
 */
19
abstract class BaseEngine implements DataTableEngineContract
20
{
21
    /**
22
     * Datatables Request object.
23
     *
24
     * @var \Yajra\Datatables\Request
25
     */
26
    public $request;
27
28
    /**
29
     * @var \Illuminate\Contracts\Logging\Log
30
     */
31
    protected $logger;
32
33
    /**
34
     * Array of result columns/fields.
35
     *
36
     * @var array
37
     */
38
    protected $columns = [];
39
40
    /**
41
     * DT columns definitions container (add/edit/remove/filter/order/escape).
42
     *
43
     * @var array
44
     */
45
    protected $columnDef = [
46
        'index'     => false,
47
        'append'    => [],
48
        'edit'      => [],
49
        'excess'    => ['rn', 'row_num'],
50
        'filter'    => [],
51
        'order'     => [],
52
        'escape'    => '*',
53
        'raw'       => ['action'],
54
        'blacklist' => ['password', 'remember_token'],
55
        'whitelist' => '*',
56
    ];
57
58
    /**
59
     * Extra/Added columns.
60
     *
61
     * @var array
62
     */
63
    protected $extraColumns = [];
64
65
    /**
66
     * Total records.
67
     *
68
     * @var int
69
     */
70
    protected $totalRecords = 0;
71
72
    /**
73
     * Total filtered records.
74
     *
75
     * @var int
76
     */
77
    protected $filteredRecords = 0;
78
79
    /**
80
     * Auto-filter flag.
81
     *
82
     * @var bool
83
     */
84
    protected $autoFilter = true;
85
86
    /**
87
     * Callback to override global search.
88
     *
89
     * @var callable
90
     */
91
    protected $filterCallback;
92
93
    /**
94
     * Parameters to passed on filterCallback.
95
     *
96
     * @var mixed
97
     */
98
    protected $filterCallbackParameters;
99
100
    /**
101
     * DT row templates container.
102
     *
103
     * @var array
104
     */
105
    protected $templates = [
106
        'DT_RowId'    => '',
107
        'DT_RowClass' => '',
108
        'DT_RowData'  => [],
109
        'DT_RowAttr'  => [],
110
    ];
111
112
    /**
113
     * Output transformer.
114
     *
115
     * @var \League\Fractal\TransformerAbstract
116
     */
117
    protected $transformer = null;
118
119
    /**
120
     * Database prefix.
121
     *
122
     * @var string
123
     */
124
    protected $prefix;
125
126
    /**
127
     * Database driver used.
128
     *
129
     * @var string
130
     */
131
    protected $database;
132
133
    /**
134
     * [internal] Track if any filter was applied for at least one column.
135
     *
136
     * @var boolean
137
     */
138
    protected $isFilterApplied = false;
139
140
    /**
141
     * Fractal serializer class.
142
     *
143
     * @var string|null
144
     */
145
    protected $serializer = null;
146
147
    /**
148
     * Custom ordering callback.
149
     *
150
     * @var callable
151
     */
152
    protected $orderCallback;
153
154
    /**
155
     * Skip paginate as needed.
156
     *
157
     * @var bool
158
     */
159
    protected $skipPaging = false;
160
161
    /**
162
     * Array of data to append on json response.
163
     *
164
     * @var array
165
     */
166
    protected $appends = [];
167
168
    /**
169
     * Flag for ordering NULLS LAST option.
170
     *
171
     * @var bool
172
     */
173
    protected $nullsLast = false;
174
175
    /**
176
     * Add column in collection.
177
     *
178
     * @param string $name
179
     * @param string|callable $content
180
     * @param bool|int $order
181
     * @return $this
182
     */
183
    public function addColumn($name, $content, $order = false)
184
    {
185
        $this->extraColumns[] = $name;
186
187
        $this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order];
188
189
        return $this;
190
    }
191
192
    /**
193
     * Add DT row index column on response.
194
     *
195
     * @return $this
196
     */
197
    public function addIndexColumn()
198
    {
199
        $this->columnDef['index'] = true;
200
201
        return $this;
202
    }
203
204
    /**
205
     * Edit column's content.
206
     *
207
     * @param string $name
208
     * @param string|callable $content
209
     * @return $this
210
     */
211
    public function editColumn($name, $content)
212
    {
213
        $this->columnDef['edit'][] = ['name' => $name, 'content' => $content];
214
215
        return $this;
216
    }
217
218
    /**
219
     * Remove column from collection.
220
     *
221
     * @return $this
222
     */
223
    public function removeColumn()
224
    {
225
        $names                     = func_get_args();
226
        $this->columnDef['excess'] = array_merge($this->columnDef['excess'], $names);
227
228
        return $this;
229
    }
230
231
    /**
232
     * Declare columns to escape values.
233
     *
234
     * @param string|array $columns
235
     * @return $this
236
     */
237
    public function escapeColumns($columns = '*')
238
    {
239
        $this->columnDef['escape'] = $columns;
240
241
        return $this;
242
    }
243
244
    /**
245
     * Set columns that should not be escaped.
246
     *
247
     * @param array $columns
248
     * @return $this
249
     */
250
    public function rawColumns(array $columns)
251
    {
252
        $this->columnDef['raw'] = $columns;
253
254
        return $this;
255
    }
256
257
    /**
258
     * Sets DT_RowClass template.
259
     * result: <tr class="output_from_your_template">.
260
     *
261
     * @param string|callable $content
262
     * @return $this
263
     */
264
    public function setRowClass($content)
265
    {
266
        $this->templates['DT_RowClass'] = $content;
267
268
        return $this;
269
    }
270
271
    /**
272
     * Sets DT_RowId template.
273
     * result: <tr id="output_from_your_template">.
274
     *
275
     * @param string|callable $content
276
     * @return $this
277
     */
278
    public function setRowId($content)
279
    {
280
        $this->templates['DT_RowId'] = $content;
281
282
        return $this;
283
    }
284
285
    /**
286
     * Set DT_RowData templates.
287
     *
288
     * @param array $data
289
     * @return $this
290
     */
291
    public function setRowData(array $data)
292
    {
293
        $this->templates['DT_RowData'] = $data;
294
295
        return $this;
296
    }
297
298
    /**
299
     * Add DT_RowData template.
300
     *
301
     * @param string $key
302
     * @param string|callable $value
303
     * @return $this
304
     */
305
    public function addRowData($key, $value)
306
    {
307
        $this->templates['DT_RowData'][$key] = $value;
308
309
        return $this;
310
    }
311
312
    /**
313
     * Set DT_RowAttr templates.
314
     * result: <tr attr1="attr1" attr2="attr2">.
315
     *
316
     * @param array $data
317
     * @return $this
318
     */
319
    public function setRowAttr(array $data)
320
    {
321
        $this->templates['DT_RowAttr'] = $data;
322
323
        return $this;
324
    }
325
326
    /**
327
     * Add DT_RowAttr template.
328
     *
329
     * @param string $key
330
     * @param string|callable $value
331
     * @return $this
332
     */
333
    public function addRowAttr($key, $value)
334
    {
335
        $this->templates['DT_RowAttr'][$key] = $value;
336
337
        return $this;
338
    }
339
340
    /**
341
     * Add custom filter handler for the give column.
342
     *
343
     * @param string $column
344
     * @param callable $callback
345
     * @return $this
346
     */
347
    public function filterColumn($column, callable $callback)
348
    {
349
        $this->columnDef['filter'][$column] = ['method' => $callback];
350
351
        return $this;
352
    }
353
354
    /**
355
     * Order each given columns versus the given custom sql.
356
     *
357
     * @param array $columns
358
     * @param string $sql
359
     * @param array $bindings
360
     * @return $this
361
     */
362
    public function orderColumns(array $columns, $sql, $bindings = [])
363
    {
364
        foreach ($columns as $column) {
365
            $this->orderColumn($column, str_replace(':column', $column, $sql), $bindings);
366
        }
367
368
        return $this;
369
    }
370
371
    /**
372
     * Override default column ordering.
373
     *
374
     * @param string $column
375
     * @param string $sql
376
     * @param array $bindings
377
     * @return $this
378
     * @internal string $1 Special variable that returns the requested order direction of the column.
379
     */
380
    public function orderColumn($column, $sql, $bindings = [])
381
    {
382
        $this->columnDef['order'][$column] = compact('sql', 'bindings');
383
384
        return $this;
385
    }
386
387
    /**
388
     * Set data output transformer.
389
     *
390
     * @param \League\Fractal\TransformerAbstract $transformer
391
     * @return $this
392
     */
393
    public function setTransformer($transformer)
394
    {
395
        $this->transformer = $transformer;
396
397
        return $this;
398
    }
399
400
    /**
401
     * Set fractal serializer class.
402
     *
403
     * @param string $serializer
404
     * @return $this
405
     */
406
    public function setSerializer($serializer)
407
    {
408
        $this->serializer = $serializer;
409
410
        return $this;
411
    }
412
413
    /**
414
     * Organizes works.
415
     *
416
     * @param bool $mDataSupport
417
     * @return \Illuminate\Http\JsonResponse
418
     * @throws \Exception
419
     */
420
    public function make($mDataSupport = false)
421
    {
422
        try {
423
            $this->totalRecords = $this->totalCount();
424
425
            if ($this->totalRecords) {
426
                $this->filterRecords();
427
                $this->ordering();
428
                $this->paginate();
429
            }
430
431
            $data = $this->transform($this->getProcessedData($mDataSupport));
432
433
            return $this->render($data);
434
        } catch (\Exception $exception) {
435
            return $this->errorResponse($exception);
436
        }
437
    }
438
439
    /**
440
     * Perform necessary filters.
441
     *
442
     * @return void
443
     */
444
    protected function filterRecords()
445
    {
446
        if ($this->autoFilter && $this->request->isSearchable()) {
447
            $this->filtering();
448
        }
449
450
        if (is_callable($this->filterCallback)) {
451
            call_user_func($this->filterCallback, $this->filterCallbackParameters);
452
        }
453
454
        $this->columnSearch();
455
        $this->filteredRecords = $this->isFilterApplied ? $this->count() : $this->totalRecords;
456
    }
457
458
    /**
459
     * Apply pagination.
460
     *
461
     * @return void
462
     */
463
    protected function paginate()
464
    {
465
        if ($this->request->isPaginationable() && !$this->skipPaging) {
466
            $this->paging();
467
        }
468
    }
469
470
    /**
471
     * Transform output.
472
     *
473
     * @param mixed $output
474
     * @return array
475
     */
476
    protected function transform($output)
477
    {
478
        if (!isset($this->transformer)) {
479
            return Helper::transform($output);
480
        }
481
482
        $fractal = app('datatables.fractal');
483
484
        if ($this->serializer) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->serializer of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
485
            $fractal->setSerializer($this->createSerializer());
486
        }
487
488
        //Get transformer reflection
489
        //Firs method parameter should be data/object to transform
490
        $reflection = new \ReflectionMethod($this->transformer, 'transform');
491
        $parameter  = $reflection->getParameters()[0];
492
493
        //If parameter is class assuming it requires object
494
        //Else just pass array by default
495
        if ($parameter->getClass()) {
496
            $resource = new Collection($output, $this->createTransformer());
497
        } else {
498
            $resource = new Collection(
499
                $output,
500
                $this->createTransformer()
501
            );
502
        }
503
504
        $collection = $fractal->createData($resource)->toArray();
505
506
        return $collection['data'];
507
    }
508
509
    /**
510
     * Get or create transformer serializer instance.
511
     *
512
     * @return \League\Fractal\Serializer\SerializerAbstract
513
     */
514
    protected function createSerializer()
515
    {
516
        if ($this->serializer instanceof \League\Fractal\Serializer\SerializerAbstract) {
0 ignored issues
show
Bug introduced by
The class League\Fractal\Serializer\SerializerAbstract does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
517
            return $this->serializer;
518
        }
519
520
        return new $this->serializer();
521
    }
522
523
    /**
524
     * Get or create transformer instance.
525
     *
526
     * @return \League\Fractal\TransformerAbstract
527
     */
528
    protected function createTransformer()
529
    {
530
        if ($this->transformer instanceof \League\Fractal\TransformerAbstract) {
0 ignored issues
show
Bug introduced by
The class League\Fractal\TransformerAbstract does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
531
            return $this->transformer;
532
        }
533
534
        return new $this->transformer();
535
    }
536
537
    /**
538
     * Get processed data.
539
     *
540
     * @param bool|false $object
541
     * @return array
542
     */
543
    protected function getProcessedData($object = false)
544
    {
545
        $processor = new DataProcessor(
546
            $this->results(),
547
            $this->getColumnsDefinition(),
548
            $this->templates,
549
            $this->request->input('start')
550
        );
551
552
        return $processor->process($object);
553
    }
554
555
    /**
556
     * Get columns definition.
557
     *
558
     * @return array
559
     */
560
    protected function getColumnsDefinition()
561
    {
562
        $config  = config('datatables.columns');
563
        $allowed = ['excess', 'escape', 'raw', 'blacklist', 'whitelist'];
564
565
        return array_merge(array_only($config, $allowed), $this->columnDef);
566
    }
567
568
    /**
569
     * Render json response.
570
     *
571
     * @param array $data
572
     * @return \Illuminate\Http\JsonResponse
573
     */
574
    protected function render(array $data)
575
    {
576
        $output = array_merge([
577
            'draw'            => (int) $this->request->input('draw'),
578
            'recordsTotal'    => $this->totalRecords,
579
            'recordsFiltered' => $this->filteredRecords,
580
            'data'            => $data,
581
        ], $this->appends);
582
583
        if ($this->isDebugging()) {
584
            $output = $this->showDebugger($output);
585
        }
586
587
        return new JsonResponse(
588
            $output,
589
            200,
590
            config('datatables.json.header', []),
591
            config('datatables.json.options', 0)
592
        );
593
    }
594
595
    /**
596
     * Check if app is in debug mode.
597
     *
598
     * @return bool
599
     */
600
    public function isDebugging()
601
    {
602
        return config('app.debug', false);
603
    }
604
605
    /**
606
     * Append debug parameters on output.
607
     *
608
     * @param  array $output
609
     * @return array
610
     */
611
    abstract protected function showDebugger(array $output);
612
613
    /**
614
     * Return an error json response.
615
     *
616
     * @param \Exception $exception
617
     * @return \Illuminate\Http\JsonResponse
618
     * @throws \Yajra\Datatables\Exception
619
     */
620
    protected function errorResponse(\Exception $exception)
621
    {
622
        $error = config('datatables.error');
623
        if ($error === 'throw') {
624
            throw new Exception($exception->getMessage(), $code = 0, $exception);
625
        }
626
627
        $this->getLogger()->error($exception);
628
629
        return new JsonResponse([
630
            'draw'            => (int) $this->request->input('draw'),
631
            'recordsTotal'    => (int) $this->totalRecords,
632
            'recordsFiltered' => 0,
633
            'data'            => [],
634
            'error'           => $error ? __($error) : "Exception Message:\n\n" . $exception->getMessage(),
635
        ]);
636
    }
637
638
    /**
639
     * Get monolog/logger instance.
640
     *
641
     * @return \Illuminate\Contracts\Logging\Log
642
     */
643
    public function getLogger()
644
    {
645
        $this->logger = $this->logger ?: resolve(Log::class);
646
647
        return $this->logger;
648
    }
649
650
    /**
651
     * Set monolog/logger instance.
652
     *
653
     * @param \Illuminate\Contracts\Logging\Log $logger
654
     * @return $this
655
     */
656
    public function setLogger(Log $logger)
657
    {
658
        $this->logger = $logger;
659
660
        return $this;
661
    }
662
663
    /**
664
     * Check if config uses case insensitive search.
665
     *
666
     * @return bool
667
     */
668
    public function isCaseInsensitive()
669
    {
670
        return config('datatables.search.case_insensitive', false);
671
    }
672
673
    /**
674
     * Append data on json response.
675
     *
676
     * @param mixed $key
677
     * @param mixed $value
678
     * @return $this
679
     */
680
    public function with($key, $value = '')
681
    {
682
        if (is_array($key)) {
683
            $this->appends = $key;
684
        } elseif (is_callable($value)) {
685
            $this->appends[$key] = value($value);
686
        } else {
687
            $this->appends[$key] = value($value);
688
        }
689
690
        return $this;
691
    }
692
693
    /**
694
     * Override default ordering method with a closure callback.
695
     *
696
     * @param callable $closure
697
     * @return $this
698
     */
699
    public function order(callable $closure)
700
    {
701
        $this->orderCallback = $closure;
702
703
        return $this;
704
    }
705
706
    /**
707
     * Update list of columns that is not allowed for search/sort.
708
     *
709
     * @param  array $blacklist
710
     * @return $this
711
     */
712
    public function blacklist(array $blacklist)
713
    {
714
        $this->columnDef['blacklist'] = $blacklist;
715
716
        return $this;
717
    }
718
719
    /**
720
     * Update list of columns that is allowed for search/sort.
721
     *
722
     * @param  string|array $whitelist
723
     * @return $this
724
     */
725
    public function whitelist($whitelist = '*')
726
    {
727
        $this->columnDef['whitelist'] = $whitelist;
728
729
        return $this;
730
    }
731
732
    /**
733
     * Set smart search config at runtime.
734
     *
735
     * @param bool $bool
736
     * @return $this
737
     */
738
    public function smart($bool = true)
739
    {
740
        config(['datatables.search.smart' => $bool]);
741
742
        return $this;
743
    }
744
745
    /**
746
     * Set total records manually.
747
     *
748
     * @param int $total
749
     * @return $this
750
     */
751
    public function setTotalRecords($total)
752
    {
753
        $this->totalRecords = $total;
754
755
        return $this;
756
    }
757
758
    /**
759
     * Skip pagination as needed.
760
     *
761
     * @return $this
762
     */
763
    public function skipPaging()
764
    {
765
        $this->skipPaging = true;
766
767
        return $this;
768
    }
769
770
    /**
771
     * Check if the current sql language is based on oracle syntax.
772
     *
773
     * @return bool
774
     */
775
    public function isOracleSql()
776
    {
777
        return in_array($this->database, ['oracle', 'oci8']);
778
    }
779
780
    /**
781
     * Set datatables to do ordering with NULLS LAST option.
782
     *
783
     * @return $this
784
     */
785
    public function orderByNullsLast()
786
    {
787
        $this->nullsLast = true;
788
789
        return $this;
790
    }
791
792
    /**
793
     * Push a new column name to blacklist.
794
     *
795
     * @param string $column
796
     * @return $this
797
     */
798
    public function pushToBlacklist($column)
799
    {
800
        if (!$this->isBlacklisted($column)) {
801
            array_push($this->columnDef['blacklist'], $column);
802
        }
803
804
        return $this;
805
    }
806
807
    /**
808
     * Check if column is blacklisted.
809
     *
810
     * @param string $column
811
     * @return bool
812
     */
813
    protected function isBlacklisted($column)
814
    {
815
        if (in_array($column, $this->columnDef['blacklist'])) {
816
            return true;
817
        }
818
819
        if ($this->columnDef['whitelist'] === '*' || in_array($column, $this->columnDef['whitelist'])) {
820
            return false;
821
        }
822
823
        return true;
824
    }
825
826
    /**
827
     * Setup search keyword.
828
     *
829
     * @param  string $value
830
     * @return string
831
     */
832
    protected function setupKeyword($value)
833
    {
834
        if ($this->isSmartSearch()) {
835
            $keyword = '%' . $value . '%';
836
            if ($this->isWildcard()) {
837
                $keyword = $this->wildcardLikeString($value);
838
            }
839
            // remove escaping slash added on js script request
840
            $keyword = str_replace('\\', '%', $keyword);
841
842
            return $keyword;
843
        }
844
845
        return $value;
846
    }
847
848
    /**
849
     * Check if config uses smart search.
850
     *
851
     * @return bool
852
     */
853
    public function isSmartSearch()
854
    {
855
        return config('datatables.search.smart', true);
856
    }
857
858
    /**
859
     * Check if config uses wild card search.
860
     *
861
     * @return bool
862
     */
863
    public function isWildcard()
864
    {
865
        return config('datatables.search.use_wildcards', false);
866
    }
867
868
    /**
869
     * Adds % wildcards to the given string.
870
     *
871
     * @param string $str
872
     * @param bool $lowercase
873
     * @return string
874
     */
875
    protected function wildcardLikeString($str, $lowercase = true)
876
    {
877
        $wild  = '%';
878
        $chars = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
879
880
        if (count($chars) > 0) {
881
            foreach ($chars as $char) {
882
                $wild .= $char . '%';
883
            }
884
        }
885
886
        if ($lowercase) {
887
            $wild = Str::lower($wild);
888
        }
889
890
        return $wild;
891
    }
892
893
    /**
894
     * Update flags to disable global search.
895
     *
896
     * @param  callable $callback
897
     * @param  mixed $parameters
898
     * @param  bool $autoFilter
899
     */
900
    protected function overrideGlobalSearch(callable $callback, $parameters, $autoFilter = false)
901
    {
902
        $this->autoFilter               = $autoFilter;
903
        $this->isFilterApplied          = true;
904
        $this->filterCallback           = $callback;
905
        $this->filterCallbackParameters = $parameters;
906
    }
907
908
    /**
909
     * Get column name to be use for filtering and sorting.
910
     *
911
     * @param integer $index
912
     * @param bool $wantsAlias
913
     * @return string
914
     */
915
    protected function getColumnName($index, $wantsAlias = false)
916
    {
917
        $column = $this->request->columnName($index);
918
919
        // DataTables is using make(false)
920
        if (is_numeric($column)) {
921
            $column = $this->getColumnNameByIndex($index);
922
        }
923
924
        if (Str::contains(Str::upper($column), ' AS ')) {
925
            $column = $this->extractColumnName($column, $wantsAlias);
926
        }
927
928
        return $column;
929
    }
930
931
    /**
932
     * Get column name by order column index.
933
     *
934
     * @param int $index
935
     * @return string
936
     */
937
    protected function getColumnNameByIndex($index)
938
    {
939
        $name = (isset($this->columns[$index]) && $this->columns[$index] != '*') ? $this->columns[$index] : $this->getPrimaryKeyName();
940
941
        return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name;
942
    }
943
944
    /**
945
     * If column name could not be resolved then use primary key.
946
     *
947
     * @return string
948
     */
949
    protected function getPrimaryKeyName()
950
    {
951
        return 'id';
952
    }
953
954
    /**
955
     * Get column name from string.
956
     *
957
     * @param string $str
958
     * @param bool $wantsAlias
959
     * @return string
960
     */
961
    protected function extractColumnName($str, $wantsAlias)
962
    {
963
        $matches = explode(' as ', Str::lower($str));
964
965
        if (!empty($matches)) {
966
            if ($wantsAlias) {
967
                return array_pop($matches);
968
            } else {
969
                return array_shift($matches);
970
            }
971
        } elseif (strpos($str, '.')) {
972
            $array = explode('.', $str);
973
974
            return array_pop($array);
975
        }
976
977
        return $str;
978
    }
979
}
980