1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Arrayy\Collection; |
6
|
|
|
|
7
|
|
|
use Arrayy\Arrayy; |
8
|
|
|
use Arrayy\ArrayyIterator; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* This class provides a full implementation of `CollectionInterface`, to |
12
|
|
|
* minimize the effort required to implement this interface. |
13
|
|
|
* |
14
|
|
|
* INFO: this collection thingy is inspired by https://github.com/ramsey/collection/ |
15
|
|
|
*/ |
16
|
|
|
abstract class AbstractCollection extends Arrayy implements CollectionInterface |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* The type of elements stored in this collection. |
20
|
|
|
* |
21
|
|
|
* @var string |
22
|
|
|
*/ |
23
|
|
|
private $collectionType; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Constructs a collection object of the specified type, optionally with the |
27
|
|
|
* specified data. |
28
|
|
|
* |
29
|
|
|
* @param mixed $data |
30
|
|
|
* <p> |
31
|
|
|
* The initial items to store in the collection. |
32
|
|
|
* </p> |
33
|
|
|
* @param string $iteratorClass |
34
|
|
|
* @param bool $checkForMissingPropertiesInConstructorAndType |
35
|
|
|
*/ |
36
|
15 |
|
public function __construct( |
37
|
|
|
$data = [], |
38
|
|
|
string $iteratorClass = ArrayyIterator::class, |
39
|
|
|
bool $checkForMissingPropertiesInConstructorAndType = true |
40
|
|
|
) { |
41
|
15 |
|
$this->collectionType = $this->getType(); |
42
|
|
|
|
43
|
|
|
// cast into array, if needed |
44
|
|
|
if ( |
45
|
15 |
|
!\is_array($data) |
46
|
|
|
&& |
47
|
15 |
|
!($data instanceof \Traversable) |
48
|
|
|
&& |
49
|
15 |
|
!($data instanceof \Closure) |
50
|
|
|
) { |
51
|
2 |
|
$data = [$data]; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
// check the type, if needed |
55
|
|
|
if ( |
56
|
15 |
|
$checkForMissingPropertiesInConstructorAndType |
57
|
|
|
&& |
58
|
15 |
|
!($data instanceof \Closure) |
59
|
|
|
) { |
60
|
14 |
|
foreach ($data as $value) { |
61
|
14 |
|
$this->checkTypeWrapper($value); |
62
|
|
|
} |
63
|
|
|
} |
64
|
|
|
|
65
|
13 |
|
parent::__construct($data, $iteratorClass, $checkForMissingPropertiesInConstructorAndType); |
66
|
13 |
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @return static[] |
70
|
|
|
*/ |
71
|
6 |
|
public function getCollection(): array |
72
|
|
|
{ |
73
|
6 |
|
return $this->array; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* The type (FQCN) associated with this collection. |
78
|
|
|
* |
79
|
|
|
* @return string |
80
|
|
|
*/ |
81
|
|
|
abstract public function getType(): string; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Merge current items and items of given collections into a new one. |
85
|
|
|
* |
86
|
|
|
* @param static ...$collections The collections to merge. |
87
|
|
|
* |
88
|
|
|
* @throws \InvalidArgumentException if any of the given collections are not of the same type |
89
|
|
|
* |
90
|
|
|
* @return static |
91
|
|
|
*/ |
92
|
1 |
|
public function merge(CollectionInterface ...$collections): CollectionInterface |
93
|
|
|
{ |
94
|
1 |
|
foreach ($collections as $collection) { |
95
|
1 |
|
if ($collection instanceof Arrayy) { |
96
|
1 |
|
foreach ($collection as $item) { |
97
|
1 |
|
$this->append($item); |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
102
|
1 |
|
return $this; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Assigns a value to the specified offset + check the type. |
107
|
|
|
* |
108
|
|
|
* @param int|string|null $offset |
109
|
|
|
* @param mixed $value |
110
|
|
|
*/ |
111
|
1 |
|
public function offsetSet($offset, $value) |
112
|
|
|
{ |
113
|
1 |
|
if ($value instanceof static) { |
114
|
|
|
foreach ($value as $valueTmp) { |
115
|
|
|
parent::offsetSet($offset, $valueTmp); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
return; |
119
|
|
|
} |
120
|
|
|
|
121
|
1 |
|
$this->checkTypeWrapper($value); |
122
|
|
|
|
123
|
|
|
parent::offsetSet($offset, $value); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Prepend a (key) + value to the current array. |
128
|
|
|
* |
129
|
|
|
* @param mixed $value |
130
|
|
|
* @param mixed $key |
131
|
|
|
* |
132
|
|
|
* @return static |
133
|
|
|
* <p>(Mutable) Return this Arrayy object, with the prepended value.</p> |
134
|
|
|
*/ |
135
|
3 |
View Code Duplication |
public function prepend($value, $key = null): Arrayy |
|
|
|
|
136
|
|
|
{ |
137
|
3 |
|
if ($value instanceof static) { |
138
|
|
|
foreach ($value as $valueTmp) { |
139
|
|
|
parent::prepend($valueTmp, $key); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
return $this; |
143
|
|
|
} |
144
|
|
|
|
145
|
3 |
|
$this->checkTypeWrapper($value); |
146
|
|
|
|
147
|
1 |
|
return parent::prepend($value, $key); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Append a (key) + value to the current array. |
152
|
|
|
* |
153
|
|
|
* @param mixed $value |
154
|
|
|
* @param mixed $key |
155
|
|
|
* |
156
|
|
|
* @return static |
157
|
|
|
* <p>(Mutable) Return this Arrayy object, with the appended values.</p> |
158
|
|
|
*/ |
159
|
4 |
View Code Duplication |
public function append($value, $key = null): Arrayy |
|
|
|
|
160
|
|
|
{ |
161
|
4 |
|
if ($value instanceof static) { |
162
|
1 |
|
foreach ($value as $valueTmp) { |
163
|
1 |
|
parent::append($valueTmp, $key); |
164
|
|
|
} |
165
|
|
|
|
166
|
1 |
|
return $this; |
167
|
|
|
} |
168
|
|
|
|
169
|
3 |
|
$this->checkTypeWrapper($value); |
170
|
|
|
|
171
|
2 |
|
return parent::append($value, $key); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Returns the values from given property or method. |
176
|
|
|
* |
177
|
|
|
* @param string $keyOrPropertyOrMethod the property or method name to filter by |
178
|
|
|
* |
179
|
|
|
* @throws \InvalidArgumentException if property or method is not defined |
180
|
|
|
* |
181
|
|
|
* @return array |
182
|
|
|
*/ |
183
|
1 |
|
public function column(string $keyOrPropertyOrMethod): array |
184
|
|
|
{ |
185
|
|
|
// init |
186
|
1 |
|
$temp = []; |
187
|
|
|
|
188
|
1 |
|
foreach ($this->getGenerator() as $item) { |
189
|
1 |
|
$temp[] = $this->extractValue($item, $keyOrPropertyOrMethod); |
190
|
|
|
} |
191
|
|
|
|
192
|
1 |
|
return $temp; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Returns a collection of matching items. |
197
|
|
|
* |
198
|
|
|
* @param string $keyOrPropertyOrMethod the property or method to evaluate |
199
|
|
|
* @param mixed $value the value to match |
200
|
|
|
* |
201
|
|
|
* @throws \InvalidArgumentException if property or method is not defined |
202
|
|
|
* |
203
|
|
|
* @return static |
204
|
|
|
*/ |
205
|
1 |
|
public function where(string $keyOrPropertyOrMethod, $value): CollectionInterface |
206
|
|
|
{ |
207
|
1 |
|
return $this->filter( |
208
|
|
|
function ($item) use ($keyOrPropertyOrMethod, $value) { |
209
|
1 |
|
$accessorValue = $this->extractValue( |
210
|
1 |
|
$item, |
211
|
1 |
|
$keyOrPropertyOrMethod |
212
|
|
|
); |
213
|
|
|
|
214
|
1 |
|
return $accessorValue === $value; |
215
|
1 |
|
} |
216
|
|
|
); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Internal mechanic of set method. |
221
|
|
|
* |
222
|
|
|
* @param string|null $key |
223
|
|
|
* @param mixed $value |
224
|
|
|
* @param bool $checkPropertiesAndType |
225
|
|
|
* |
226
|
|
|
* @return bool |
227
|
|
|
*/ |
228
|
12 |
|
protected function internalSet($key, $value, $checkPropertiesAndType = true): bool |
229
|
|
|
{ |
230
|
12 |
|
if ($value instanceof static) { |
231
|
|
|
foreach ($value as $valueTmp) { |
232
|
|
|
parent::internalSet($key, $valueTmp, $checkPropertiesAndType); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return true; |
236
|
|
|
} |
237
|
|
|
|
238
|
12 |
|
if ($checkPropertiesAndType) { |
239
|
|
|
$this->checkTypeWrapper($value); |
240
|
|
|
} |
241
|
|
|
|
242
|
12 |
|
return parent::internalSet($key, $value, $checkPropertiesAndType); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @param mixed $value |
247
|
|
|
*/ |
248
|
14 |
|
private function checkTypeWrapper($value) |
249
|
|
|
{ |
250
|
14 |
|
if ($this->checkType($this->collectionType, $value) === false) { |
251
|
6 |
|
throw new \InvalidArgumentException( |
252
|
6 |
|
'Value must be of type ' . $this->collectionType . '; type is ' . \gettype($value) . ', value is "' . $this->valueToString($value) . '"' |
253
|
|
|
); |
254
|
|
|
} |
255
|
12 |
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Extracts the value of the given property or method from the object. |
259
|
|
|
* |
260
|
|
|
* @param Arrayy $object the object to extract the value from |
261
|
|
|
* @param string $keyOrPropertyOrMethod the property or method for which the |
262
|
|
|
* value should be extracted |
263
|
|
|
* |
264
|
|
|
* @throws \InvalidArgumentException if the method or property is not defined |
265
|
|
|
* |
266
|
|
|
* @return mixed the value extracted from the specified property or method |
267
|
|
|
*/ |
268
|
2 |
|
private function extractValue(Arrayy $object, string $keyOrPropertyOrMethod) |
269
|
|
|
{ |
270
|
2 |
|
if (isset($object[$keyOrPropertyOrMethod])) { |
271
|
2 |
|
$return = $object->get($keyOrPropertyOrMethod); |
272
|
|
|
|
273
|
2 |
|
if ($return instanceof Arrayy) { |
274
|
1 |
|
return $return->getArray(); |
275
|
|
|
} |
276
|
|
|
|
277
|
1 |
|
return $return; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
if (\property_exists($object, $keyOrPropertyOrMethod)) { |
281
|
|
|
return $object->{$keyOrPropertyOrMethod}; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
if (\method_exists($object, $keyOrPropertyOrMethod)) { |
285
|
|
|
return $object->{$keyOrPropertyOrMethod}(); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
throw new \InvalidArgumentException( |
289
|
|
|
\sprintf('array-key & property & method "%s" not defined in %s', $keyOrPropertyOrMethod, \gettype($object)) |
290
|
|
|
); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Returns `true` if value is of the specified type. |
295
|
|
|
* |
296
|
|
|
* @param string $type the type to check the value against |
297
|
|
|
* @param mixed $value the value to check |
298
|
|
|
* |
299
|
|
|
* @return bool |
300
|
|
|
*/ |
301
|
14 |
|
private function checkType(string $type, $value): bool |
302
|
|
|
{ |
303
|
|
|
switch ($type) { |
304
|
14 |
|
case 'array': |
305
|
|
|
return \is_array($value); |
306
|
14 |
|
case 'bool': |
307
|
14 |
|
case 'boolean': |
308
|
|
|
return \is_bool($value); |
309
|
14 |
|
case 'callable': |
310
|
|
|
return \is_callable($value); |
311
|
14 |
|
case 'float': |
312
|
14 |
|
case 'double': |
313
|
|
|
return \is_float($value); |
314
|
14 |
|
case 'int': |
315
|
14 |
|
case 'integer': |
316
|
|
|
return \is_int($value); |
317
|
14 |
|
case 'null': |
318
|
|
|
return $value === null; |
319
|
14 |
|
case 'numeric': |
320
|
|
|
return \is_numeric($value); |
321
|
14 |
|
case 'object': |
322
|
|
|
return \is_object($value); |
323
|
14 |
|
case 'resource': |
324
|
|
|
return \is_resource($value); |
325
|
14 |
|
case 'scalar': |
326
|
|
|
return \is_scalar($value); |
327
|
14 |
|
case 'string': |
328
|
|
|
return \is_string($value); |
329
|
14 |
|
case 'mixed': |
330
|
1 |
|
return true; |
331
|
|
|
default: |
332
|
13 |
|
return $value instanceof $type; |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* @param mixed $value |
338
|
|
|
* |
339
|
|
|
* @return string |
340
|
|
|
*/ |
341
|
6 |
|
private function valueToString($value): string |
342
|
|
|
{ |
343
|
|
|
// null |
344
|
6 |
|
if ($value === null) { |
345
|
|
|
return 'NULL'; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
// bool |
349
|
6 |
|
if (\is_bool($value)) { |
350
|
|
|
return $value ? 'TRUE' : 'FALSE'; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
// array |
354
|
6 |
|
if (\is_array($value)) { |
355
|
|
|
return 'Array'; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
// scalar types (integer, float, string) |
359
|
6 |
|
if (\is_scalar($value)) { |
360
|
|
|
return (string) $value; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
// resource |
364
|
6 |
|
if (\is_resource($value)) { |
365
|
|
|
return \get_resource_type($value) . ' resource #' . (int) $value; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
// object |
369
|
6 |
|
return \get_class($value) . ' Object'; |
370
|
|
|
} |
371
|
|
|
} |
372
|
|
|
|
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.