Passed
Push — master ( feb9ea...ff897e )
by Roman
02:35
created

Collection   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 43
eloc 83
dl 0
loc 241
rs 8.96
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A validate() 0 6 2
A offsetExists() 0 3 1
A __construct() 0 9 3
A jsonSerialize() 0 3 1
A getIterator() 0 3 1
A append() 0 6 2
A offsetGet() 0 3 1
A type() 0 3 1
A offsetSet() 0 3 1
A offsetUnset() 0 4 2
A count() 0 3 1
A add() 0 4 1
A isCompatible() 0 11 3
A first() 0 5 1
A last() 0 5 1
A chunk() 0 11 2
A column() 0 13 2
A map() 0 33 6
A validateObject() 0 4 2
A reverse() 0 3 1
A current() 0 3 1
A __callStatic() 0 14 4
A pop() 0 3 1
A sum() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like Collection 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.

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 Collection, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace kartavik\Collections;
4
5
use kartavik\Collections\Exception;
6
7
/**
8
 * Class Collection
9
 * @package kartavik\Collections
10
 */
11
class Collection implements \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable
12
{
13
    /** @var string */
14
    private $type = null;
15
16
    /** @var array */
17
    protected $container = [];
18
19
    public function __construct(string $type, iterable ...$iterables)
20
    {
21
        static::validateObject($type);
22
23
        $this->type = $type;
24
25
        foreach ($iterables as $iterable) {
26
            foreach ($iterable as $index => $item) {
27
                $this->add($item, $index);
28
            }
29
        }
30
    }
31
32
    final public function type(): string
33
    {
34
        return $this->type;
35
    }
36
37
    public function offsetExists($offset): bool
38
    {
39
        return isset($this->container[$offset]);
40
    }
41
42
    public function offsetGet($offset): object
43
    {
44
        return $this->container[$offset];
45
    }
46
47
    public function offsetUnset($offset): void
48
    {
49
        if ($this->offsetExists($offset)) {
50
            unset($this->container[$offset]);
51
        }
52
    }
53
54
    public function getIterator(): \ArrayIterator
55
    {
56
        return new \ArrayIterator($this->container);
57
    }
58
59
    public function append(): void
60
    {
61
        $items = func_get_args();
62
63
        foreach ($items as $item) {
64
            $this->add($item);
65
        }
66
    }
67
68
    /**
69
     * @param mixed $index
70
     * @param mixed $value
71
     *
72
     * @throws Exception\InvalidElement
73
     */
74
    public function offsetSet($index, $value): void
75
    {
76
        $this->add($value, $index);
77
    }
78
79
    public function jsonSerialize(): array
80
    {
81
        return get_object_vars($this);
82
    }
83
84
    public function isCompatible(iterable $collection): bool
85
    {
86
        try {
87
            foreach ($collection as $item) {
88
                $this->validate($item);
89
            }
90
        } catch (Exception\InvalidElement $exception) {
91
            return false;
92
        }
93
94
        return true;
95
    }
96
97
    public function first(): object
98
    {
99
        reset($this->container);
100
101
        return $this->current();
102
    }
103
104
    public function last(): object
105
    {
106
        end($this->container);
107
108
        return $this->current();
109
    }
110
111
    /**
112
     * @param object $item
113
     *
114
     * @throws Exception\InvalidElement
115
     */
116
    public function validate(object $item): void
117
    {
118
        $type = $this->type();
119
120
        if (!$item instanceof $type) {
121
            throw new Exception\InvalidElement($item, $type);
122
        }
123
    }
124
125
    public function chunk(int $size): Collection
126
    {
127
        /** @var Collection $collection */
128
        $collection = new static(Collection::class);
129
        $chunked = array_chunk($this->container, $size);
130
131
        foreach ($chunked as $chunk) {
132
            $collection->append(new Collection($this->type(), $chunk));
133
        }
134
135
        return $collection;
136
    }
137
138
    public function column(\Closure $callback): Collection
139
    {
140
        $type = get_class(call_user_func($callback, $this->current()));
141
        $collection = new Collection($type);
142
        $fetched = [];
143
144
        foreach ($this->container as $item) {
145
            $fetched[] = call_user_func($callback, $item);
146
        }
147
148
        $collection->append(...$fetched);
149
150
        return $collection;
151
    }
152
153
    public function map(\Closure $closure, iterable ...$collections): array
154
    {
155
        $result = [];
156
        $count = $this->count();
157
        $values[] = array_values($this->container);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$values was never initialized. Although not strictly required by PHP, it is generally a good practice to add $values = array(); before regardless.
Loading history...
158
159
        foreach ($collections as $index => $collection) {
160
            if (!$this->isCompatible($collection)) {
161
                throw new Exception\IncompatibleIterable($collection, 'Given iterable object contain invalid element');
162
            }
163
164
            if ($count !== count($collection)) {
165
                throw new Exception\IncompatibleIterable(
166
                    $collection,
167
                    'Given iterable object must contain same count elements'
168
                );
169
            }
170
171
            foreach ($collection as $item) {
172
                $values[$index + 1][] = $item;
173
            }
174
        }
175
176
        foreach (range(0, $this->count() - 1) as $index) {
177
            $result[] = call_user_func(
178
                $closure,
179
                ...array_map(function (array $collection) use ($index) {
180
                    return $collection[$index];
181
                }, $values)
182
            );
183
        }
184
185
        return $result;
186
    }
187
188
    public function reverse(bool $preserveKeys = false): Collection
189
    {
190
        return new static($this->type(), array_reverse($this->container, $preserveKeys));
191
    }
192
    
193
    public function current()
194
    {
195
        return current($this->container);
196
    }
197
198
    public function pop(): object
199
    {
200
        return array_pop($this->container);
201
    }
202
203
    public function sum(\Closure $callback)
204
    {
205
        $sum = 0;
206
207
        foreach ($this as $element) {
208
            $sum += call_user_func($callback, $element);
209
        }
210
211
        return $sum;
212
    }
213
214
    public function count(): int
215
    {
216
        return count($this->container);
217
    }
218
219
    public function add($item, $index = null): void
220
    {
221
        $this->validate($item);
222
        $this->container[$index ?? $this->count()] = $item;
223
    }
224
225
    /**
226
     * @param string $name
227
     * @param array $arguments
228
     *
229
     * @return Collection
230
     */
231
    public static function __callStatic(string $name, array $arguments = [])
232
    {
233
        if (!empty($arguments) && is_array($arguments[0])) {
234
            $arguments = $arguments[0];
235
        }
236
237
        static::validateObject($name);
238
239
        reset($arguments);
240
241
        if (current($arguments) instanceof Collection) {
242
            return new static($name, ...$arguments);
243
        } else {
244
            return new static($name, $arguments);
245
        }
246
    }
247
248
    protected static function validateObject(string $type): void
249
    {
250
        if (!class_exists($type)) {
251
            throw new Exception\UnprocessedType($type);
252
        }
253
    }
254
}
255