Completed
Push — master ( 2b15d9...8122cf )
by Lars
01:53
created

AbstractCollection::prepend()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 14
Ratio 100 %

Code Coverage

Tests 4
CRAP Score 3.7085

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 14
loc 14
ccs 4
cts 7
cp 0.5714
crap 3.7085
rs 9.7998
c 0
b 0
f 0
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
    {
42 15
        $this->collectionType = $this->getType();
43
44
        // cast into array, if needed
45
        if (
46 15
            !\is_array($data)
47
            &&
48 15
            !($data instanceof \Traversable)
49
            &&
50 15
            !($data instanceof \Closure)
51
        ) {
52 2
            $data = [$data];
53
        }
54
55
        // check the type, if needed
56
        if (
57 15
            $checkForMissingPropertiesInConstructorAndType
58
            &&
59 15
            !($data instanceof \Closure)
60
        ) {
61 14
            foreach ($data as $value) {
62 14
                $this->checkTypeWrapper($value);
63
            }
64
        }
65
66 13
        parent::__construct($data, $iteratorClass, $checkForMissingPropertiesInConstructorAndType);
67 13
    }
68
69
    /**
70
     * @return static[]
71
     */
72 6
    public function getCollection(): array
73
    {
74 6
        return $this->array;
75
    }
76
77
    /**
78
     * The type (FQCN) associated with this collection.
79
     *
80
     * @return string
81
     */
82
    abstract public function getType(): string;
83
84
    /**
85
     * Merge current items and items of given collections into a new one.
86
     *
87
     * @param static ...$collections The collections to merge.
88
     *
89
     * @throws \InvalidArgumentException if any of the given collections are not of the same type
90
     *
91
     * @return static
92
     */
93 1
    public function merge(CollectionInterface ...$collections): CollectionInterface
94
    {
95 1
        foreach ($collections as $collection) {
96 1
            if ($collection instanceof Arrayy) {
97 1
                foreach ($collection as $item) {
98 1
                    $this->append($item);
99
                }
100
            }
101
        }
102
103 1
        return $this;
104
    }
105
106
    /**
107
     * Assigns a value to the specified offset + check the type.
108
     *
109
     * @param int|string|null $offset
110
     * @param mixed           $value
111
     */
112 1
    public function offsetSet($offset, $value)
113
    {
114 1
        if ($value instanceof static) {
115
            foreach ($value as $valueTmp) {
116
                parent::offsetSet($offset, $valueTmp);
117
            }
118
119
            return;
120
        }
121
122 1
        $this->checkTypeWrapper($value);
123
124
        parent::offsetSet($offset, $value);
125
    }
126
127
    /**
128
     * Prepend a (key) + value to the current array.
129
     *
130
     * @param mixed $value
131
     * @param mixed $key
132
     *
133
     * @return static
134
     *                <p>(Mutable) Return this Arrayy object, with the prepended value.</p>
135
     */
136 3 View Code Duplication
    public function prepend($value, $key = null): Arrayy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
137
    {
138 3
        if ($value instanceof static) {
139
            foreach ($value as $valueTmp) {
140
                parent::prepend($valueTmp, $key);
141
            }
142
143
            return $this;
144
        }
145
146 3
        $this->checkTypeWrapper($value);
147
148 1
        return parent::prepend($value, $key);
149
    }
150
151
    /**
152
     * Append a (key) + value to the current array.
153
     *
154
     * @param mixed $value
155
     * @param mixed $key
156
     *
157
     * @return static
158
     *                <p>(Mutable) Return this Arrayy object, with the appended values.</p>
159
     */
160 4 View Code Duplication
    public function append($value, $key = null): Arrayy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
161
    {
162 4
        if ($value instanceof static) {
163 1
            foreach ($value as $valueTmp) {
164 1
                parent::append($valueTmp, $key);
165
            }
166
167 1
            return $this;
168
        }
169
170 3
        $this->checkTypeWrapper($value);
171
172 2
        return parent::append($value, $key);
173
    }
174
175
    /**
176
     * Returns the values from given property or method.
177
     *
178
     * @param string $keyOrPropertyOrMethod the property or method name to filter by
179
     *
180
     * @throws \InvalidArgumentException if property or method is not defined
181
     *
182
     * @return array
183
     */
184 1
    public function column(string $keyOrPropertyOrMethod): array
185
    {
186
        // init
187 1
        $temp = [];
188
189 1
        foreach ($this->getGenerator() as $item) {
190 1
            $temp[] = $this->extractValue($item, $keyOrPropertyOrMethod);
191
        }
192
193 1
        return $temp;
194
    }
195
196
    /**
197
     * Returns a collection of matching items.
198
     *
199
     * @param string $keyOrPropertyOrMethod the property or method to evaluate
200
     * @param mixed  $value                 the value to match
201
     *
202
     * @throws \InvalidArgumentException if property or method is not defined
203
     *
204
     * @return static
205
     */
206 1
    public function where(string $keyOrPropertyOrMethod, $value): CollectionInterface
207
    {
208 1
        return $this->filter(
209
            function ($item) use ($keyOrPropertyOrMethod, $value) {
210 1
                $accessorValue = $this->extractValue(
211 1
                    $item,
212 1
                    $keyOrPropertyOrMethod
213
                );
214
215 1
                return $accessorValue === $value;
216 1
            }
217
        );
218
    }
219
220
    /**
221
     * Internal mechanic of set method.
222
     *
223
     * @param string|null $key
224
     * @param mixed       $value
225
     * @param bool        $checkPropertiesAndType
226
     *
227
     * @return bool
228
     */
229 12
    protected function internalSet($key, $value, $checkPropertiesAndType = true): bool
230
    {
231 12
        if ($value instanceof static) {
232
            foreach ($value as $valueTmp) {
233
                parent::internalSet($key, $valueTmp, $checkPropertiesAndType);
234
            }
235
236
            return true;
237
        }
238
239 12
        if ($checkPropertiesAndType) {
240
            $this->checkTypeWrapper($value);
241
        }
242
243 12
        return parent::internalSet($key, $value, $checkPropertiesAndType);
244
    }
245
246
    /**
247
     * @param mixed $value
248
     */
249 14
    private function checkTypeWrapper($value)
250
    {
251 14
        if ($this->checkType($this->collectionType, $value) === false) {
252 6
            throw new \InvalidArgumentException(
253 6
                'Value must be of type ' . $this->collectionType . '; type is ' . \gettype($value) . ', value is "' . $this->valueToString($value) . '"'
254
            );
255
        }
256 12
    }
257
258
    /**
259
     * Extracts the value of the given property or method from the object.
260
     *
261
     * @param Arrayy $object                the object to extract the value from
262
     * @param string $keyOrPropertyOrMethod the property or method for which the
263
     *                                      value should be extracted
264
     *
265
     * @throws \InvalidArgumentException if the method or property is not defined
266
     *
267
     * @return mixed the value extracted from the specified property or method
268
     */
269 2
    private function extractValue(Arrayy $object, string $keyOrPropertyOrMethod)
270
    {
271 2
        if (isset($object[$keyOrPropertyOrMethod])) {
272 2
            $return = $object->get($keyOrPropertyOrMethod);
273
274 2
            if ($return instanceof Arrayy) {
275 1
                return $return->getArray();
276
            }
277
278 1
            return $return;
279
        }
280
281
        if (\property_exists($object, $keyOrPropertyOrMethod)) {
282
            return $object->{$keyOrPropertyOrMethod};
283
        }
284
285
        if (\method_exists($object, $keyOrPropertyOrMethod)) {
286
            return $object->{$keyOrPropertyOrMethod}();
287
        }
288
289
        throw new \InvalidArgumentException(
290
            \sprintf('array-key & property & method "%s" not defined in %s', $keyOrPropertyOrMethod, \gettype($object))
291
        );
292
    }
293
294
    /**
295
     * Returns `true` if value is of the specified type.
296
     *
297
     * @param string $type  the type to check the value against
298
     * @param mixed  $value the value to check
299
     *
300
     * @return bool
301
     */
302 14
    private function checkType(string $type, $value): bool
303
    {
304
        switch ($type) {
305 14
            case 'array':
306
                return \is_array($value);
307 14
            case 'bool':
308 14
            case 'boolean':
309
                return \is_bool($value);
310 14
            case 'callable':
311
                return \is_callable($value);
312 14
            case 'float':
313 14
            case 'double':
314
                return \is_float($value);
315 14
            case 'int':
316 14
            case 'integer':
317
                return \is_int($value);
318 14
            case 'null':
319
                return $value === null;
320 14
            case 'numeric':
321
                return \is_numeric($value);
322 14
            case 'object':
323
                return \is_object($value);
324 14
            case 'resource':
325
                return \is_resource($value);
326 14
            case 'scalar':
327
                return \is_scalar($value);
328 14
            case 'string':
329
                return \is_string($value);
330 14
            case 'mixed':
331 1
                return true;
332
            default:
333 13
                return $value instanceof $type;
334
        }
335
    }
336
337
    /**
338
     * @param mixed $value
339
     *
340
     * @return string
341
     */
342 6
    private function valueToString($value): string
343
    {
344
        // null
345 6
        if ($value === null) {
346
            return 'NULL';
347
        }
348
349
        // bool
350 6
        if (\is_bool($value)) {
351
            return $value ? 'TRUE' : 'FALSE';
352
        }
353
354
        // array
355 6
        if (\is_array($value)) {
356
            return 'Array';
357
        }
358
359
        // scalar types (integer, float, string)
360 6
        if (\is_scalar($value)) {
361
            return (string) $value;
362
        }
363
364
        // resource
365 6
        if (\is_resource($value)) {
366
            return \get_resource_type($value) . ' resource #' . (int) $value;
367
        }
368
369
        // object
370 6
        return \get_class($value) . ' Object';
371
    }
372
}
373