Completed
Push — master ( ef96a9...24dfd0 )
by Lars
01:55
created

AbstractCollection::extractValue()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 8.125

Importance

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