1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Enzyme\Collection; |
4
|
|
|
|
5
|
|
|
use Closure; |
6
|
|
|
|
7
|
|
|
class Collection extends BaseCollection |
8
|
|
|
{ |
9
|
|
|
/** |
10
|
|
|
* Static helper method to instantiate a new collection. Useful for when you |
11
|
|
|
* want to immediately chain a method. Eg: Collection::make([1, 2, 3])->map(...). |
12
|
|
|
* |
13
|
|
|
* @param array $initial |
14
|
|
|
* |
15
|
|
|
* @return Collection |
16
|
|
|
*/ |
17
|
|
|
public static function make(array $initial) |
18
|
|
|
{ |
19
|
|
|
return new static($initial); |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Get a PHP style array from the current collection. |
24
|
|
|
* |
25
|
|
|
* @return array |
26
|
|
|
*/ |
27
|
|
|
public function toArray() |
28
|
|
|
{ |
29
|
|
|
return $this->items; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Whether the collection has the specified key, and/or value associated |
34
|
|
|
* with the specified key. |
35
|
|
|
* |
36
|
|
|
* @param string $key |
37
|
|
|
* @param mixed $value |
38
|
|
|
* |
39
|
|
|
* @return bool |
40
|
|
|
*/ |
41
|
|
|
public function has($key, $value = null) |
42
|
|
|
{ |
43
|
|
|
$key_exists = self::keyExists($key, $this->items); |
44
|
|
|
|
45
|
|
|
return null !== $value |
46
|
|
|
? $value === $this->get($key) |
47
|
|
|
: $key_exists; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Get the value associated with the specified key. |
52
|
|
|
* |
53
|
|
|
* @param string $key |
54
|
|
|
* |
55
|
|
|
* @throws \Enzyme\Collection\CollectionException If the key does not exist. |
56
|
|
|
* |
57
|
|
|
* @return mixed |
58
|
|
|
*/ |
59
|
|
|
public function get($key) |
60
|
|
|
{ |
61
|
|
|
if (false === self::keyExists($key, $this->items)) { |
62
|
|
|
throw new CollectionException( |
63
|
|
|
"An element with the key [${key}] does not exist." |
64
|
|
|
); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
return $this->items[$key]; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Get the value associated with the specified key or return a default value |
72
|
|
|
* instead if it does not exist. |
73
|
|
|
* |
74
|
|
|
* @param string $key |
75
|
|
|
* @param mixed $default |
76
|
|
|
* |
77
|
|
|
* @return mixed |
78
|
|
|
*/ |
79
|
|
|
public function getOrDefault($key, $default = null) |
80
|
|
|
{ |
81
|
|
|
try { |
82
|
|
|
return $this->get($key); |
83
|
|
|
} catch (CollectionException $e) { |
84
|
|
|
return $default; |
85
|
|
|
} |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Execute the given callback function for each element in this collection. |
90
|
|
|
* |
91
|
|
|
* @param Closure $fn |
92
|
|
|
*/ |
93
|
|
|
public function each(Closure $fn) |
94
|
|
|
{ |
95
|
|
|
foreach ($this->items as $key => $value) { |
96
|
|
|
if (false === $fn($value, $key)) { |
97
|
|
|
break; |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Execute the given callback function for each element in this collection |
104
|
|
|
* and save the results to a new collection. |
105
|
|
|
* |
106
|
|
|
* @param Closure $fn |
107
|
|
|
* |
108
|
|
|
* @return \Enzyme\Collection\Collection |
109
|
|
|
*/ |
110
|
|
View Code Duplication |
public function map(Closure $fn) |
|
|
|
|
111
|
|
|
{ |
112
|
|
|
$results = []; |
113
|
|
|
foreach ($this->items as $key => $value) { |
114
|
|
|
$results[] = $fn($value, $key); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
return new static($results); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Execute the given callback function for each element in this collection |
122
|
|
|
* and save the results to a new collection with the specified key. The |
123
|
|
|
* callback function should return a 1 element associative array, eg: |
124
|
|
|
* ['key' => 'value'] to be mapped. |
125
|
|
|
* |
126
|
|
|
* @param Closure $fn |
127
|
|
|
* |
128
|
|
|
* @return \Enzyme\Collection\Collection |
129
|
|
|
*/ |
130
|
|
|
public function mapWithKey(Closure $fn) |
131
|
|
|
{ |
132
|
|
|
$results = []; |
133
|
|
|
foreach ($this->items as $key => $value) { |
134
|
|
|
$result = $fn($value, $key); |
135
|
|
|
$keys = array_keys($result); |
136
|
|
|
|
137
|
|
|
if (count($keys) < 1) { |
138
|
|
|
throw new CollectionException( |
139
|
|
|
'Map with key expects a 1 element associative array.' |
140
|
|
|
); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
$results[$keys[0]] = $result[$keys[0]]; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
return new static($results); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Pluck out all values in this collection which have the specified key. |
151
|
|
|
* |
152
|
|
|
* @param string $pluck_key |
153
|
|
|
* @param bool $deep Whether to traverse into sub-arrays. |
154
|
|
|
* |
155
|
|
|
* @return \Enzyme\Collection\Collection |
156
|
|
|
*/ |
157
|
|
|
public function pluck($pluck_key, $deep = true) |
158
|
|
|
{ |
159
|
|
|
return self::pluckKey($this->items, $pluck_key, $deep); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Get the number of elements in this collection. |
164
|
|
|
* |
165
|
|
|
* @return int |
166
|
|
|
*/ |
167
|
|
|
public function count() |
168
|
|
|
{ |
169
|
|
|
return count($this->items); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Whether this collection is empty. |
174
|
|
|
* |
175
|
|
|
* @return bool |
176
|
|
|
*/ |
177
|
|
|
public function isEmpty() |
178
|
|
|
{ |
179
|
|
|
return $this->count() < 1; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Get the value of the first element in this collection. |
184
|
|
|
* |
185
|
|
|
* @throws \Enzyme\Collection\CollectionException If the collection is empty. |
186
|
|
|
* |
187
|
|
|
* @return mixed |
188
|
|
|
*/ |
189
|
|
|
public function first() |
190
|
|
|
{ |
191
|
|
|
if (true === $this->isEmpty()) { |
192
|
|
|
throw new CollectionException( |
193
|
|
|
'Cannot get first item as the collection is empty.' |
194
|
|
|
); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
return reset($this->items); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Get the value of the first element in this collection or return the |
202
|
|
|
* default value specified if the collection is empty. |
203
|
|
|
* |
204
|
|
|
* @param mixed $default |
205
|
|
|
* |
206
|
|
|
* @return mixed |
207
|
|
|
*/ |
208
|
|
|
public function firstOrDefault($default = null) |
209
|
|
|
{ |
210
|
|
|
try { |
211
|
|
|
return $this->first(); |
212
|
|
|
} catch (CollectionException $e) { |
213
|
|
|
return $default; |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Get the value of the last element in this collection. |
219
|
|
|
* |
220
|
|
|
* @throws \Enzyme\Collection\CollectionException If the collection is empty. |
221
|
|
|
* |
222
|
|
|
* @return mixed |
223
|
|
|
*/ |
224
|
|
|
public function last() |
225
|
|
|
{ |
226
|
|
|
if (true === $this->isEmpty()) { |
227
|
|
|
throw new CollectionException( |
228
|
|
|
'Cannot get last element as collection is empty.' |
229
|
|
|
); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
end($this->items); |
233
|
|
|
$key = key($this->items); |
234
|
|
|
reset($this->items); |
235
|
|
|
|
236
|
|
|
return $this->items[$key]; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* Get the value of the last element in this collection or return the |
241
|
|
|
* default value specified if the collection is empty. |
242
|
|
|
* |
243
|
|
|
* @param mixed $default |
244
|
|
|
* |
245
|
|
|
* @return mixed |
246
|
|
|
*/ |
247
|
|
|
public function lastOrDefault($default = null) |
248
|
|
|
{ |
249
|
|
|
try { |
250
|
|
|
return $this->first(); |
251
|
|
|
} catch (CollectionException $e) { |
252
|
|
|
return $default; |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Get a new collection of only the elements in the current collection |
258
|
|
|
* that have the specified keys. |
259
|
|
|
* |
260
|
|
|
* @param array $keys |
261
|
|
|
* |
262
|
|
|
* @return \Enzyme\Collection\Collection |
263
|
|
|
*/ |
264
|
|
|
public function only(array $keys) |
265
|
|
|
{ |
266
|
|
|
return $this->filter(function ($value, $key) use ($keys) { |
267
|
|
|
return true === self::keyExists($key, array_flip($keys)); |
268
|
|
|
}); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Get a new collection of all the elements in the current collection |
273
|
|
|
* except those that have the specified keys. |
274
|
|
|
* |
275
|
|
|
* @param array $keys |
276
|
|
|
* |
277
|
|
|
* @return \Enzyme\Collection\Collection |
278
|
|
|
*/ |
279
|
|
|
public function except(array $keys) |
280
|
|
|
{ |
281
|
|
|
return $this->filter(function ($value, $key) use ($keys) { |
282
|
|
|
return false === self::keyExists($key, array_flip($keys)); |
283
|
|
|
}); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Return a new collection with the current collection's elements plus the |
288
|
|
|
* given value pushed onto the end of the array. |
289
|
|
|
* |
290
|
|
|
* @param mixed $value |
291
|
|
|
* |
292
|
|
|
* @return \Enzyme\Collection\Collection |
293
|
|
|
*/ |
294
|
|
|
public function push($value) |
295
|
|
|
{ |
296
|
|
|
$items = $this->items; |
297
|
|
|
$items[] = $value; |
298
|
|
|
|
299
|
|
|
return new static($items); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Return a new collection with the current collection's elements plus the |
304
|
|
|
* given key and value pushed onto the end of the array. |
305
|
|
|
* |
306
|
|
|
* @param string $key |
307
|
|
|
* @param mixed $value |
308
|
|
|
* |
309
|
|
|
* @return \Enzyme\Collection\Collection |
310
|
|
|
*/ |
311
|
|
|
public function pushWithKey($key, $value) |
312
|
|
|
{ |
313
|
|
|
$items = $this->items; |
314
|
|
|
$items[$key] = $value; |
315
|
|
|
|
316
|
|
|
return new static($items); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Return a new collection with the current collection's elements plus the |
321
|
|
|
* given array pushed onto the end of the array. |
322
|
|
|
* |
323
|
|
|
* @param array $data |
324
|
|
|
* |
325
|
|
|
* @return \Enzyme\Collection\Collection |
326
|
|
|
*/ |
327
|
|
|
public function pushArray(array $data) |
328
|
|
|
{ |
329
|
|
|
$items = $this->items; |
330
|
|
|
foreach ($data as $key => $value) { |
331
|
|
|
if (true === is_int($key)) { |
332
|
|
|
$items[] = $value; |
333
|
|
|
} else { |
334
|
|
|
$items[$key] = $value; |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
return new static($items); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Return a new collection with a subset of all the current collection's |
343
|
|
|
* elements that pass the given callback functions truth test. |
344
|
|
|
* |
345
|
|
|
* @param Closure $fn |
346
|
|
|
* |
347
|
|
|
* @return \Enzyme\Collection\Collection |
348
|
|
|
*/ |
349
|
|
View Code Duplication |
public function filter(Closure $fn) |
|
|
|
|
350
|
|
|
{ |
351
|
|
|
$results = []; |
352
|
|
|
foreach ($this->items as $key => $value) { |
353
|
|
|
if (true === $fn($value, $key)) { |
354
|
|
|
$results[$key] = $value; |
355
|
|
|
} |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
// Pushing this new array will normalize numeric keys if they exist. |
359
|
|
|
// After filtering, they may not start at zero and sequentially |
360
|
|
|
// go upwards, which is generally not expected. |
361
|
|
|
return (new static())->pushArray($results); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* Sort the collection using the provided callback function. Same expected |
366
|
|
|
* parameters and the PHP usort function. |
367
|
|
|
* |
368
|
|
|
* @param Closure $fn |
369
|
|
|
* |
370
|
|
|
* @return \Enzyme\Collection\Collection |
371
|
|
|
*/ |
372
|
|
|
public function sort(Closure $fn) |
373
|
|
|
{ |
374
|
|
|
$sorted = $this->items; |
375
|
|
|
$result = usort($sorted, $fn); |
376
|
|
|
|
377
|
|
|
if (false === $result) { |
378
|
|
|
throw new CollectionException( |
379
|
|
|
'The collection could be not sorted.' |
380
|
|
|
); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
return new static($sorted); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Whether this collection has the specified number of elements within the |
388
|
|
|
* given range or equal too or above the minimum value specified. |
389
|
|
|
* |
390
|
|
|
* @param int $min |
391
|
|
|
* @param int $max Default is null. |
392
|
|
|
* |
393
|
|
|
* @return bool |
394
|
|
|
*/ |
395
|
|
|
public function hasCount($min, $max = null) |
396
|
|
|
{ |
397
|
|
|
if (null === $max) { |
398
|
|
|
return $this->count() >= $min; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
return $this->count() >= $min |
402
|
|
|
&& $this->count() <= $max; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* Get a list of the keys used by this collection. |
407
|
|
|
* |
408
|
|
|
* @return array |
409
|
|
|
*/ |
410
|
|
|
public function keys() |
411
|
|
|
{ |
412
|
|
|
return array_keys($this->items); |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* Checks whether the specified key exists in the given collection. |
417
|
|
|
* |
418
|
|
|
* @param string $key |
419
|
|
|
* @param array $collection |
420
|
|
|
* |
421
|
|
|
* @return bool |
422
|
|
|
*/ |
423
|
|
|
protected static function keyExists($key, array $collection) |
424
|
|
|
{ |
425
|
|
|
return true === isset($collection[$key]); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* Pluck all the values that have the specified key from the given |
430
|
|
|
* collection. |
431
|
|
|
* |
432
|
|
|
* @param array $collection |
433
|
|
|
* @param string $pluck_key |
434
|
|
|
* @param bool $deep Whether to traverse into sub-arrays. |
435
|
|
|
* |
436
|
|
|
* @return \Enzyme\Collection\Collection |
437
|
|
|
*/ |
438
|
|
|
protected static function pluckKey(array $collection, $pluck_key, $deep) |
439
|
|
|
{ |
440
|
|
|
$results = []; |
441
|
|
|
foreach ($collection as $key => $value) { |
442
|
|
|
if (true === $deep && true === is_array($value)) { |
443
|
|
|
$deeper_results = self::pluckKey( |
444
|
|
|
$value, |
445
|
|
|
$pluck_key, |
446
|
|
|
$deep |
447
|
|
|
)->toArray(); |
448
|
|
|
|
449
|
|
|
foreach ($deeper_results as $deep_value) { |
450
|
|
|
$results[] = $deep_value; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
continue; |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
if ($key === $pluck_key) { |
457
|
|
|
$results[] = $value; |
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
return new static($results); |
462
|
|
|
} |
463
|
|
|
} |
464
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.