Passed
Push — master ( 2dfe05...59681d )
by Christian
02:28
created

Collection::sort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace RemotelyLiving\PHPCollection;
6
7
final class Collection implements CollectionInterface
8
{
9
    private array $items;
10
11
    private \Generator $deferredValues;
12
13
    private function __construct(array $items)
14
    {
15
        $this->items = $items;
16
    }
17
18
    public static function collect(array $items = []): self
19
    {
20
        self::validateItems($items);
21
22
        return new static($items);
23
    }
24
25
    public static function fill(int $startIndex, int $amount, $item = null): self
26
    {
27
        self::validateItem($item);
28
29
        $items = array_fill($startIndex, $amount, $item);
30
31
        return new self($items);
32
    }
33
34
    public static function fromString(string $string, string $delimiter = ','): self
35
    {
36
        return new self(explode($delimiter, trim($string)));
37
    }
38
39
    public static function later(\Generator $deferredValues): self
40
    {
41
        $deferred = new static([]);
42
        $deferred->deferredValues = $deferredValues;
43
44
        return $deferred;
45
    }
46
47
    public function count(): int
48
    {
49
        return count($this->all());
50
    }
51
52
    public function map(callable $fn): self
53
    {
54
        return new static(array_map($fn, $this->all()));
55
    }
56
57
    public function filter(callable $fn): self
58
    {
59
        return new static(array_filter($this->all(), $fn, ARRAY_FILTER_USE_BOTH));
60
    }
61
62
    public function each(callable $fn): self
63
    {
64
        $deferredValues = function () use ($fn) {
65
            foreach ($this->all() as $key => $item) {
66
                yield $key => $fn($item, $key);
67
            }
68
        };
69
70
        return self::later($deferredValues());
71
    }
72
73
    public function reverse(): self
74
    {
75
        return new static(array_reverse($this->all()));
76
    }
77
78
    /**
79
     * @return mixed|null
80
     */
81
    public function first()
82
    {
83
        if ($this->empty()) {
84
            return null;
85
        }
86
87
        return $this->get(array_key_first($this->all()));
88
    }
89
90
    /**
91
     * @return mixed|null
92
     */
93
    public function last()
94
    {
95
        if ($this->empty()) {
96
            return null;
97
        }
98
99
        return $this->get(array_key_last($this->all()));
100
    }
101
102
    /**
103
     * @param callable $fn
104
     * @param null     $initial
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $initial is correct as it would always require null to be passed?
Loading history...
105
     *
106
     * @return mixed
107
     */
108
    public function reduce(callable $fn, $initial = null)
109
    {
110
        return array_reduce($this->all(), $fn, $initial);
111
    }
112
113
    public function unique(): self
114
    {
115
        return new self(array_unique($this->all()));
116
    }
117
118
    public function diff(Collection $collection, callable $comparator = null): self
119
    {
120
        return new self(array_udiff($this->all(), $collection->all(), $comparator ?? self::getObjectSafeComparator()));
121
    }
122
123
    public function merge(Collection $collection): self
124
    {
125
        return new self(array_merge($this->all(), $collection->all()));
126
    }
127
128
    public function union(Collection $collection): self
129
    {
130
        return $this->merge($collection)->unique();
131
    }
132
133
    public function intersect(Collection $collection): self
134
    {
135
        return new self(array_intersect($this->all(), $collection->all()));
136
    }
137
138
    public function sort(callable $comparator = null): self
139
    {
140
        $items = $this->all();
141
        uasort($items, $comparator ?? self::getObjectSafeComparator());
142
143
        return new self($items);
144
    }
145
146
    public function kSort(callable $comparator = null): self
147
    {
148
        $items = $this->all();
149
        uksort($items, $comparator ?? self::getStringComparator());
150
151
        return new self($items);
152
    }
153
154
    public function empty(): bool
155
    {
156
        return $this->count() === 0;
157
    }
158
159
    public function all(): array
160
    {
161
        if (isset($this->deferredValues) && $this->deferredValues->valid()) {
162
            $this->items = $this->unwrapDeferred();
163
        }
164
165
        return $this->items;
166
    }
167
168
    public function deferred(): \Generator
169
    {
170
        foreach ($this->all() as $key => $value) {
171
            yield $key => $value;
172
        }
173
    }
174
175
    public function values(): array
176
    {
177
        return array_values($this->all());
178
    }
179
180
    public function equals(Collection $collection): bool
181
    {
182
        return $this->diff($collection)->empty();
183
    }
184
185
    /**
186
     * @param string|int $offset
187
     *
188
     * @return bool
189
     */
190
    public function has($offset): bool
191
    {
192
        return isset($this->all()[$offset]);
193
    }
194
195
    /**
196
     * @param mixed $item
197
     *
198
     * @return bool
199
     */
200
    public function contains($item): bool
201
    {
202
        return in_array($item, $this->all(), true);
203
    }
204
205
    public function some(callable $evaluation): bool
206
    {
207
        foreach ($this->all() as $key => $value) {
208
            if ($evaluation($value, $key) === true) {
209
                return true;
210
            }
211
        }
212
213
        return false;
214
    }
215
216
    /**
217
     * @param string|int $offset
218
     * @param mixed|null $default
219
     *
220
     * @return mixed|null
221
     */
222
    public function get($offset, $default = null)
223
    {
224
        return $this->all()[$offset] ?? $default;
225
    }
226
227
    /**
228
     * @return mixed
229
     */
230
    public function rand()
231
    {
232
        return $this->all()[array_rand($this->all())];
233
    }
234
235
    /**
236
     * @param string|int $offset
237
     */
238
    public function remove(...$offsets): self
239
    {
240
        $withRemoved = new static($this->all());
241
        foreach ($offsets as $offset) {
242
            unset($withRemoved->items[$offset]);
243
        }
244
245
        return $withRemoved;
246
    }
247
248
    public function getIterator(): \ArrayIterator
249
    {
250
        return new \ArrayIterator($this->all());
251
    }
252
253
    public function serialize(): string
254
    {
255
        return \serialize(['items' => $this->all()]);
256
    }
257
258
    public function unserialize($serialized, array $classnames = [])
259
    {
260
        if (isset($this->items)) {
261
            throw new \LogicException('Cannot unserialize instance of collection');
262
        }
263
264
        $this->items = \unserialize($serialized, $classnames)['items'] ?? [];
265
    }
266
267
    public function jsonSerialize(): array
268
    {
269
        return $this->all();
270
    }
271
272
    private function unwrapDeferred(): array
273
    {
274
        $items = [];
275
        foreach ($this->deferredValues as $key => $item) {
276
            self::validateItem($item);
277
            $items[$key] = $item;
278
        }
279
280
        return $items;
281
    }
282
283
    /**
284
     * @throw \InvalidArgumentException
285
     */
286
    private static function validateItems(array $items): void
287
    {
288
        foreach ($items as $item) {
289
            self::validateItem($item);
290
        }
291
    }
292
293
    private static function validateItem($item): void
294
    {
295
        if (is_iterable($item)) {
296
            throw new \InvalidArgumentException('A collection may only contain numbers, strings, or objects');
297
        }
298
    }
299
300
    private static function getStringComparator(): callable
301
    {
302
        return function ($a, $b): int {
303
            return strcmp((string) $a, (string) $b);
304
        };
305
    }
306
307
    private static function getObjectSafeComparator(): callable
308
    {
309
        return function ($a, $b): int {
310
            return (\gettype($a) === \gettype($b)) ? $a <=> $b : -1;
311
        };
312
    }
313
}
314