Completed
Push — master ( c6b695...ef96a9 )
by Lars
01:52
created

AbstractCollection   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 313
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 75.49%

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 1
dl 0
loc 313
ccs 77
cts 102
cp 0.7549
rs 8.5599
c 0
b 0
f 0

14 Methods

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

How to fix   Complexity   

Complex Class

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
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 14
    public function __construct($data = [])
34
    {
35 14
        $this->collectionType = $this->getType();
36
37
        // cast into array, if needed
38
        if (
39 14
            !\is_array($data)
40
            &&
41 14
            !($data instanceof \Traversable)
42
            &&
43 14
            !($data instanceof \Closure)
44
        ) {
45 1
            $data = [$data];
46
        }
47
48
        // check the type, if needed
49 14
        if (!($data instanceof \Closure)) {
50 13
            foreach ($data as $value) {
51 13
                $this->checkTypeWrapper($value);
52
            }
53
        }
54
55 12
        parent::__construct($data);
56 12
    }
57
58
    /**
59
     * @return static[]
60
     */
61 5
    public function getCollection(): array
62
    {
63 5
        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 CollectionInterface ...$collections The collections to merge.
77
     *
78
     * @throws \InvalidArgumentException if any of the given collections are not of the same type
79
     *
80
     * @return CollectionInterface
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
        $this->checkTypeWrapper($value);
104
105
        parent::offsetSet($offset, $value);
106
    }
107
108
    /**
109
     * Prepend a (key) + value to the current array.
110
     *
111
     * @param mixed $value
112
     * @param mixed $key
113
     *
114
     * @return static
115
     *                <p>(Mutable) Return this Arrayy object, with the prepended value.</p>
116
     */
117 2
    public function prepend($value, $key = null): Arrayy
118
    {
119 2
        $this->checkTypeWrapper($value);
120
121
        return parent::prepend($value, $key);
122
    }
123
124
    /**
125
     * Append a (key) + value to the current array.
126
     *
127
     * @param mixed $value
128
     * @param mixed $key
129
     *
130
     * @return static
131
     *                <p>(Mutable) Return this Arrayy object, with the appended values.</p>
132
     */
133 3
    public function append($value, $key = null): Arrayy
134
    {
135 3
        $this->checkTypeWrapper($value);
136
137 2
        return parent::append($value, $key);
138
    }
139
140
    /**
141
     * Returns the values from given property or method.
142
     *
143
     * @param string $keyOrPropertyOrMethod the property or method name to filter by
144
     *
145
     * @throws \InvalidArgumentException if property or method is not defined
146
     *
147
     * @return array
148
     */
149 1
    public function column(string $keyOrPropertyOrMethod): array
150
    {
151
        // init
152 1
        $temp = [];
153
154 1
        foreach ($this->getGenerator() as $item) {
155 1
            $temp[] = $this->extractValue($item, $keyOrPropertyOrMethod);
156
        }
157
158 1
        return $temp;
159
    }
160
161
    /**
162
     * Returns a collection of matching items.
163
     *
164
     * @param string $keyOrPropertyOrMethod the property or method to evaluate
165
     * @param mixed  $value                 the value to match
166
     *
167
     * @throws \InvalidArgumentException if property or method is not defined
168
     *
169
     * @return CollectionInterface
170
     */
171 1
    public function where(string $keyOrPropertyOrMethod, $value): CollectionInterface
172
    {
173 1
        return $this->filter(
174
            function ($item) use ($keyOrPropertyOrMethod, $value) {
175 1
                $accessorValue = $this->extractValue(
176 1
                    $item,
177 1
                    $keyOrPropertyOrMethod
178
                );
179
180 1
                return $accessorValue === $value;
181 1
            }
182
        );
183
    }
184
185
    /**
186
     * Internal mechanic of set method.
187
     *
188
     * @param string|null $key
189
     * @param mixed       $value
190
     * @param bool        $checkProperties
191
     *
192
     * @return bool
193
     */
194 11
    protected function internalSet($key, $value, $checkProperties = true): bool
195
    {
196 11
        $this->checkTypeWrapper($value);
197
198 11
        return parent::internalSet($key, $value, $checkProperties);
199
    }
200
201
    /**
202
     * @param mixed $value
203
     */
204 13
    private function checkTypeWrapper($value)
205
    {
206 13
        if ($this->checkType($this->collectionType, $value) === false) {
207 6
            throw new \InvalidArgumentException(
208 6
                'Value must be of type ' . $this->collectionType . '; value is ' . $this->valueToString($value)
209
            );
210
        }
211 11
    }
212
213
    /**
214
     * Extracts the value of the given property or method from the object.
215
     *
216
     * @param Arrayy $object                the object to extract the value from
217
     * @param string $keyOrPropertyOrMethod the property or method for which the
218
     *                                      value should be extracted
219
     *
220
     * @throws \InvalidArgumentException if the method or property is not defined
221
     *
222
     * @return mixed the value extracted from the specified property or method
223
     */
224 2
    private function extractValue(Arrayy $object, string $keyOrPropertyOrMethod)
225
    {
226 2
        if (isset($object[$keyOrPropertyOrMethod])) {
227 2
            $return = $object->get($keyOrPropertyOrMethod);
228
229 2
            if ($return instanceof Arrayy) {
230 1
                return $return->getArray();
231
            }
232
233 1
            return $return;
234
        }
235
236
        if (\property_exists($object, $keyOrPropertyOrMethod)) {
237
            return $object->{$keyOrPropertyOrMethod};
238
        }
239
240
        if (\method_exists($object, $keyOrPropertyOrMethod)) {
241
            return $object->{$keyOrPropertyOrMethod}();
242
        }
243
244
        throw new \InvalidArgumentException(
245
            \sprintf('array-key & property & method "%s" not defined in %s', $keyOrPropertyOrMethod, \gettype($object))
246
        );
247
    }
248
249
    /**
250
     * Returns `true` if value is of the specified type.
251
     *
252
     * @param string $type  the type to check the value against
253
     * @param mixed  $value the value to check
254
     *
255
     * @return bool
256
     */
257 13
    private function checkType(string $type, $value): bool
258
    {
259
        switch ($type) {
260 13
            case 'array':
261
                return \is_array($value);
262 13
            case 'bool':
263 13
            case 'boolean':
264
                return \is_bool($value);
265 13
            case 'callable':
266
                return \is_callable($value);
267 13
            case 'float':
268 13
            case 'double':
269
                return \is_float($value);
270 13
            case 'int':
271 13
            case 'integer':
272
                return \is_int($value);
273 13
            case 'null':
274
                return $value === null;
275 13
            case 'numeric':
276
                return \is_numeric($value);
277 13
            case 'object':
278
                return \is_object($value);
279 13
            case 'resource':
280
                return \is_resource($value);
281 13
            case 'scalar':
282
                return \is_scalar($value);
283 13
            case 'string':
284
                return \is_string($value);
285 13
            case 'mixed':
286 1
                return true;
287
            default:
288 12
                return $value instanceof $type;
289
        }
290
    }
291
292
    /**
293
     * @param mixed $value
294
     *
295
     * @return string
296
     */
297 6
    private function valueToString($value): string
298
    {
299
        // null
300 6
        if ($value === null) {
301
            return 'NULL';
302
        }
303
304
        // bool
305 6
        if (\is_bool($value)) {
306
            return $value ? 'TRUE' : 'FALSE';
307
        }
308
309
        // array
310 6
        if (\is_array($value)) {
311
            return 'Array';
312
        }
313
314
        // scalar types (integer, float, string)
315 6
        if (\is_scalar($value)) {
316
            return (string) $value;
317
        }
318
319
        // resource
320 6
        if (\is_resource($value)) {
321
            return \get_resource_type($value) . ' resource #' . (int) $value;
322
        }
323
324
        // object
325 6
        return \get_class($value) . ' Object';
326
    }
327
}
328