Passed
Push — master ( ede5c4...d0b3a2 )
by Max
02:45
created

GenericList::slice()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPCollections\Collections;
6
7
use OutOfRangeException;
8
use PHPCollections\Checker;
9
use PHPCollections\Exceptions\InvalidOperationException;
10
use PHPCollections\Interfaces\IterableInterface;
11
use PHPCollections\Interfaces\MergeableInterface;
12
use PHPCollections\Interfaces\ObjectCollectionInterface;
13
use PHPCollections\Interfaces\SortableInterface;
14
15
/**
16
 * A list for a generic type of data.
17
 */
18
class GenericList extends BaseCollection implements ObjectCollectionInterface, IterableInterface, MergeableInterface, SortableInterface
19
{
20
    /**
21
     * The error message to show when
22
     * someone try to store a value of a
23
     * different type than the specified
24
     * in the type property.
25
     *
26
     * @var string
27
     */
28
    private $error;
29
30
    /**
31
     * The type of data that
32
     * will be stored.
33
     *
34
     * @var string
35
     */
36
    private $type;
37
38
    /**
39
     * Creates a new GenericList.
40
     *
41
     * @param string $type
42
     * @param array  $data
43
     *
44
     * @throws \InvalidArgumentException
45
     */
46 24
    public function __construct(string $type, object ...$data)
47
    {
48 24
        $this->type = $type;
49 24
        $this->error = "The type specified for this collection is {$type}, you cannot pass an object of type %s";
50
51 24
        foreach ($data as $value) {
52 11
            Checker::objectIsOfType($value, $this->type, sprintf($this->error, get_class($value)));
53
        }
54
55 24
        parent::__construct($data);
56 24
    }
57
58
    /**
59
     * Adds a new object to the collection.
60
     *
61
     * @param mixed $value
62
     *
63
     * @throws \InvalidArgumentException
64
     *
65
     * @return void
66
     */
67 22
    public function add($value): void
68
    {
69 22
        Checker::objectIsOfType($value, $this->type, sprintf($this->error, get_class($value)));
70
71 22
        $data = $this->toArray();
72
73 22
        array_push($data, $value);
74 22
        $this->dataHolder->setContainer($data);
75 22
    }
76
77
    /**
78
     * Gets the difference between two GenericList.
79
     *
80
     * @param \PHPCollections\Collections\GenericList $newGenericList
81
     *
82
     * @throws \PHPCollections\Exceptions\InvalidOperationException
83
     *
84
     * @return \PHPCollections\Collections\GenericList
85
     */
86 3
    public function diff(BaseCollection $newGenericList): BaseCollection
87
    {
88 3
        if (!is_a($newGenericList, self::class)) {
89 1
            throw new InvalidOperationException('You should only compare a GenericList against another GenericList');
90
        }
91
92 2
        if ($this->type !== $newGenericList->getType()) {
93 1
            throw new InvalidOperationException("This is a collection of {$this->type} objects, you cannot pass a collection of {$newGenericList->getType()} objects");
94
        }
95
96
        $diffValues = array_udiff($this->toArray(), $newGenericList->toArray(), function ($firstValue, $secondValue) {
97 1
            return $firstValue <=> $secondValue;
98 1
        });
99
100 1
        return new self($this->type, ...$diffValues);
101
    }
102
103
    /**
104
     * Returns all the coincidences found
105
     * for the given callback or null.
106
     *
107
     * @param callable $callback
108
     *
109
     * @return \PHPCollections\Collections\GenericList|null
110
     */
111 1
    public function filter(callable $callback): ?self
112
    {
113 1
        $matcheds = [];
114
115 1
        foreach ($this->dataHolder as $key => $value) {
116 1
            if (call_user_func($callback, $key, $value) === true) {
117 1
                $matcheds[] = $value;
118
            }
119
        }
120
121 1
        return count($matcheds) > 0 ? new $this($this->type, ...array_values($matcheds)) : null;
122
    }
123
124
    /**
125
     * Iterates over every element of the collection.
126
     *
127
     * @param callable $callback
128
     *
129
     * @return void
130
     */
131 2
    public function forEach(callable $callback): void
132
    {
133 2
        $data = $this->toArray();
134
135 2
        array_walk($data, $callback);
136 2
        $this->dataHolder->setContainer($data);
137 2
    }
138
139
    /**
140
     * Returns the object at the specified index
141
     * or null if it's not defined.
142
     *
143
     * @param int $offset
144
     *
145
     * @throws \OutOfRangeException
146
     *
147
     * @return object
148
     */
149 10
    public function get(int $offset): object
150
    {
151 10
        if ($this->isEmpty()) {
152
            throw new OutOfRangeException('You\'re trying to get data from an empty collection.');
153
        }
154
155 10
        if (!$this->dataHolder->offsetExists($offset)) {
156 1
            throw new OutOfRangeException(sprintf('The %d index do not exits for this collection.', $offset));
157
        }
158
159 10
        return $this->dataHolder->offsetGet($offset);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->dataHolder->offsetGet($offset) could return the type null which is incompatible with the type-hinted return object. Consider adding an additional type-check to rule them out.
Loading history...
160
    }
161
162
    /**
163
     * Returns the type property.
164
     *
165
     * @return string
166
     */
167 2
    private function getType(): string
168
    {
169 2
        return $this->type;
170
    }
171
172
    /**
173
     * Updates elements in the collection by
174
     * applying a given callback function.
175
     *
176
     * @param callable $callback
177
     *
178
     * @return \PHPCollections\Collections\GenericList|null
179
     */
180 1
    public function map(callable $callback): ?self
181
    {
182 1
        $matcheds = array_map($callback, $this->toArray());
183
184 1
        return count($matcheds) > 0 ? new $this($this->type, ...array_values($matcheds)) : null;
185
    }
186
187
    /**
188
     * Merges two GenericList into a new one.
189
     *
190
     * @param array $data
191
     *
192
     * @throws \InvalidArgumentException
193
     *
194
     * @return \PHPCollections\Collections\GenericList
195
     */
196 1
    public function merge(BaseCollection $newGenericList): BaseCollection
197
    {
198
        $newGenericList->forEach(function ($value, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

198
        $newGenericList->forEach(function ($value, /** @scrutinizer ignore-unused */ $key) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Bug introduced by
The method forEach() does not exist on PHPCollections\Collections\BaseCollection. Since it exists in all sub-types, consider adding an abstract or default implementation to PHPCollections\Collections\BaseCollection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

198
        $newGenericList->/** @scrutinizer ignore-call */ 
199
                         forEach(function ($value, $key) {
Loading history...
199 1
            Checker::objectIsOfType($value, $this->type, sprintf($this->error, get_class($value)));
200 1
        });
201
202 1
        return new $this($this->type, ...array_merge($this->toArray(), $newGenericList->toArray()));
203
    }
204
205
    /**
206
     * Returns a random element from
207
     * the collection.
208
     *
209
     * @throws \PHPCollections\Exceptions\InvalidOperationException
210
     *
211
     * @return mixed
212
     */
213 1
    public function rand()
214
    {
215 1
        if ($this->isEmpty()) {
216 1
            throw new InvalidOperationException('You cannot get a random element from an empty collection.');
217
        }
218
219 1
        $randomIndex = array_rand($this->toArray());
220
221 1
        return $this->get($randomIndex);
222
    }
223
224
    /**
225
     * Removes an item from the collection
226
     * and repopulate the data container.
227
     *
228
     * @param int $offset
229
     *
230
     * @throws \OutOfRangeException
231
     *
232
     * @return void
233
     */
234 1
    public function remove(int $offset): void
235
    {
236 1
        if ($this->isEmpty()) {
237
            throw new OutOfRangeException('You\'re trying to remove data into a empty collection.');
238
        }
239
240 1
        if (!$this->dataHolder->offsetExists($offset)) {
241 1
            throw new OutOfRangeException(sprintf('The %d index do not exits for this collection.', $offset));
242
        }
243
244 1
        $this->dataHolder->offsetUnset($offset);
245 1
        $this->repopulate();
246 1
    }
247
248
    /**
249
     * Repopulates the data container.
250
     *
251
     * @return void
252
     */
253 1
    private function repopulate(): void
254
    {
255 1
        $oldData = array_values($this->toArray());
256 1
        $this->dataHolder->setContainer($oldData);
257 1
    }
258
259
    /**
260
     * Returns a new collection with the
261
     * reversed values.
262
     *
263
     * @throws \PHPCollections\Exceptions\InvalidOperationException
264
     *
265
     * @return \PHPCollections\Collections\GenericList
266
     */
267 1
    public function reverse(): self
268
    {
269 1
        if ($this->isEmpty()) {
270 1
            throw new InvalidOperationException('You cannot reverse an empty collection.');
271
        }
272
273 1
        return new $this($this->type, ...array_reverse($this->toArray()));
274
    }
275
276
    /**
277
     * Returns a portion of the GenericList.
278
     *
279
     * @param int      $offset
280
     * @param int|null $length
281
     *
282
     * @return PHPCollections\Collections\GenericList|null
0 ignored issues
show
Bug introduced by
The type PHPCollections\Collectio...Collections\GenericList was not found. Did you mean PHPCollections\Collections\GenericList? If so, make sure to prefix the type with \.
Loading history...
283
     */
284 1
    public function slice(int $offset, ?int $length = null): ?BaseCollection
285
    {
286 1
        $newData = array_slice($this->toArray(), $offset, $length);
287
288 1
        return count($newData) > 0 ? new self($this->type, ...$newData) : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return count($newData) >...>type, $newData) : null also could return the type PHPCollections\Collections\GenericList which is incompatible with the documented return type PHPCollections\Collectio...ctions\GenericList|null.
Loading history...
289
    }
290
291
    /**
292
     * Returns a new GenericList with the
293
     * values ordered by a given callback
294
     * if couldn't sort returns null.
295
     *
296
     *
297
     * @param callable $callback
298
     *
299
     * @return \PHPCollections\Collections\GenericList|null
300
     */
301 1
    public function sort(callable $callback): ?BaseCollection
302
    {
303 1
        $data = $this->toArray();
304
305 1
        return usort($data, $callback) ? new $this($this->type, ...$data) : null;
306
    }
307
308
    /**
309
     * Updates the value of the element
310
     * at the given index.
311
     *
312
     * @param int   $index
313
     * @param mixed $value
314
     *
315
     * @throws \InvalidArgumentException
316
     * @throws \PHPCollections\Exceptions\InvalidOperationException
317
     *
318
     * @return bool
319
     */
320 1
    public function update(int $index, object $value): bool
321
    {
322 1
        Checker::objectIsOfType($value, $this->type, sprintf($this->error, get_class($value)));
323
324 1
        if (!$this->exists($index)) {
325
            throw new InvalidOperationException('You cannot update a non-existent value');
326
        }
327
328 1
        $this->dataHolder[$index] = $value;
329
330 1
        return $this->dataHolder[$index] === $value;
331
    }
332
}
333