Collection::sort()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 1
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   https://www.platine-php.com
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
     * Create new instance
67
     * @param array<mixed, mixed> $data
68
     * @param string $type The type of this collection elements
69
     */
70
    public function __construct(array $data = [], protected string $type = '')
71
    {
72
        foreach ($data as $value) {
73
            $this->validateEntry($value);
74
        }
75
76
        parent::__construct($data);
77
    }
78
79
    /**
80
     * Fill the collection
81
     * @param array<mixed, mixed> $data
82
     * @return void
83
     */
84
    public function fill(array $data): void
85
    {
86
        foreach ($data as $value) {
87
            $this->add($value);
88
        }
89
    }
90
91
    /**
92
     *
93
     * @param mixed $value
94
     * @return void
95
     */
96
    public function add(mixed $value): void
97
    {
98
        $this->validateEntry($value);
99
100
        $data = $this->all();
101
        array_push($data, $value);
102
        $this->data->setData($data);
103
    }
104
105
    /**
106
     *
107
     * @param int $offset
108
     * @param mixed $value
109
     * @return bool
110
     * @throws InvalidOperationException
111
     */
112
    public function update(int $offset, mixed $value): bool
113
    {
114
        $this->validateEntry($value);
115
116
        if (!$this->exists($offset)) {
117
            throw new InvalidOperationException(sprintf(
118
                'The collection index [%d] does not exists',
119
                $offset
120
            ));
121
        }
122
123
        $this->data[$offset] = $value;
124
125
        return $this->data[$offset] === $value;
126
    }
127
128
    /**
129
     *
130
     * @return string
131
     */
132
    public function getType(): string
133
    {
134
        return $this->type;
135
    }
136
137
    /**
138
     *
139
     * @param BaseCollection<T> $collection
140
     * @return $this
141
     * @throws InvalidOperationException
142
     */
143
    public function diff(BaseCollection $collection): self
144
    {
145
        if (!$collection instanceof self) {
146
            throw new InvalidOperationException(
147
                'You should only compare a collection of same type'
148
            );
149
        }
150
151
        if ($this->type !== $collection->getType()) {
152
            throw new InvalidOperationException(sprintf(
153
                'This is a collection of type [%s], you '
154
                    . 'cannot pass a collection of type [%s]',
155
                $this->type,
156
                $collection->getType()
157
            ));
158
        }
159
160
        $diffValues = array_udiff(
161
            $this->all(),
162
            $collection->all(),
163
            function ($a, $b) {
164
                return $a <=> $b;
165
            }
166
        );
167
168
        return new $this(array_values($diffValues));
169
    }
170
171
    /**
172
     *
173
     * @param int $offset
174
     * @return mixed
175
     * @throws OutOfRangeException
176
     */
177
    public function get(int $offset): mixed
178
    {
179
        if ($this->isEmpty()) {
180
            throw new OutOfRangeException('The collection is empty');
181
        }
182
183
        if (!$this->data->offsetExists($offset)) {
184
            throw new OutOfRangeException(sprintf(
185
                'The collection index [%d] does not exists',
186
                $offset
187
            ));
188
        }
189
190
        return $this->data->offsetGet($offset);
191
    }
192
193
    /**
194
     * {@inheritedoc}
195
     */
196
    public function equals(BaseCollection $collection): bool
197
    {
198
        if (!$collection instanceof self) {
199
            throw new InvalidOperationException(
200
                'You should only compare a collection of the same type'
201
            );
202
        }
203
204
        return $this->all() == $collection->all();
205
    }
206
207
    /**
208
     *
209
     * @param callable $callback
210
     * @return $this|null
211
     */
212
    public function filter(callable $callback): ?self
213
    {
214
        $matches = [];
215
216
        foreach ($this->data as $key => $value) {
217
            $val = call_user_func($callback, $key, $value);
218
            if ($val === true) {
219
                $matches[] = $value;
220
            }
221
        }
222
223
        return count($matches) > 0
224
                ? new $this($matches)
225
                : null;
226
    }
227
228
    /**
229
     * {@inheritedoc}
230
     */
231
    public function forEach(callable $callback): void
232
    {
233
        $data = $this->all();
234
        array_walk($data, $callback);
235
236
        $this->data->setData($data);
237
    }
238
239
    /**
240
     *
241
     * @param callable $callback
242
     * @return $this|null
243
     */
244
    public function map(callable $callback): ?self
245
    {
246
        $matches = array_map($callback, $this->all());
247
248
        return count($matches) > 0
249
                ? new $this(array_values($matches))
250
                : null;
251
    }
252
253
    /**
254
     * {@inheritedoc}
255
     */
256
    public function merge(BaseCollection $collection): BaseCollection
257
    {
258
        return new $this(array_merge($this->all(), $collection->all()));
259
    }
260
261
    /**
262
     * Return a random element of the collection
263
     * @return mixed
264
     */
265
    public function rand(): mixed
266
    {
267
        if ($this->isEmpty()) {
268
            throw new InvalidOperationException('The collection is empty');
269
        }
270
271
        /** @var int $offset */
272
        $offset = array_rand($this->all());
273
274
        return $this->get($offset);
275
    }
276
277
    /**
278
     * Remove the element at the given index
279
     * @param int $offset
280
     * @return void
281
     */
282
    public function remove(int $offset): void
283
    {
284
        if ($this->isEmpty()) {
285
            throw new OutOfRangeException('The collection is empty');
286
        }
287
288
        if (!$this->data->offsetExists($offset)) {
289
            throw new OutOfRangeException(sprintf(
290
                'The collection index [%d] does not exists',
291
                $offset
292
            ));
293
        }
294
295
        $this->data->offsetUnset($offset);
296
        $this->repopulate();
297
    }
298
299
    /**
300
     *
301
     * @param int $offset
302
     * @param int|null $length
303
     * @return $this|null
304
     */
305
    public function slice(int $offset, ?int $length = null): ?self
306
    {
307
        $newData = array_slice($this->all(), $offset, $length);
308
309
        return count($newData) > 0
310
                ? new $this($newData)
311
                : null;
312
    }
313
314
    /**
315
     * Sort the collection
316
     * @param callable $callback
317
     * @return Collection<T>|null
318
     */
319
    public function sort(callable $callback): ?BaseCollection
320
    {
321
        $data = $this->all();
322
323
        return usort($data, $callback)
324
                ? new $this($data)
325
                : null;
326
    }
327
328
    /**
329
     *
330
     * @return $this
331
     */
332
    public function reverse(): self
333
    {
334
        if ($this->isEmpty()) {
335
            throw new InvalidOperationException('The collection is empty');
336
        }
337
338
        return new $this(array_reverse($this->all()));
339
    }
340
341
    /**
342
     * Validate the collection value type
343
     * @param mixed $value
344
     * @return bool
345
     */
346
    protected function validateEntry(mixed $value): bool
347
    {
348
        if (!empty($this->type)) {
349
            TypeCheck::isValueOf(
350
                $value,
351
                $this->type,
352
                sprintf(
353
                    'The type specified for this collection is '
354
                        . '[%s], you cannot pass a value of type [%s]',
355
                    $this->type,
356
                    is_object($value) ? get_class($value) : gettype($value)
357
                )
358
            );
359
        }
360
        return true;
361
    }
362
}
363