Passed
Push — develop ( eb0d73...33e885 )
by nguereza
02:57
created

Collection::repopulate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * Platine Collection
5
 *
6
 * Platine Collection provides a flexible and simple PHP collection implementation.
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Collection
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
/**
32
 *  @file Collection.php
33
 *
34
 *  The Collection class
35
 *
36
 *  @package    Platine\Collection
37
 *  @author Platine Developers Team
38
 *  @copyright  Copyright (c) 2020
39
 *  @license    http://opensource.org/licenses/MIT  MIT License
40
 *  @link   http://www.iacademy.cf
41
 *  @version 1.0.0
42
 *  @filesource
43
 */
44
45
declare(strict_types=1);
46
47
namespace Platine\Collection;
48
49
use OutOfRangeException;
50
use Platine\Collection\Exception\InvalidOperationException;
51
52
/**
53
 * Class Collection
54
 * @package Platine\Collection
55
 * @template T
56
 * @extends BaseCollection<T>
57
 * @implements MergeableInterface<T>
58
 * @implements SortableInterface<T>
59
 */
60
class Collection extends BaseCollection implements
61
    IterableInterface,
62
    MergeableInterface,
63
    SortableInterface
64
{
65
66
    /**
67
     * The type of this collection elements
68
     * @var string
69
     */
70
    protected string $type = '';
71
72
    /**
73
     * Create new instance
74
     * @param array<mixed, mixed> $data
75
     * @param string $type
76
     */
77
    public function __construct(array $data = [], string $type = '')
78
    {
79
        $this->type = $type;
80
81
        foreach ($data as $value) {
82
            $this->validateEntry($value);
83
        }
84
85
        parent::__construct($data);
86
    }
87
88
    /**
89
     * Fill the collection
90
     * @param array<mixed, mixed> $data
91
     * @return void
92
     */
93
    public function fill(array $data): void
94
    {
95
        foreach ($data as $value) {
96
            $this->add($value);
97
        }
98
    }
99
100
    /**
101
     *
102
     * @param mixed $value
103
     * @return void
104
     */
105
    public function add($value): void
106
    {
107
        $this->validateEntry($value);
108
109
        $data = $this->all();
110
        array_push($data, $value);
111
        $this->data->setData($data);
112
    }
113
114
    /**
115
     *
116
     * @param int $offset
117
     * @param mixed $value
118
     * @return bool
119
     * @throws InvalidOperationException
120
     */
121
    public function update(int $offset, $value): bool
122
    {
123
        $this->validateEntry($value);
124
125
        if (!$this->exists($offset)) {
126
            throw new InvalidOperationException(sprintf(
127
                'The collection index [%d] does not exists',
128
                $offset
129
            ));
130
        }
131
132
        $this->data[$offset] = $value;
133
134
        return $this->data[$offset] === $value;
135
    }
136
137
    /**
138
     *
139
     * @return string
140
     */
141
    public function getType(): string
142
    {
143
        return $this->type;
144
    }
145
146
    /**
147
     *
148
     * @param Collection<T> $collection
149
     * @return $this<T>
150
     * @throws InvalidOperationException
151
     */
152
    public function diff(BaseCollection $collection): self
153
    {
154
        if (!$collection instanceof self) {
155
            throw new InvalidOperationException(
156
                'You should only compare a collection of same type'
157
            );
158
        }
159
160
        if ($this->type !== $collection->getType()) {
161
            throw new InvalidOperationException(sprintf(
162
                'This is a collection of type [%s], you '
163
                    . 'cannot pass a collection of type [%s]',
164
                $this->type,
165
                $collection->getType()
166
            ));
167
        }
168
169
        $diffValues = array_udiff(
170
            $this->all(),
171
            $collection->all(),
172
            function ($a, $b) {
173
                return $a <=> $b;
174
            }
175
        );
176
177
        return new $this(array_values($diffValues));
178
    }
179
180
    /**
181
     *
182
     * @param int $offset
183
     * @return mixed
184
     * @throws OutOfRangeException
185
     */
186
    public function get(int $offset)
187
    {
188
        if ($this->isEmpty()) {
189
            throw new OutOfRangeException('The collection is empty');
190
        }
191
192
        if (!$this->data->offsetExists($offset)) {
193
            throw new OutOfRangeException(sprintf(
194
                'The collection index [%d] does not exists',
195
                $offset
196
            ));
197
        }
198
199
        return $this->data->offsetGet($offset);
200
    }
201
202
    /**
203
     * {@inheritedoc}
204
     */
205
    public function equals(BaseCollection $collection): bool
206
    {
207
        if (!$collection instanceof self) {
208
            throw new InvalidOperationException(
209
                'You should only compare a collection of the same type'
210
            );
211
        }
212
213
        return $this->all() == $collection->all();
214
    }
215
216
    /**
217
     *
218
     * @param callable $callback
219
     * @return $this|null
220
     */
221
    public function filter(callable $callback): ?self
222
    {
223
        $matches = [];
224
225
        foreach ($this->data as $key => $value) {
226
            $val = call_user_func($callback, $key, $value);
227
            if ($val === true) {
228
                $matches[] = $value;
229
            }
230
        }
231
232
        return count($matches) > 0
233
                ? new $this(array_values($matches))
234
                : null;
235
    }
236
237
    /**
238
     * {@inheritedoc}
239
     */
240
    public function forEach(callable $callback): void
241
    {
242
        $data = $this->all();
243
        array_walk($data, $callback);
244
245
        $this->data->setData($data);
246
    }
247
248
    /**
249
     *
250
     * @param callable $callback
251
     * @return $this|null
252
     */
253
    public function map(callable $callback): ?self
254
    {
255
        $matches = array_map($callback, $this->all());
256
257
        return count($matches) > 0
258
                ? new $this(array_values($matches))
259
                : null;
260
    }
261
262
    /**
263
     * {@inheritedoc}
264
     */
265
    public function merge(BaseCollection $collection): BaseCollection
266
    {
267
        return new $this(array_merge($this->all(), $collection->all()));
268
    }
269
270
    /**
271
     * Return a random element of the collection
272
     * @return mixed
273
     */
274
    public function rand()
275
    {
276
        if ($this->isEmpty()) {
277
            throw new InvalidOperationException('The collection is empty');
278
        }
279
280
        /** @var int $offset */
281
        $offset = array_rand($this->all());
282
283
        return $this->get($offset);
284
    }
285
286
    /**
287
     * Remove the element at the given index
288
     * @param int $offset
289
     * @return void
290
     */
291
    public function remove(int $offset): void
292
    {
293
        if ($this->isEmpty()) {
294
            throw new OutOfRangeException('The collection is empty');
295
        }
296
297
        if (!$this->data->offsetExists($offset)) {
298
            throw new OutOfRangeException(sprintf(
299
                'The collection index [%d] does not exists',
300
                $offset
301
            ));
302
        }
303
304
        $this->data->offsetUnset($offset);
305
        $this->repopulate();
306
    }
307
308
    /**
309
     *
310
     * @param int $offset
311
     * @param int|null $length
312
     * @return $this<T>|null
313
     */
314
    public function slice(int $offset, ?int $length = null): ?self
315
    {
316
        $newData = array_slice($this->all(), $offset, $length);
317
318
        return count($newData) > 0
319
                ? new $this($newData)
320
                : null;
321
    }
322
323
    /**
324
     * Sort the collection
325
     * @param callable $callback
326
     * @return Collection<T>|null
327
     */
328
    public function sort(callable $callback): ?BaseCollection
329
    {
330
        $data = $this->all();
331
332
        return usort($data, $callback)
333
                ? new $this($data)
334
                : null;
335
    }
336
337
    /**
338
     *
339
     * @return $this
340
     */
341
    public function reverse(): self
342
    {
343
        if ($this->isEmpty()) {
344
            throw new InvalidOperationException('The collection is empty');
345
        }
346
347
        return new $this(array_reverse($this->all()));
348
    }
349
350
    /**
351
     * Validate the collection value type
352
     * @param mixed $value
353
     * @return bool
354
     */
355
    protected function validateEntry($value): bool
356
    {
357
        if (!empty($this->type)) {
358
            TypeCheck::isValueOf(
359
                $value,
360
                $this->type,
361
                sprintf(
362
                    'The type specified for this collection is '
363
                        . '[%s], you cannot pass a value of type [%s]',
364
                    $this->type,
365
                    is_object($value) ? get_class($value) : gettype($value)
366
                )
367
            );
368
        }
369
        return true;
370
    }
371
}
372