1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Yajra\DataTables; |
4
|
|
|
|
5
|
|
|
use Illuminate\Support\Arr; |
6
|
|
|
use Illuminate\Support\Str; |
7
|
|
|
use Illuminate\Support\Collection; |
8
|
|
|
use Illuminate\Contracts\Support\Arrayable; |
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 array_dot($data); |
265
|
|
|
}) |
266
|
|
|
->sort($sorter) |
267
|
|
|
->map(function ($data) { |
268
|
|
|
foreach ($data as $key => $value) { |
269
|
|
|
unset($data[$key]); |
270
|
|
|
array_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 ($this->config->isCaseInsensitive()) { |
298
|
|
|
$cmp = strnatcasecmp($first[$column], $second[$column]); |
299
|
|
|
} else { |
300
|
|
|
$cmp = strnatcmp($first[$column], $second[$column]); |
301
|
|
|
} |
302
|
|
|
if ($cmp != 0) { |
303
|
|
|
return $cmp; |
304
|
|
|
} |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
// all elements were equal |
308
|
|
|
return 0; |
309
|
|
|
}; |
310
|
|
|
|
311
|
|
|
return $sorter; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* Resolve callback parameter instance. |
316
|
|
|
* |
317
|
|
|
* @return $this |
318
|
|
|
*/ |
319
|
|
|
protected function resolveCallbackParameter() |
320
|
|
|
{ |
321
|
|
|
return $this; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Define the offset of the first item of the collection with respect to |
326
|
|
|
* the FULL dataset the collection was sliced from. It effectively allows the |
327
|
|
|
* collection to be "pre-sliced". |
328
|
|
|
* |
329
|
|
|
* @param int $offset |
330
|
|
|
* @return $this |
331
|
|
|
*/ |
332
|
|
|
public function setOffset(int $offset) |
333
|
|
|
{ |
334
|
|
|
$this->offset = $offset; |
335
|
|
|
|
336
|
|
|
return $this; |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
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 thecomposer.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
orrequire-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 you have not tested against this specific condition, such errors might go unnoticed.