Skip to content

Commit bc45caa

Browse files
committed
Implement SeekableIterator for SplObjectStorage
1 parent edbef3e commit bc45caa

File tree

6 files changed

+202
-5
lines changed

6 files changed

+202
-5
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ PHP NEWS
192192
. Add support for AEGIS-128L and AEGIS-256 (jedisct1)
193193
. Enable AES-GCM on aarch64 with the ARM crypto extensions (jedisct1)
194194

195+
- SPL:
196+
. Implement SeekableIterator for SplObjectStorage. (nielsdos)
197+
195198
- Standard:
196199
. Implement GH-12188 (Indication for the int size in phpinfo()). (timwolla)
197200
. Partly fix GH-12143 (Incorrect round() result for 0.49999999999999994).

UPGRADING

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,10 @@ PHP 8.4 UPGRADE NOTES
436436
. sodium_crypto_aead_aes256gcm_*() functions are now enabled on aarch64 CPUs
437437
with the ARM cryptographic extensions.
438438

439+
- SPL:
440+
. Added seek() method to SplObjectStorage, now it implements
441+
SeekableIterator.
442+
439443
- Standard:
440444
. Added the http_get_last_response_headers() and
441445
http_clear_last_response_headers() that allows retrieving the same content

ext/spl/spl_observer.c

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,49 @@ PHP_METHOD(SplObjectStorage, next)
757757
intern->index++;
758758
} /* }}} */
759759

760+
/* {{{ Seek to position. */
761+
PHP_METHOD(SplObjectStorage, seek)
762+
{
763+
zend_long position;
764+
spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
765+
766+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &position) == FAILURE) {
767+
RETURN_THROWS();
768+
}
769+
770+
if (position < 0 || position >= zend_hash_num_elements(&intern->storage)) {
771+
zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Seek position " ZEND_LONG_FMT " is out of range", position);
772+
RETURN_THROWS();
773+
}
774+
775+
if (position == 0) {
776+
/* fast path */
777+
zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
778+
intern->index = 0;
779+
} else if (position > intern->index) {
780+
/* unlike the optimization below, it's not cheap to go to the end */
781+
do {
782+
zend_hash_move_forward_ex(&intern->storage, &intern->pos);
783+
intern->index++;
784+
} while (position > intern->index);
785+
} else if (position < intern->index) {
786+
/* optimization: check if it's more profitable to reset and do a forwards seek instead, it's cheap to reset */
787+
if (intern->index - position > position) {
788+
zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
789+
intern->index = 0;
790+
do {
791+
zend_hash_move_forward_ex(&intern->storage, &intern->pos);
792+
intern->index++;
793+
} while (position > intern->index);
794+
} else {
795+
do {
796+
zend_hash_move_backwards_ex(&intern->storage, &intern->pos);
797+
intern->index--;
798+
} while (position < intern->index);
799+
}
800+
}
801+
} /* }}} */
802+
760803
/* {{{ Serializes storage */
761804
PHP_METHOD(SplObjectStorage, serialize)
762805
{
@@ -1325,7 +1368,7 @@ PHP_MINIT_FUNCTION(spl_observer)
13251368
spl_ce_SplObserver = register_class_SplObserver();
13261369
spl_ce_SplSubject = register_class_SplSubject();
13271370

1328-
spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, zend_ce_iterator, zend_ce_serializable, zend_ce_arrayaccess);
1371+
spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, spl_ce_SeekableIterator, zend_ce_serializable, zend_ce_arrayaccess);
13291372
spl_ce_SplObjectStorage->create_object = spl_SplObjectStorage_new;
13301373
spl_ce_SplObjectStorage->default_object_handlers = &spl_handler_SplObjectStorage;
13311374

ext/spl/spl_observer.stub.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function detach(SplObserver $observer): void;
2020
public function notify(): void;
2121
}
2222

23-
class SplObjectStorage implements Countable, Iterator, Serializable, ArrayAccess
23+
class SplObjectStorage implements Countable, SeekableIterator, Serializable, ArrayAccess
2424
{
2525
/** @tentative-return-type */
2626
public function attach(object $object, mixed $info = null): void {}
@@ -64,6 +64,8 @@ public function current(): object {}
6464
/** @tentative-return-type */
6565
public function next(): void {}
6666

67+
public function seek(int $offset): void {}
68+
6769
/** @tentative-return-type */
6870
public function unserialize(string $data): void {}
6971

ext/spl/spl_observer_arginfo.h

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
--TEST--
2+
SplObjectStorage::seek() basic functionality
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function __construct(public string $marker) {}
8+
}
9+
10+
$a = new Test("a");
11+
$b = new Test("b");
12+
$c = new Test("c");
13+
$d = new Test("d");
14+
$e = new Test("e");
15+
16+
$storage = new SplObjectStorage();
17+
$storage[$a] = 1;
18+
$storage[$b] = 2;
19+
$storage[$c] = 3;
20+
$storage[$d] = 4;
21+
$storage[$e] = 5;
22+
23+
echo "--- Error cases ---\n";
24+
25+
try {
26+
$storage->seek(-1);
27+
} catch (OutOfBoundsException $e) {
28+
echo $e->getMessage(), "\n";
29+
}
30+
try {
31+
$storage->seek(5);
32+
} catch (OutOfBoundsException $e) {
33+
echo $e->getMessage(), "\n";
34+
}
35+
36+
var_dump($storage->key());
37+
var_dump($storage->current());
38+
39+
echo "--- Normal cases ---\n";
40+
41+
$storage->seek(2);
42+
var_dump($storage->key());
43+
var_dump($storage->current());
44+
45+
$storage->seek(1);
46+
var_dump($storage->key());
47+
var_dump($storage->current());
48+
49+
$storage->seek(4);
50+
var_dump($storage->key());
51+
var_dump($storage->current());
52+
53+
$storage->seek(0);
54+
var_dump($storage->key());
55+
var_dump($storage->current());
56+
57+
$storage->seek(3);
58+
var_dump($storage->key());
59+
var_dump($storage->current());
60+
61+
$storage->seek(3);
62+
var_dump($storage->key());
63+
var_dump($storage->current());
64+
65+
echo "--- With holes cases ---\n";
66+
67+
$storage->detach($b);
68+
$storage->detach($d);
69+
70+
foreach (range(0, 2) as $index) {
71+
$storage->seek($index);
72+
var_dump($storage->key());
73+
var_dump($storage->current());
74+
}
75+
76+
try {
77+
$storage->seek(3);
78+
} catch (OutOfBoundsException $e) {
79+
echo $e->getMessage(), "\n";
80+
}
81+
82+
?>
83+
--EXPECT--
84+
--- Error cases ---
85+
Seek position -1 is out of range
86+
Seek position 5 is out of range
87+
int(0)
88+
object(Test)#1 (1) {
89+
["marker"]=>
90+
string(1) "a"
91+
}
92+
--- Normal cases ---
93+
int(2)
94+
object(Test)#3 (1) {
95+
["marker"]=>
96+
string(1) "c"
97+
}
98+
int(1)
99+
object(Test)#2 (1) {
100+
["marker"]=>
101+
string(1) "b"
102+
}
103+
int(4)
104+
object(Test)#5 (1) {
105+
["marker"]=>
106+
string(1) "e"
107+
}
108+
int(0)
109+
object(Test)#1 (1) {
110+
["marker"]=>
111+
string(1) "a"
112+
}
113+
int(3)
114+
object(Test)#4 (1) {
115+
["marker"]=>
116+
string(1) "d"
117+
}
118+
int(3)
119+
object(Test)#4 (1) {
120+
["marker"]=>
121+
string(1) "d"
122+
}
123+
--- With holes cases ---
124+
int(0)
125+
object(Test)#1 (1) {
126+
["marker"]=>
127+
string(1) "a"
128+
}
129+
int(1)
130+
object(Test)#3 (1) {
131+
["marker"]=>
132+
string(1) "c"
133+
}
134+
int(2)
135+
object(Test)#5 (1) {
136+
["marker"]=>
137+
string(1) "e"
138+
}
139+
Seek position 3 is out of range

0 commit comments

Comments
 (0)