GenericList::diff()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3
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 31
    public function __construct(string $type, object ...$data)
47
    {
48 31
        $this->type = $type;
49 31
        $this->error = "The type specified for this collection is {$type}, you cannot pass an object of type %s";
50
51 31
        foreach ($data as $value) {
52 14
            Checker::objectIsOfType($value, $this->type, sprintf($this->error, get_class($value)));
53
        }
54
55 31
        parent::__construct($data);
56 31
    }
57
58
    /**
59
     * Adds a new object to the collection.
60
     *
61
     * @param object $value
62
     *
63
     * @throws \InvalidArgumentException
64
     *
65
     * @return void
66
     */
67 27
    public function add(object $value): void
68
    {
69 27
        Checker::objectIsOfType($value, $this->type, sprintf($this->error, get_class($value)));
70
71 27
        $data = $this->toArray();
72
73 27
        array_push($data, $value);
74 27
        $this->dataHolder->setContainer($data);
75 27
    }
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 (!$newGenericList instanceof self) {
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
     * Determines if two GenericList objects are equal.
105
     *
106
     * @param \PHPCollections\Collections\GenericList $newGenericList
107
     *
108
     * @return bool
109
     */
110 2
    public function equals(BaseCollection $newGenericList): bool
111
    {
112 2
        if (!$newGenericList instanceof self) {
113 1
            throw new InvalidOperationException('You should only compare an GenericList against another GenericList');
114
        }
115
116 1
        return $this->toArray() == $newGenericList->toArray();
117
    }
118
119
    /**
120
     * Returns all the coincidences found
121
     * for the given callback or null.
122
     *
123
     * @param callable $callback
124
     *
125
     * @return \PHPCollections\Collections\GenericList|null
126
     */
127 1
    public function filter(callable $callback): ?self
128
    {
129 1
        $matcheds = [];
130
131 1
        foreach ($this->dataHolder as $key => $value) {
132 1
            if (call_user_func($callback, $key, $value) === true) {
133 1
                $matcheds[] = $value;
134
            }
135
        }
136
137 1
        return count($matcheds) > 0 ? new $this($this->type, ...array_values($matcheds)) : null;
138
    }
139
140
    /**
141
     * Iterates over every element of the collection.
142
     *
143
     * @param callable $callback
144
     *
145
     * @return void
146
     */
147 2
    public function forEach(callable $callback): void
148
    {
149 2
        $data = $this->toArray();
150
151 2
        array_walk($data, $callback);
152 2
        $this->dataHolder->setContainer($data);
153 2
    }
154
155
    /**
156
     * Returns the object at the specified index
157
     * or null if it's not defined.
158
     *
159
     * @param int $offset
160
     *
161
     * @throws \OutOfRangeException
162
     *
163
     * @return object
164
     */
165 10
    public function get(int $offset): object
166
    {
167 10
        if ($this->isEmpty()) {
168
            throw new OutOfRangeException('You\'re trying to get data from an empty collection.');
169
        }
170
171 10
        if (!$this->dataHolder->offsetExists($offset)) {
172 1
            throw new OutOfRangeException(sprintf('The %d index do not exits for this collection.', $offset));
173
        }
174
175 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...
176
    }
177
178
    /**
179
     * Returns the type property.
180
     *
181
     * @return string
182
     */
183 2
    private function getType(): string
184
    {
185 2
        return $this->type;
186
    }
187
188
    /**
189
     * Updates elements in the collection by
190
     * applying a given callback function.
191
     *
192
     * @param callable $callback
193
     *
194
     * @return \PHPCollections\Collections\GenericList|null
195
     */
196 1
    public function map(callable $callback): ?self
197
    {
198 1
        $matcheds = array_map($callback, $this->toArray());
199
200 1
        return count($matcheds) > 0 ? new $this($this->type, ...array_values($matcheds)) : null;
201
    }
202
203
    /**
204
     * Merges two GenericList into a new one.
205
     *
206
     * @param array $data
207
     *
208
     * @throws \InvalidArgumentException
209
     *
210
     * @return \PHPCollections\Collections\GenericList
211
     */
212 1
    public function merge(BaseCollection $newGenericList): BaseCollection
213
    {
214
        $newGenericList->forEach(function ($value) {
0 ignored issues
show
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

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