Completed
Push — master ( 12f4bf...b1bb78 )
by Arjay
01:52
created

CollectionDataTable::getSorter()   B

Complexity

Conditions 9
Paths 1

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 1
nop 1
dl 0
loc 37
rs 7.7724
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\DataTables;
4
5
use Illuminate\Contracts\Support\Arrayable;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Str;
9
10
class CollectionDataTable extends DataTableAbstract
11
{
12
    /**
13
     * Collection object.
14
     *
15
     * @var \Illuminate\Support\Collection
16
     */
17
    public $collection;
18
19
    /**
20
     * Collection object.
21
     *
22
     * @var \Illuminate\Support\Collection
23
     */
24
    public $original;
25
26
    /**
27
     * The offset of the first record in the full dataset.
28
     *
29
     * @var int
30
     */
31
    private $offset = 0;
32
33
    /**
34
     * Can the DataTable engine be created with these parameters.
35
     *
36
     * @param mixed $source
37
     * @return bool
38
     */
39
    public static function canCreate($source)
40
    {
41
        return is_array($source) || $source instanceof Collection;
42
    }
43
44
    /**
45
     * Factory method, create and return an instance for the DataTable engine.
46
     *
47
     * @param array|\Illuminate\Support\Collection $source
48
     * @return CollectionDataTable|DataTableAbstract
49
     */
50
    public static function create($source)
51
    {
52
        if (is_array($source)) {
53
            $source = new Collection($source);
54
        }
55
56
        return parent::create($source);
57
    }
58
59
    /**
60
     * CollectionEngine constructor.
61
     *
62
     * @param \Illuminate\Support\Collection $collection
63
     */
64
    public function __construct(Collection $collection)
65
    {
66
        $this->request    = app('datatables.request');
67
        $this->config     = app('datatables.config');
68
        $this->collection = $collection;
69
        $this->original   = $collection;
70
        $this->columns    = array_keys($this->serialize($collection->first()));
71
    }
72
73
    /**
74
     * Serialize collection.
75
     *
76
     * @param  mixed $collection
77
     * @return mixed|null
78
     */
79
    protected function serialize($collection)
80
    {
81
        return $collection instanceof Arrayable ? $collection->toArray() : (array) $collection;
82
    }
83
84
    /**
85
     * Count results.
86
     *
87
     * @return int
88
     */
89
    public function count()
90
    {
91
        return $this->collection->count() > $this->totalRecords ? $this->totalRecords : $this->collection->count();
92
    }
93
94
    /**
95
     * Perform column search.
96
     *
97
     * @return void
98
     */
99
    public function columnSearch()
100
    {
101
        $columns = $this->request->get('columns', []);
102
        for ($i = 0, $c = count($columns); $i < $c; $i++) {
103
            $column  = $this->getColumnName($i);
104
105
            if (! $this->request->isColumnSearchable($i) || $this->isBlacklisted($column)) {
106
                continue;
107
            }
108
109
            $this->isFilterApplied = true;
110
111
            $regex   = $this->request->isRegex($i);
112
            $keyword = $this->request->columnKeyword($i);
113
114
            $this->collection = $this->collection->filter(
115
                function ($row) use ($column, $keyword, $regex) {
116
                    $data = $this->serialize($row);
117
118
                    $value = Arr::get($data, $column);
119
120
                    if ($this->config->isCaseInsensitive()) {
121
                        if ($regex) {
122
                            return preg_match('/' . $keyword . '/i', $value) == 1;
123
                        }
124
125
                        return strpos(Str::lower($value), Str::lower($keyword)) !== false;
126
                    }
127
128
                    if ($regex) {
129
                        return preg_match('/' . $keyword . '/', $value) == 1;
130
                    }
131
132
                    return strpos($value, $keyword) !== false;
133
                }
134
            );
135
        }
136
    }
137
138
    /**
139
     * Perform pagination.
140
     *
141
     * @return void
142
     */
143
    public function paging()
144
    {
145
        $this->collection = $this->collection->slice(
146
            $this->request->input('start') - $this->offset,
147
            (int) $this->request->input('length') > 0 ? $this->request->input('length') : 10
148
        );
149
    }
150
151
    /**
152
     * Organizes works.
153
     *
154
     * @param bool $mDataSupport
155
     * @return \Illuminate\Http\JsonResponse
156
     */
157
    public function make($mDataSupport = true)
158
    {
159
        try {
160
            $this->totalRecords = $this->totalCount();
161
162
            if ($this->totalRecords) {
163
                $results   = $this->results();
164
                $processed = $this->processResults($results, $mDataSupport);
165
                $output    = $this->transform($results, $processed);
166
167
                $this->collection = collect($output);
168
                $this->ordering();
169
                $this->filterRecords();
170
                $this->paginate();
171
172
                $this->revertIndexColumn($mDataSupport);
173
            }
174
175
            return $this->render($this->collection->values()->all());
176
        } catch (\Exception $exception) {
177
            return $this->errorResponse($exception);
178
        }
179
    }
180
181
    /**
182
     * Count total items.
183
     *
184
     * @return int
185
     */
186
    public function totalCount()
187
    {
188
        return $this->totalRecords ? $this->totalRecords : $this->collection->count();
189
    }
190
191
    /**
192
     * Get results.
193
     *
194
     * @return mixed
195
     */
196
    public function results()
197
    {
198
        return $this->collection->all();
199
    }
200
201
    /**
202
     * Revert transformed DT_RowIndex back to it's original values.
203
     *
204
     * @param bool $mDataSupport
205
     */
206
    private function revertIndexColumn($mDataSupport)
207
    {
208
        if ($this->columnDef['index']) {
209
            $index = $mDataSupport ? config('datatables.index_column', 'DT_RowIndex') : 0;
210
            $start = (int) $this->request->input('start');
211
            $this->collection->transform(function ($data) use ($index, &$start) {
212
                $data[$index] = ++$start;
213
214
                return $data;
215
            });
216
        }
217
    }
218
219
    /**
220
     * Perform global search for the given keyword.
221
     *
222
     * @param string $keyword
223
     */
224
    protected function globalSearch($keyword)
225
    {
226
        $keyword = $this->config->isCaseInsensitive() ? Str::lower($keyword) : $keyword;
227
228
        $this->collection = $this->collection->filter(function ($row) use ($keyword) {
229
            $this->isFilterApplied = true;
230
231
            $data = $this->serialize($row);
232
            foreach ($this->request->searchableColumnIndex() as $index) {
233
                $column = $this->getColumnName($index);
234
                $value = Arr::get($data, $column);
235
                if (! $value || is_array($value)) {
236
                    if (! is_numeric($value)) {
237
                        continue;
238
                    }
239
240
                    $value = (string) $value;
241
                }
242
243
                $value = $this->config->isCaseInsensitive() ? Str::lower($value) : $value;
244
                if (Str::contains($value, $keyword)) {
245
                    return true;
246
                }
247
            }
248
249
            return false;
250
        });
251
    }
252
253
    /**
254
     * Perform default query orderBy clause.
255
     */
256
    protected function defaultOrdering()
257
    {
258
        $criteria = $this->request->orderableColumns();
259
        if (! empty($criteria)) {
260
            $sorter = $this->getSorter($criteria);
261
262
            $this->collection = $this->collection
263
                ->map(function ($data) {
264
                    return Arr::dot($data);
265
                })
266
                ->sort($sorter)
267
                ->map(function ($data) {
268
                    foreach ($data as $key => $value) {
269
                        unset($data[$key]);
270
                        Arr::set($data, $key, $value);
271
                    }
272
273
                    return $data;
274
                });
275
        }
276
    }
277
278
    /**
279
     * Get array sorter closure.
280
     *
281
     * @param array $criteria
282
     * @return \Closure
283
     */
284
    protected function getSorter(array $criteria)
285
    {
286
        $sorter = function ($a, $b) use ($criteria) {
287
            foreach ($criteria as $orderable) {
288
                $column    = $this->getColumnName($orderable['column']);
289
                $direction = $orderable['direction'];
290
                if ($direction === 'desc') {
291
                    $first  = $b;
292
                    $second = $a;
293
                } else {
294
                    $first  = $a;
295
                    $second = $b;
296
                }
297
                if (is_numeric($first[$column] ?? null) && is_numeric($second[$column] ?? null)) {
298
                    if ($first[$column] < $second[$column]) {
299
                        $cmp = -1;
300
                    } elseif ($first[$column] > $second[$column]) {
301
                        $cmp = 1;
302
                    } else {
303
                        $cmp = 0;
304
                    }
305
                } elseif ($this->config->isCaseInsensitive()) {
306
                    $cmp = strnatcasecmp($first[$column] ?? null, $second[$column] ?? null);
307
                } else {
308
                    $cmp = strnatcmp($first[$column] ?? null, $second[$column] ?? null);
309
                }
310
                if ($cmp != 0) {
311
                    return $cmp;
312
                }
313
            }
314
315
            // all elements were equal
316
            return 0;
317
        };
318
319
        return $sorter;
320
    }
321
322
    /**
323
     * Resolve callback parameter instance.
324
     *
325
     * @return $this
326
     */
327
    protected function resolveCallbackParameter()
328
    {
329
        return $this;
330
    }
331
332
    /**
333
     * Define the offset of the first item of the collection with respect to
334
     * the FULL dataset the collection was sliced from. It effectively allows the
335
     * collection to be "pre-sliced".
336
     *
337
     * @param int $offset
338
     * @return $this
339
     */
340
    public function setOffset(int $offset)
341
    {
342
        $this->offset = $offset;
343
344
        return $this;
345
    }
346
}
347