Passed
Push — master ( 9821c2...feb9ea )
by Roman
02:13
created

Collection::map()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 33
rs 9.0111
c 0
b 0
f 0
cc 6
nc 8
nop 2
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 current()
189
    {
190
        return current($this->container);
191
    }
192
193
    public function pop(): object
194
    {
195
        return array_pop($this->container);
196
    }
197
198
    public function sum(\Closure $callback)
199
    {
200
        $sum = 0;
201
202
        foreach ($this as $element) {
203
            $sum += call_user_func($callback, $element);
204
        }
205
206
        return $sum;
207
    }
208
209
    public function count(): int
210
    {
211
        return count($this->container);
212
    }
213
214
    public function add($item, $index = null): void
215
    {
216
        $this->validate($item);
217
        $this->container[$index ?? $this->count()] = $item;
218
    }
219
220
    /**
221
     * @param string $name
222
     * @param array $arguments
223
     *
224
     * @return Collection
225
     */
226
    public static function __callStatic(string $name, array $arguments = [])
227
    {
228
        if (!empty($arguments) && is_array($arguments[0])) {
229
            $arguments = $arguments[0];
230
        }
231
232
        static::validateObject($name);
233
234
        reset($arguments);
235
236
        if (current($arguments) instanceof Collection) {
237
            return new static($name, ...$arguments);
238
        } else {
239
            return new static($name, $arguments);
240
        }
241
    }
242
243
    protected static function validateObject(string $type): void
244
    {
245
        if (!class_exists($type)) {
246
            throw new Exception\UnprocessedType($type);
247
        }
248
    }
249
}
250