• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHP-Alchemist / coreFiles / 13531756279

25 Feb 2025 09:58PM UTC coverage: 96.481% (+0.006%) from 96.475%
13531756279

push

github

druid628
Uses coverallsapp/github-actions@v2

521 of 540 relevant lines covered (96.48%)

4.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.59
/src/Abstracts/AbstractIndexedArray.php
1
<?php
2

3
namespace PHPAlchemist\Abstracts;
4

5
use PHPAlchemist\Contracts\IndexedArrayInterface;
6
use PHPAlchemist\Contracts\StringInterface;
7
use PHPAlchemist\Exceptions\InvalidKeyTypeException;
8
use PHPAlchemist\Exceptions\UnmatchedClassException;
9
use PHPAlchemist\Exceptions\UnmatchedVersionException;
10
use PHPAlchemist\Traits\Array\OnClearTrait;
11
use PHPAlchemist\Traits\Array\OnInsertTrait;
12
use PHPAlchemist\Traits\Array\OnRemoveTrait;
13
use PHPAlchemist\Traits\Array\OnSetTrait;
14
use PHPAlchemist\Traits\ArrayTrait;
15
use PHPAlchemist\Types\Base\Default;
16
use PHPAlchemist\Types\Collection;
17
use PHPAlchemist\Types\Number;
18
use PHPAlchemist\Types\Roll;
19
use PHPAlchemist\Types\Twine;
20

21
/**
22
 * Abstract class Collection (Objectified Array Class).
23
 */
24
abstract class AbstractIndexedArray implements IndexedArrayInterface
25
{
26
    use OnInsertTrait;
27
    use OnRemoveTrait;
28
    use OnClearTrait;
29
    use OnSetTrait;
30
    use ArrayTrait;
31

32
    public static $serializeVersion = 1;
33

34
    /**
35
     * Strict typing - force keys to be integer/Indexed.
36
     *
37
     * @var bool Strict typing for int/index array
38
     */
39
    protected bool $strict;
40

41
    /**
42
     * Positioning variable.
43
     *
44
     * @var int position sentinel variable
45
     */
46
    protected int $position;
47

48
    /**
49
     * Where all the data for the IndexedArray lives.
50
     *
51
     * @var array<int, mixed> Object data
52
     */
53
    protected array $data;
54

55
    public function __construct(array $data = [], bool $strict = true)
37✔
56
    {
57
        $this->strict = $strict;
37✔
58
        if (!$this->validateKeys($data)) {
37✔
59
            throw new InvalidKeyTypeException('Invalid Key type for Array');
1✔
60
        }
61

62
        $this->data     = $data;
37✔
63
        $this->position = 0;
37✔
64
    }
65

66
    /**
67
     * @inheritDoc
68
     */
69
    public function count() : int
4✔
70
    {
71
        return count($this->data);
4✔
72
    }
73

74
    /**
75
     * @inheritDoc
76
     */
77
    public function prev() : void
1✔
78
    {
79
        $this->position--;
1✔
80
    }
81

82
    /**
83
     * @param string $glue default: ' '
84
     *
85
     * @return StringInterface
86
     */
87
    public function implode($glue = ' ') : StringInterface
2✔
88
    {
89
        return new Twine(join($glue, $this->data));
2✔
90
    }
91

92
    // region Contractual Obligations
93

94
    /**
95
     * Build Array from data for serialization.
96
     *
97
     * @return array
98
     */
99
    public function __serialize() : array
1✔
100
    {
101
        // Check version and if mismatch call conversion method
102
        return [
1✔
103
            'version' => static::$serializeVersion,
1✔
104
            'model'   => get_class($this),
1✔
105
            'data'    => $this->data,
1✔
106
        ];
1✔
107
    }
108

109
    /**
110
     * Take Deserialized Array and populate object with that data.
111
     *
112
     * @param array $data
113
     *
114
     * @throws UnmatchedClassException
115
     * @throws UnmatchedVersionException
116
     *
117
     * @return void
118
     */
119
    public function __unserialize(array $data) : void
1✔
120
    {
121
        // Check version and if mismatch call conversion method
122
        if ($data['model'] !== get_class($this)) {
1✔
123
            throw new UnmatchedClassException();
1✔
124
        }
125

126
        if ($data['version'] !== static::$serializeVersion) {
1✔
127
            throw new UnmatchedVersionException();
1✔
128
        }
129

130
        $this->data = $data['data'];
1✔
131
    }
132

133
    /**
134
     * Whether a offset exists.
135
     *
136
     * @link   https://php.net/manual/en/arrayaccess.offsetexists.php
137
     *
138
     * @param mixed $offset <p>
139
     *                      An offset to check for.
140
     *                      </p>
141
     *
142
     * @return bool true on success or false on failure.
143
     *              </p>
144
     *              <p>
145
     *              The return value will be casted to boolean if non-boolean was returned.
146
     *
147
     * @since  5.0.0
148
     */
149
    public function offsetExists(mixed $offset) : bool
2✔
150
    {
151
        return isset($this->data[$offset]);
2✔
152
    }
153

154
    /**
155
     * Offset to retrieve.
156
     *
157
     * @param mixed $offset The offset to retrieve.
158
     *
159
     * @return mixed Can return all value types.
160
     */
161
    public function offsetGet(mixed $offset) : mixed
10✔
162
    {
163
        return $this->data[$offset];
10✔
164
    }
165

166
    /**
167
     * Offset to set.
168
     *
169
     * @param mixed $offset The offset to assign the value to.
170
     * @param mixed $value  The value to set.
171
     *
172
     * @return void
173
     *
174
     * @since  5.0.0
175
     */
176
    public function offsetSet(mixed $offset, mixed $value) : void
12✔
177
    {
178
        if ($this->isStrict() && !$this->validateKey($offset)) {
12✔
179
            throw new InvalidKeyTypeException(sprintf('Invalid Key type (%s) for Array', gettype($offset)));
1✔
180
        }
181

182
        if (isset($this->onInsert) && is_callable($this->onInsert)) {
12✔
183
            $onInsert = $this->onInsert; // may overload __call to check if member exists && is_callable()
2✔
184
            [
2✔
185
                $offset,
2✔
186
                $value,
2✔
187
            ] = $onInsert($offset, $value);
2✔
188
        }
189

190
        $this->data[$offset] = $value;
12✔
191

192
        if (isset($this->onInsertComplete) && is_callable($this->onInsertComplete)) {
12✔
193
            $onInsertComplete = $this->onInsertComplete;
1✔
194
            $onInsertComplete($this->data);
1✔
195
        }
196
    }
197

198
    /**
199
     * Offset to unset.
200
     *
201
     * @param mixed $offset The offset to unset.
202
     *
203
     * @return void
204
     */
205
    public function offsetUnset(mixed $offset) : void
6✔
206
    {
207
        if (isset($this->onRemove) && is_callable($this->onRemove)) {
6✔
208
            $onRemove = $this->onRemove;
1✔
209
            $onRemove($offset, $this->data[$offset]);
1✔
210
        }
211

212
        unset($this->data[$offset]);
6✔
213

214
        if (isset($this->onRemoveComplete) && is_callable($this->onRemoveComplete)) {
6✔
215
            $onRemoveComplete = $this->onRemoveComplete;
2✔
216
            $onRemoveComplete($this->data);
2✔
217
        }
218
    }
219

220
    /**
221
     * Return the current element.
222
     *
223
     * @link   https://php.net/manual/en/iterator.current.php
224
     *
225
     * @return mixed Can return any type.
226
     *
227
     * @since  5.0.0
228
     */
229
    public function current() : mixed
4✔
230
    {
231
        return ($this->valid()) ? array_values($this->data)[$this->position] : false;
4✔
232
    }
233

234
    /**
235
     * Move forward to next element.
236
     *
237
     * @link   https://php.net/manual/en/iterator.next.php
238
     *
239
     * @return void Any returned value is ignored.
240
     *
241
     * @since  5.0.0
242
     */
243
    public function next() : void
5✔
244
    {
245
        $this->position++;
5✔
246
    }
247

248
    /**
249
     * Return the key of the current element.
250
     *
251
     * @link   https://php.net/manual/en/iterator.key.php
252
     *
253
     * @return mixed scalar on success, or null on failure.
254
     *
255
     * @since  5.0.0
256
     */
257
    public function key() : mixed
2✔
258
    {
259
        return array_keys($this->data)[$this->position];
2✔
260
    }
261

262
    /**
263
     * Checks if current position is valid.
264
     *
265
     * @link   https://php.net/manual/en/iterator.valid.php
266
     *
267
     * @return bool The return value will be casted to boolean and then evaluated.
268
     *              Returns true on success or false on failure.
269
     *
270
     * @since  5.0.0
271
     */
272
    public function valid() : bool
4✔
273
    {
274
        return isset(array_values($this->data)[$this->position]);
4✔
275
    }
276

277
    /**
278
     * @inheritDoc
279
     */
280
    public function rewind() : void
4✔
281
    {
282
        $this->position = 0;
4✔
283
    }
284

285
    // endregion
286

287
    // region Public Methods
288

289
    /**
290
     * @return array
291
     */
292
    public function getData() : array
13✔
293
    {
294
        return $this->data;
13✔
295
    }
296

297
    /**
298
     * @inheritDoc
299
     */
300
    public function setData(array $data) : IndexedArrayInterface
4✔
301
    {
302
        if (isset($this->onSet) && is_callable($this->onSet)) {
4✔
303
            $onSet = $this->onSet;
1✔
304
            $onSet($data);
1✔
305
        }
306

307
        $this->data = $data;
4✔
308

309
        if (isset($this->onSetComplete) && is_callable($this->onSetComplete)) {
4✔
310
            $onSetComplete = $this->onSetComplete;
1✔
311
            $onSetComplete($this->data);
1✔
312
        }
313

314
        return $this;
4✔
315
    }
316

317
    public function isStrict() : bool
37✔
318
    {
319
        return $this->strict;
37✔
320
    }
321

322
    protected function validateKeys(array $dataSet) : bool
37✔
323
    {
324
        if ($this->isStrict()) {
37✔
325
            foreach (array_keys($dataSet) as $key) {
36✔
326
                if (!$this->validateKey($key)) {
26✔
327
                    return false;
1✔
328
                }
329
            }
330
        }
331

332
        return true;
37✔
333
    }
334

335
    protected function validateKey($key) : bool
32✔
336
    {
337
        return is_int($key);
32✔
338
    }
339

340
    public function merge(IndexedArrayInterface|array $collection) : void
1✔
341
    {
342
        $this->data = array_merge($this->data, $collection);
1✔
343
    }
344

345
    public function push(mixed $data) : IndexedArrayInterface
4✔
346
    {
347
        $this->offsetSet($this->getNextKey()->get(), $data);
4✔
348

349
        return $this;
4✔
350
    }
351

352
    /**
353
     * @inheritDoc
354
     */
355
    public function add(mixed $data) : IndexedArrayInterface
6✔
356
    {
357
        $this->offsetSet($this->getNextKey()->get(), $data);
6✔
358

359
        return $this;
6✔
360
    }
361

362
    /**
363
     * Find intersection of this and another collection - I would really like to explore putting this into the
364
     * CollectionInterface but for now  it's going to be a non-contracted function. I also REALLY would like to make a
365
     * strict option that allows for type matching.
366
     *
367
     * @param Collection $secondCollection
368
     *
369
     * @throws InvalidKeyTypeException
370
     *
371
     * @return Collection
372
     */
373
    public function intersection(Collection $secondCollection) : Collection
1✔
374
    {
375
        return new Collection(
1✔
376
            array_values(
1✔
377
            array_intersect($this->data, $secondCollection->getData())
1✔
378
        )
1✔
379
        );
1✔
380
    }
381

382
    /**
383
     * @inheritDoc
384
     */
385
    public function pop() : mixed
2✔
386
    {
387
        $value = array_pop($this->data);
2✔
388

389
        if (is_string($value)) {
2✔
390
            return new Twine($value);
1✔
391
        }
392

393
        if (is_array($value)) {
2✔
394
            return new Collection($value);
1✔
395
        }
396

397
        return $value;
1✔
398
    }
399

400
    /**
401
     * @inheritDoc
402
     */
403
    public function get(mixed $key) : mixed
6✔
404
    {
405
        return $this->offsetGet($key);
6✔
406
    }
407

408
    /**
409
     * @inheritDoc
410
     */
411
    public function first() : mixed
1✔
412
    {
413
        return $this->data[array_key_first($this->data)];
1✔
414
    }
415

416
    /**
417
     * Convert AbstractCollection to a AbstractList (Roll).
418
     *
419
     * @param Collection $indexes
420
     * @param            $rollClass Default: \PHPAlchemist\Type\Roll
421
     *
422
     * @throws \Exception
423
     *
424
     * @return AbstractList
425
     */
426
    public function toRoll(Collection $indexes = new Collection(), $rollClass = Roll::class) : AbstractList
1✔
427
    {
428
        if ($indexes->count() > 0 && $indexes->count() !== $this->count()) {
1✔
429
            throw new \Exception('Indexes count mismatch');
1✔
430
        }
431

432
        if ($indexes->count() === 0) {
1✔
433
            $indexes->setData(range(0, $this->count() - 1));
1✔
434
        }
435

436
        return new $rollClass(array_combine($indexes->getData(), $this->getData()));
1✔
437
    }
438

439
    /**
440
     * Get the value of a specified key and remove from
441
     * array.
442
     *
443
     * @param mixed $key The key for the element desired
444
     *
445
     * @return mixed
446
     */
447
    public function extract(mixed $key) : mixed
1✔
448
    {
449
        $returnValue = $this->data[$key];
1✔
450
        $this->delete($key);
1✔
451

452
        return $returnValue;
1✔
453
    }
454

455
    public function delete(mixed $key) : void
3✔
456
    {
457
        if (array_key_exists($key, $this->data)) {
3✔
458
            $this->offsetUnset($key);
3✔
459
        }
460
    }
461

462
    /**
463
     * Find the key for $value.
464
     *
465
     * @param mixed $value the value to search the array for
466
     *
467
     * @return int|false
468
     */
469
    public function search(mixed $value) : int|false
2✔
470
    {
471
        return array_search($value, $this->data);
2✔
472
    }
473

474
    public function clear() : void
2✔
475
    {
476
        if (isset($this->onClear) && is_callable($this->onClear)) {
2✔
477
            $onClear = $this->onClear;
1✔
478
            $onClear($this->data);
1✔
479
        }
480

481
        $this->data = [];
2✔
482
        $this->rewind();
2✔
483

484
        if (isset($this->onClearComplete) && is_callable($this->onClearComplete)) {
2✔
485
            $onClearComplete = $this->onClearComplete;
1✔
486
            $onClearComplete($this->data);
1✔
487
        }
488
    }
489

490
    public function isEmpty() : bool
×
491
    {
492
        return empty($this->data);
×
493
    }
494

495
    // endregion
496

497
    // region Protected Methods
498

499
    /**
500
     * Retrieves the maximum key value from the data array.
501
     * If the data array is empty, returns null.
502
     *
503
     * @return Number|null The maximum key value or null if the array is empty.
504
     */
505
    protected function getMaxKeyValue() : Number|null
9✔
506
    {
507
        $keys = array_keys($this->data);
9✔
508
        if (empty($keys)) {
9✔
509
            return null;
4✔
510
        }
511

512
        return new Number(max($keys));
8✔
513
    }
514

515
    /**
516
     * Retrieves the next key to be used based on the maximum key value currently in use.
517
     *
518
     * @return Number|null The next key value as a Number object, or null if no key value is found.
519
     */
520
    protected function getNextKey() : Number
9✔
521
    {
522
        $keyValue = $this->getMaxKeyValue();
9✔
523

524
        if (is_null($keyValue)) {
9✔
525
            return new Number(0);
4✔
526
        }
527

528
        $keyValue->add(1);
8✔
529

530
        return $keyValue;
8✔
531
    }
532

533
    // endregion
534
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc