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

chdemko / php-sorted-collections / 24612242147

18 Apr 2026 07:32PM UTC coverage: 97.651% (-0.3%) from 97.912%
24612242147

push

github

chdemko
🚨 Fix scrutinizer complexity

28 of 30 new or added lines in 1 file covered. (93.33%)

790 of 809 relevant lines covered (97.65%)

118.47 hits per line

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

99.2
/src/SortedCollection/SubMap.php
1
<?php
2

3
/**
4
 * chdemko\SortedCollection\SubMap class
5
 *
6
 * @author    Christophe Demko <chdemko@gmail.com>
7
 * @copyright Copyright (C) 2012-2024 Christophe Demko. All rights reserved.
8
 *
9
 * @license BSD 3-Clause License
10
 *
11
 * This file is part of the php-sorted-collections package https://github.com/chdemko/php-sorted-collections
12
 */
13

14
// Declare chdemko\SortedCollection namespace
15
namespace chdemko\SortedCollection;
16

17
/**
18
 * Sub map
19
 *
20
 * @package    SortedCollection
21
 * @subpackage Map
22
 *
23
 * @since 1.0.0
24
 *
25
 * @property-read callable   $comparator     The key comparison function
26
 * @property-read TreeNode   $first          The first element of the map
27
 * @property-read mixed      $firstKey       The first key of the map
28
 * @property-read mixed      $firstValue     The first value of the map
29
 * @property-read TreeNode   $last           The last element of the map
30
 * @property-read mixed      $lastKey        The last key of the map
31
 * @property-read mixed      $lastValue      The last value of the map
32
 * @property-read Iterator   $keys           The keys iterator
33
 * @property-read Iterator   $values         The values iterator
34
 * @property-read integer    $count          The number of elements in the map
35
 * @property      mixed      $fromKey        The from key
36
 * @property      boolean    $fromInclusive  The from inclusive flag
37
 * @property      mixed      $toKey          The to key
38
 * @property      boolean    $toInclusive    The to inclusive flag
39
 * @property-read SortedMap  $map            The underlying map
40
 */
41
class SubMap extends AbstractMap
42
{
43
    /**
44
     * When the from or to key is unused
45
     *
46
     * @since 1.0.0
47
     */
48
    private const UNUSED = 0;
49

50
    /**
51
     * When the from or to key is inclusive
52
     *
53
     * @since 1.0.0
54
     */
55
    private const INCLUSIVE = 1;
56

57
    /**
58
     * When the from or to key is exclusive
59
     *
60
     * @since 1.0.0
61
     */
62
    private const EXCLUSIVE = 2;
63

64
    /**
65
     * @var SortedMap  Internal map
66
     *
67
     * @since 1.0.0
68
     */
69
    private $mapInternal;
70

71
    /**
72
     * @var integer  from option
73
     *
74
     * @since 1.0.0
75
     */
76
    private $fromOption;
77

78
    /**
79
     * @var mixed  from key
80
     *
81
     * @since 1.0.0
82
     */
83
    private $fromKey;
84

85
    /**
86
     * @var integer  to option
87
     *
88
     * @since 1.0.0
89
     */
90
    private $toOption;
91

92
    /**
93
     * @var mixed  to key
94
     *
95
     * @since 1.0.0
96
     */
97
    private $toKey;
98

99
    /**
100
     * @var boolean  Empty flag
101
     *
102
     * @since 1.0.0
103
     */
104
    private $empty;
105

106
    /**
107
     * Magic get method
108
     *
109
     * @param string $property The property
110
     *
111
     * @throws RuntimeException If the property does not exist
112
     *
113
     * @return mixed The value associated to the property
114
     *
115
     * @since 1.0.0
116
     */
117
    public function __get($property)
118
    {
119
        switch ($property) {
120
            case 'fromKey':
165✔
121
                if ($this->fromOption == self::UNUSED) {
14✔
122
                     throw new \RuntimeException('Undefined property');
3✔
123
                } else {
124
                    return $this->fromKey;
11✔
125
                }
126

127
            case 'toKey':
162✔
128
                if ($this->toOption == self::UNUSED) {
14✔
129
                    throw new \RuntimeException('Undefined property');
3✔
130
                } else {
131
                    return $this->toKey;
11✔
132
                }
133

134
            case 'fromInclusive':
159✔
135
                if ($this->fromOption == self::UNUSED) {
12✔
136
                    throw new \RuntimeException('Undefined property');
1✔
137
                } else {
138
                    return $this->fromOption == self::INCLUSIVE;
11✔
139
                }
140

141
            case 'toInclusive':
156✔
142
                if ($this->toOption == self::UNUSED) {
12✔
143
                    throw new \RuntimeException('Undefined property');
1✔
144
                } else {
145
                    return $this->toOption == self::INCLUSIVE;
11✔
146
                }
147

148
            case 'map':
146✔
149
                return $this->mapInternal;
136✔
150
            default:
151
                return parent::__get($property);
13✔
152
        }
153
    }
154

155
    /**
156
     * Magic set method
157
     *
158
     * @param string $property The property
159
     * @param mixed  $value    The new value
160
     *
161
     * @throws RuntimeException If the property does not exist
162
     *
163
     * @return void
164
     *
165
     * @since 1.0.0
166
     */
167
    public function __set($property, $value)
168
    {
169
        switch ($property) {
170
            case 'fromKey':
7✔
171
                $this->fromKey = $value;
2✔
172

173
                if ($this->fromOption == self::UNUSED) {
2✔
174
                     $this->fromOption = self::INCLUSIVE;
1✔
175
                }
176
                break;
2✔
177
            case 'toKey':
6✔
178
                $this->toKey = $value;
2✔
179

180
                if ($this->toOption == self::UNUSED) {
2✔
181
                    $this->toOption = self::EXCLUSIVE;
1✔
182
                }
183
                break;
2✔
184
            case 'fromInclusive':
5✔
185
                if ($this->fromOption == self::UNUSED) {
3✔
186
                    throw new \RuntimeException('Undefined property');
1✔
187
                } else {
188
                    $this->fromOption = $value ? self::INCLUSIVE : self::EXCLUSIVE;
2✔
189
                }
190
                break;
2✔
191
            case 'toInclusive':
4✔
192
                if ($this->toOption == self::UNUSED) {
3✔
193
                    throw new \RuntimeException('Undefined property');
1✔
194
                } else {
195
                    $this->toOption = $value ? self::INCLUSIVE : self::EXCLUSIVE;
2✔
196
                }
197
                break;
2✔
198
            default:
199
                throw new \RuntimeException('Undefined property');
1✔
200
        }
201

202
        $this->setEmpty();
4✔
203
    }
204

205
    /**
206
     * Magic unset method
207
     *
208
     * @param string $property The property
209
     *
210
     * @throws RuntimeException If the property does not exist
211
     *
212
     * @return void
213
     *
214
     * @since 1.0.0
215
     */
216
    public function __unset($property)
217
    {
218
        switch ($property) {
219
            case 'fromKey':
10✔
220
            case 'fromInclusive':
7✔
221
                $this->fromOption = self::UNUSED;
5✔
222
                break;
5✔
223
            case 'toKey':
6✔
224
            case 'toInclusive':
3✔
225
                $this->toOption = self::UNUSED;
5✔
226
                break;
5✔
227
            default:
228
                throw new \RuntimeException('Undefined property');
1✔
229
        }
230

231
        $this->setEmpty();
9✔
232
    }
233

234
    /**
235
     * Magic isset method
236
     *
237
     * @param string $property The property
238
     *
239
     * @return boolean
240
     *
241
     * @since 1.0.0
242
     */
243
    public function __isset($property)
244
    {
245
        switch ($property) {
246
            case 'fromKey':
13✔
247
            case 'fromInclusive':
13✔
248
                return $this->fromOption != self::UNUSED;
11✔
249
            case 'toKey':
12✔
250
            case 'toInclusive':
3✔
251
                return $this->toOption != self::UNUSED;
11✔
252
            default:
253
                return false;
1✔
254
        }
255
    }
256

257
    /**
258
     * Constructor
259
     *
260
     * @param SortedMap $map        Internal map
261
     * @param mixed     $fromKey    The from key
262
     * @param integer   $fromOption The option for from (SubMap::UNUSED, SubMap::INCLUSIVE or SubMap::EXCLUSIVE)
263
     * @param mixed     $toKey      The to key
264
     * @param integer   $toOption   The option for to (SubMap::UNUSED, SubMap::INCLUSIVE or SubMap::EXCLUSIVE)
265
     *
266
     * @since 1.0.0
267
     */
268
    protected function __construct(SortedMap $map, $fromKey, $fromOption, $toKey, $toOption)
269
    {
270
        $this->mapInternal = $map;
231✔
271
        $this->fromKey = $fromKey;
231✔
272
        $this->fromOption = $fromOption;
231✔
273
        $this->toKey = $toKey;
231✔
274
        $this->toOption = $toOption;
231✔
275
        $this->setEmpty();
231✔
276
    }
277

278
    /**
279
     * Set the empty flag
280
     *
281
     * @return void
282
     *
283
     * @since 1.0.0
284
     */
285
    protected function setEmpty()
286
    {
287
        if ($this->fromOption != self::UNUSED && $this->toOption != self::UNUSED) {
231✔
288
            $cmp = $this->compareKeys($this->fromKey, $this->toKey);
180✔
289

290
            $this->empty = $cmp > 0
180✔
291
              || $cmp == 0 && ($this->fromOption == self::EXCLUSIVE || $this->toOption == self::EXCLUSIVE);
180✔
292
        } else {
293
            $this->empty = false;
56✔
294
        }
295
    }
296

297
    /**
298
     * Compare 2 keys using the map comparator
299
     *
300
     * @param mixed $key1 First key
301
     * @param mixed $key2 Second key
302
     *
303
     * @return integer Comparison result
304
     *
305
     * @since 1.0.0
306
     */
307
    private function compareKeys($key1, $key2)
308
    {
309
        return call_user_func($this->mapInternal->comparator(), $key1, $key2);
188✔
310
    }
311

312
    /**
313
     * Validate lower() input against the lower bound
314
     *
315
     * @param mixed $key The searched key
316
     *
317
     * @return void
318
     *
319
     * @throws OutOfBoundsException If lower bound excludes the key
320
     *
321
     * @since 1.0.0
322
     */
323
    private function assertLowerAllowed($key)
324
    {
325
        if ($this->fromOption != self::UNUSED && $this->compareKeys($key, $this->fromKey) <= 0) {
27✔
326
            throw new \OutOfBoundsException('Lower element unexisting');
6✔
327
        }
328
    }
329

330
    /**
331
     * Validate higher() input against the upper bound
332
     *
333
     * @param mixed $key The searched key
334
     *
335
     * @return void
336
     *
337
     * @throws OutOfBoundsException If upper bound excludes the key
338
     *
339
     * @since 1.0.0
340
     */
341
    private function assertHigherAllowed($key)
342
    {
343
        if ($this->toOption != self::UNUSED && $this->compareKeys($key, $this->toKey) >= 0) {
24✔
344
            throw new \OutOfBoundsException('Higher element unexisting');
5✔
345
        }
346
    }
347

348
    /**
349
     * Ensure lower() result respects an exclusive lower bound
350
     *
351
     * @param TreeNode $node The found node
352
     *
353
     * @return void
354
     *
355
     * @throws OutOfBoundsException If result is out of bounds
356
     *
357
     * @since 1.0.0
358
     */
359
    private function assertLowerResultInRange($node)
360
    {
361
        if ($node === null) {
20✔
NEW
362
            return;
×
363
        }
364

365
        if ($this->fromOption == self::EXCLUSIVE && $this->compareKeys($node->key, $this->fromKey) <= 0) {
20✔
366
            throw new \OutOfBoundsException('Lower element unexisting');
2✔
367
        }
368
    }
369

370
    /**
371
     * Ensure higher() result respects an exclusive upper bound
372
     *
373
     * @param TreeNode $node The found node
374
     *
375
     * @return void
376
     *
377
     * @throws OutOfBoundsException If result is out of bounds
378
     *
379
     * @since 1.0.0
380
     */
381
    private function assertHigherResultInRange($node)
382
    {
383
        if ($node === null) {
17✔
NEW
384
            return;
×
385
        }
386

387
        if ($this->toOption == self::EXCLUSIVE && $this->compareKeys($node->key, $this->toKey) >= 0) {
17✔
388
            throw new \OutOfBoundsException('Higher element unexisting');
1✔
389
        }
390
    }
391

392
    /**
393
     * Clamp a lower-like result against the upper bound
394
     *
395
     * @param TreeNode $node The found node
396
     *
397
     * @return TreeNode The clamped node
398
     *
399
     * @since 1.0.0
400
     */
401
    private function clampLowerToUpperBound($node)
402
    {
403
        if (
404
            ($this->toOption == self::INCLUSIVE && $this->compareKeys($node->key, $this->toKey) > 0)
18✔
405
            || ($this->toOption == self::EXCLUSIVE && $this->compareKeys($node->key, $this->toKey) >= 0)
18✔
406
        ) {
407
            return $this->last();
4✔
408
        }
409

410
        return $node;
14✔
411
    }
412

413
    /**
414
     * Clamp a higher-like result against the lower bound
415
     *
416
     * @param TreeNode $node The found node
417
     *
418
     * @return TreeNode The clamped node
419
     *
420
     * @since 1.0.0
421
     */
422
    private function clampHigherToLowerBound($node)
423
    {
424
        if (
425
            ($this->fromOption == self::INCLUSIVE && $this->compareKeys($node->key, $this->fromKey) < 0)
16✔
426
            || ($this->fromOption == self::EXCLUSIVE && $this->compareKeys($node->key, $this->fromKey) <= 0)
16✔
427
        ) {
428
            return $this->first();
4✔
429
        }
430

431
        return $node;
12✔
432
    }
433

434
    /**
435
     * Create
436
     *
437
     * @param SortedMap $map           A sorted map
438
     * @param mixed     $fromKey       The from key
439
     * @param mixed     $toKey         The to key
440
     * @param boolean   $fromInclusive The inclusive flag for from
441
     * @param boolean   $toInclusive   The inclusive flag for to
442
     *
443
     * @return SubMap A new sub map
444
     *
445
     * @since 1.0.0
446
     */
447
    public static function create(SortedMap $map, $fromKey, $toKey, $fromInclusive = true, $toInclusive = false)
448
    {
449
        return new static(
178✔
450
            $map,
178✔
451
            $fromKey,
178✔
452
            $fromInclusive ? self::INCLUSIVE : self::EXCLUSIVE,
178✔
453
            $toKey,
178✔
454
            $toInclusive ? self::INCLUSIVE : self::EXCLUSIVE
178✔
455
        );
178✔
456
    }
457

458
    /**
459
     * Return a head portion of a sorted map
460
     *
461
     * @param SortedMap $map         A sorted map
462
     * @param mixed     $toKey       The to key
463
     * @param boolean   $toInclusive The inclusive flag for to
464
     *
465
     * @return SubMap A new head map
466
     *
467
     * @since 1.0.0
468
     */
469
    public static function head(SortedMap $map, $toKey, $toInclusive = false)
470
    {
471
        return new static($map, null, self::UNUSED, $toKey, $toInclusive ? self::INCLUSIVE : self::EXCLUSIVE);
23✔
472
    }
473

474
    /**
475
     * Return a tail portion of a sorted map
476
     *
477
     * @param SortedMap $map           A sorted map
478
     * @param mixed     $fromKey       The from key
479
     * @param boolean   $fromInclusive The inclusive flag for from
480
     *
481
     * @return SubMap A new tail map
482
     *
483
     * @since 1.0.0
484
     */
485
    public static function tail(SortedMap $map, $fromKey, $fromInclusive = true)
486
    {
487
        return new static($map, $fromKey, $fromInclusive ? self::INCLUSIVE : self::EXCLUSIVE, null, self::UNUSED);
26✔
488
    }
489

490
    /**
491
     * Return a view of the map
492
     *
493
     * @param SortedMap $map A sorted map
494
     *
495
     * @return SubMap A new sub map
496
     *
497
     * @since 1.0.0
498
     */
499
    public static function view(SortedMap $map)
500
    {
501
        return new static($map, null, self::UNUSED, null, self::UNUSED);
4✔
502
    }
503

504
    /**
505
     * Get the comparator
506
     *
507
     * @return callable The comparator
508
     *
509
     * @since 1.0.0
510
     */
511
    public function comparator()
512
    {
513
        return $this->mapInternal->comparator();
1✔
514
    }
515

516
    /**
517
     * Get the first element
518
     *
519
     * @return mixed The first element
520
     *
521
     * @throws OutOfBoundsException If there is no element
522
     *
523
     * @since 1.0.0
524
     */
525
    public function first()
526
    {
527
        if ($this->empty) {
40✔
528
            throw new \OutOfBoundsException('First element unexisting');
5✔
529
        }
530

531
        switch ($this->fromOption) {
36✔
532
            case self::INCLUSIVE:
533
                $first = $this->mapInternal->ceiling($this->fromKey);
13✔
534
                break;
13✔
535
            case self::EXCLUSIVE:
536
                $first = $this->mapInternal->higher($this->fromKey);
18✔
537
                break;
18✔
538
            default:
539
                $first = $this->mapInternal->first();
5✔
540
                break;
5✔
541
        }
542

543
        return $first;
36✔
544
    }
545

546
    /**
547
     * Get the last element
548
     *
549
     * @return mixed The last element
550
     *
551
     * @throws OutOfBoundsException If there is no element
552
     *
553
     * @since 1.0.0
554
     */
555
    public function last()
556
    {
557
        if ($this->empty) {
17✔
558
            throw new \OutOfBoundsException('Last element unexisting');
2✔
559
        }
560

561
        switch ($this->toOption) {
15✔
562
            case self::INCLUSIVE:
563
                $last = $this->map->floor($this->toKey);
4✔
564
                break;
4✔
565
            case self::EXCLUSIVE:
566
                $last = $this->map->lower($this->toKey);
10✔
567
                break;
10✔
568
            default:
569
                $last = $this->map->last();
1✔
570
                break;
1✔
571
        }
572

573
        return $last;
15✔
574
    }
575

576
    /**
577
     * Get the predecessor element
578
     *
579
     * @param TreeNode $element A tree node member of the underlying TreeMap
580
     *
581
     * @return mixed The predecessor element
582
     *
583
     * @throws OutOfBoundsException If there is no predecessor
584
     *
585
     * @since 1.0.0
586
     */
587
    public function predecessor($element)
588
    {
589
        $predecessor = $this->map->predecessor($element);
4✔
590

591
        if ($predecessor) {
4✔
592
            switch ($this->fromOption) {
4✔
593
                case self::INCLUSIVE:
594
                    if (call_user_func($this->map->comparator(), $predecessor->key, $this->fromKey) < 0) {
2✔
595
                        throw new \OutOfBoundsException('Predecessor element unexisting');
1✔
596
                    }
597
                    break;
1✔
598
                case self::EXCLUSIVE:
599
                    if (call_user_func($this->map->comparator(), $predecessor->key, $this->fromKey) <= 0) {
2✔
600
                        throw new \OutOfBoundsException('Predecessor element unexisting');
1✔
601
                    }
602
                    break;
1✔
603
            }
604
        }
605

606
        return $predecessor;
2✔
607
    }
608

609
    /**
610
     * Get the successor element
611
     *
612
     * @param TreeNode $element A tree node member of the underlying TreeMap
613
     *
614
     * @return mixed The successor element
615
     *
616
     * @throws OutOfBoundsException If there is no successor
617
     *
618
     * @since 1.0.0
619
     */
620
    public function successor($element)
621
    {
622
        $successor = $this->map->successor($element);
21✔
623

624
        if ($successor) {
21✔
625
            switch ($this->toOption) {
21✔
626
                case self::INCLUSIVE:
627
                    if (call_user_func($this->map->comparator(), $successor->key, $this->toKey) > 0) {
9✔
628
                        throw new \OutOfBoundsException('Successor element unexisting');
8✔
629
                    }
630
                    break;
8✔
631
                case self::EXCLUSIVE:
632
                    if (call_user_func($this->map->comparator(), $successor->key, $this->toKey) >= 0) {
9✔
633
                        throw new \OutOfBoundsException('Successor element unexisting');
8✔
634
                    }
635
                    break;
8✔
636
            }
637
        }
638

639
        return $successor;
19✔
640
    }
641

642
    /**
643
     * Returns the element whose key is the greatest key lesser than the given key
644
     *
645
     * @param mixed $key The searched key
646
     *
647
     * @return mixed The found element
648
     *
649
     * @throws OutOfBoundsException If there is no lower element
650
     *
651
     * @since 1.0.0
652
     */
653
    public function lower($key)
654
    {
655
        if ($this->empty) {
29✔
656
            throw new \OutOfBoundsException('Lower element unexisting');
2✔
657
        }
658

659
        $this->assertLowerAllowed($key);
27✔
660

661
        $lower = $this->mapInternal->lower($key);
21✔
662
        $this->assertLowerResultInRange($lower);
20✔
663

664
        if ($lower) {
18✔
665
            $lower = $this->clampLowerToUpperBound($lower);
18✔
666
        }
667

668
        return $lower;
18✔
669
    }
670

671
    /**
672
     * Returns the element whose key is the greatest key lesser than or equal to the given key
673
     *
674
     * @param mixed $key The searched key
675
     *
676
     * @return mixed The found element
677
     *
678
     * @throws OutOfBoundsException If there is no floor element
679
     *
680
     * @since 1.0.0
681
     */
682
    public function floor($key)
683
    {
684
        if ($this->empty) {
25✔
685
            throw new \OutOfBoundsException('Floor element unexisting');
2✔
686
        }
687

688
        switch ($this->fromOption) {
23✔
689
            case self::INCLUSIVE:
690
                if (call_user_func($this->map->comparator(), $key, $this->fromKey) < 0) {
8✔
691
                    throw new \OutOfBoundsException('Floor element unexisting');
2✔
692
                } else {
693
                    $floor = $this->map->floor($key);
6✔
694
                }
695
                break;
6✔
696
            case self::EXCLUSIVE:
697
                if (call_user_func($this->map->comparator(), $key, $this->fromKey) <= 0) {
12✔
698
                    throw new \OutOfBoundsException('Floor element unexisting');
4✔
699
                } else {
700
                    $floor = $this->map->floor($key);
8✔
701
                }
702
                break;
8✔
703
            default:
704
                $floor = $this->map->floor($key);
3✔
705
                break;
3✔
706
        }
707

708
        if ($floor) {
17✔
709
            switch ($this->toOption) {
17✔
710
                case self::INCLUSIVE:
711
                    if (call_user_func($this->map->comparator(), $floor->key, $this->toKey) > 0) {
7✔
712
                          $floor = $this->last();
2✔
713
                    }
714
                    break;
7✔
715
                case self::EXCLUSIVE:
716
                    if (call_user_func($this->map->comparator(), $floor->key, $this->toKey) >= 0) {
10✔
717
                        $floor = $this->last();
6✔
718
                    }
719
                    break;
10✔
720
            }
721
        }
722

723
        return $floor;
17✔
724
    }
725

726
    /**
727
     * Returns the element whose key is equal to the given key
728
     *
729
     * @param mixed $key The searched key
730
     *
731
     * @return mixed The found element
732
     *
733
     * @throws OutOfBoundsException  If there is no such element
734
     *
735
     * @since 1.0.0
736
     */
737
    public function find($key)
738
    {
739
        switch ($this->fromOption) {
56✔
740
            case self::INCLUSIVE:
741
                if (call_user_func($this->map->comparator(), $key, $this->fromKey) < 0) {
26✔
742
                     throw new \OutOfBoundsException('Element unexisting');
8✔
743
                }
744
                break;
18✔
745
            case self::EXCLUSIVE:
746
                if (call_user_func($this->map->comparator(), $key, $this->fromKey) <= 0) {
30✔
747
                    throw new \OutOfBoundsException('Element unexisting');
8✔
748
                }
749
                break;
22✔
750
        }
751

752
        switch ($this->toOption) {
40✔
753
            case self::INCLUSIVE:
754
                if (call_user_func($this->map->comparator(), $key, $this->toKey) > 0) {
18✔
755
                    throw new \OutOfBoundsException('Element unexisting');
4✔
756
                }
757
                break;
14✔
758
            case self::EXCLUSIVE:
759
                if (call_user_func($this->map->comparator(), $key, $this->toKey) >= 0) {
22✔
760
                    throw new \OutOfBoundsException('Element unexisting');
8✔
761
                }
762
                break;
14✔
763
        }
764

765
        return $this->map->find($key);
28✔
766
    }
767

768
    /**
769
     * Returns the element whose key is the lowest key greater than or equal to the given key
770
     *
771
     * @param mixed $key The searched key
772
     *
773
     * @return mixed The found element
774
     *
775
     * @throws OutOfBoundsException If there is no ceiling element
776
     *
777
     * @since 1.0.0
778
     */
779
    public function ceiling($key)
780
    {
781
        if ($this->empty) {
23✔
782
            throw new \OutOfBoundsException('Ceiling element unexisting');
2✔
783
        }
784

785
        switch ($this->toOption) {
21✔
786
            case self::INCLUSIVE:
787
                if (call_user_func($this->map->comparator(), $key, $this->toKey) > 0) {
9✔
788
                    throw new \OutOfBoundsException('Ceiling element unexisting');
2✔
789
                } else {
790
                    $ceiling = $this->map->ceiling($key);
7✔
791
                }
792
                break;
7✔
793
            case self::EXCLUSIVE:
794
                if (call_user_func($this->map->comparator(), $key, $this->toKey) >= 0) {
6✔
795
                    throw new \OutOfBoundsException('Ceiling element unexisting');
2✔
796
                } else {
797
                    $ceiling = $this->map->ceiling($key);
4✔
798
                }
799
                break;
4✔
800
            default:
801
                $ceiling = $this->map->ceiling($key);
6✔
802
                break;
5✔
803
        }
804

805
        if ($ceiling) {
16✔
806
            switch ($this->fromOption) {
16✔
807
                case self::INCLUSIVE:
808
                    if (call_user_func($this->map->comparator(), $ceiling->key, $this->fromKey) < 0) {
8✔
809
                          $ceiling = $this->first();
3✔
810
                    }
811
                    break;
8✔
812
                case self::EXCLUSIVE:
813
                    if (call_user_func($this->map->comparator(), $ceiling->key, $this->fromKey) <= 0) {
8✔
814
                        $ceiling = $this->first();
6✔
815
                    }
816
                    break;
8✔
817
            }
818
        }
819

820
        return $ceiling;
16✔
821
    }
822

823
    /**
824
     * Returns the element whose key is the lowest key greater than to the given key
825
     *
826
     * @param mixed $key The searched key
827
     *
828
     * @return mixed The found element
829
     *
830
     * @throws OutOfBoundsException If there is no higher element
831
     *
832
     * @since 1.0.0
833
     */
834
    public function higher($key)
835
    {
836
        if ($this->empty) {
26✔
837
            throw new \OutOfBoundsException('Higher element unexisting');
2✔
838
        }
839

840
        $this->assertHigherAllowed($key);
24✔
841

842
        $higher = $this->mapInternal->higher($key);
19✔
843
        $this->assertHigherResultInRange($higher);
17✔
844

845
        if ($higher) {
16✔
846
            $higher = $this->clampHigherToLowerBound($higher);
16✔
847
        }
848

849
        return $higher;
16✔
850
    }
851

852
    /**
853
     * Serialize the object
854
     *
855
     * @return array Array of values
856
     *
857
     * @since 1.0.0
858
     */
859
    public function jsonSerialize(): array
860
    {
861
        if ($this->fromOption == self::UNUSED) {
8✔
862
            if ($this->toOption == self::UNUSED) {
2✔
863
                return array(
1✔
864
                    'ViewMap' => array(
1✔
865
                        'map' => $this->map->jsonSerialize(),
1✔
866
                     )
1✔
867
                );
1✔
868
            } else {
869
                return array(
1✔
870
                    'HeadMap' => array(
1✔
871
                        'map' => $this->map->jsonSerialize(),
1✔
872
                        'toKey' => $this->toKey,
1✔
873
                        'toInclusive' => $this->toOption == self::INCLUSIVE,
1✔
874
                    )
1✔
875
                );
1✔
876
            }
877
        } else {
878
            if ($this->toOption == self::UNUSED) {
6✔
879
                return array(
1✔
880
                    'TailMap' => array(
1✔
881
                        'map' => $this->map->jsonSerialize(),
1✔
882
                        'fromKey' => $this->fromKey,
1✔
883
                        'fromInclusive' => $this->fromOption == self::INCLUSIVE,
1✔
884
                    )
1✔
885
                );
1✔
886
            } else {
887
                return array(
5✔
888
                    'SubMap' => array(
5✔
889
                        'map' => $this->map->jsonSerialize(),
5✔
890
                        'fromKey' => $this->fromKey,
5✔
891
                        'fromInclusive' => $this->fromOption == self::INCLUSIVE,
5✔
892
                        'toKey' => $this->toKey,
5✔
893
                        'toInclusive' => $this->toOption == self::INCLUSIVE,
5✔
894
                    )
5✔
895
                );
5✔
896
            }
897
        }
898
    }
899

900
    /**
901
     * Count the number of key/value pairs
902
     *
903
     * @return integer
904
     *
905
     * @since 1.0.0
906
     */
907
    public function count(): int
908
    {
909
        $count = 0;
5✔
910

911
        foreach ($this as $value) {
5✔
912
            $count++;
4✔
913
        }
914

915
        return $count;
5✔
916
    }
917
}
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