Completed
Push — master ( 633045...791dc4 )
by Lars
03:19
created

AbstractCollection   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 358
Duplicated Lines 7.82 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 71.9%

Importance

Changes 0
Metric Value
wmc 58
lcom 1
cbo 1
dl 28
loc 358
ccs 87
cts 121
cp 0.719
rs 4.5599
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 31 7
A getCollection() 0 4 1
getType() 0 1 ?
A merge() 0 12 4
A offsetSet() 0 14 3
A prepend() 14 14 3
A append() 14 14 3
A column() 0 11 2
A where() 0 13 1
A internalSet() 0 16 4
A checkTypeWrapper() 0 8 2
A extractValue() 0 24 5
C checkType() 0 34 16
B valueToString() 0 30 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AbstractCollection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractCollection, and based on these observations, apply Extract Interface, too.

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
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...
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
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...
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 1
            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)
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
     * @noinspection ReturnTypeCanBeDeclaredInspection
342
     */
343 6
    private function valueToString($value)
344
    {
345
        // null
346 6
        if ($value === null) {
347
            return 'NULL';
348
        }
349
350
        // bool
351 6
        if (\is_bool($value)) {
352
            return $value ? 'TRUE' : 'FALSE';
353
        }
354
355
        // array
356 6
        if (\is_array($value)) {
357
            return 'Array';
358
        }
359
360
        // scalar types (integer, float, string)
361 6
        if (\is_scalar($value)) {
362
            return (string) $value;
363
        }
364
365
        // resource
366 6
        if (\is_resource($value)) {
367
            return \get_resource_type($value) . ' resource #' . (int) $value;
368
        }
369
370
        // object
371 6
        return \get_class($value) . ' Object';
372
    }
373
}
374