Skip to content

Commit f97bd07

Browse files
authored
Implement SeekableIterator for SplObjectStorage (#13665)
1 parent 8637a3f commit f97bd07

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
@@ -239,6 +239,9 @@ PHP NEWS
239239
. Add support for AEGIS-128L and AEGIS-256 (jedisct1)
240240
. Enable AES-GCM on aarch64 with the ARM crypto extensions (jedisct1)
241241

242+
- SPL:
243+
. Implement SeekableIterator for SplObjectStorage. (nielsdos)
244+
242245
- Standard:
243246
. Implement GH-12188 (Indication for the int size in phpinfo()). (timwolla)
244247
. 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
@@ -504,6 +504,10 @@ PHP 8.4 UPGRADE NOTES
504504
. sodium_crypto_aead_aes256gcm_*() functions are now enabled on aarch64 CPUs
505505
with the ARM cryptographic extensions.
506506

507+
- SPL:
508+
. Added seek() method to SplObjectStorage, now it implements
509+
SeekableIterator.
510+
507511
- Standard:
508512
. Added the http_get_last_response_headers() and
509513
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
@@ -758,6 +758,49 @@ PHP_METHOD(SplObjectStorage, next)
758758
intern->index++;
759759
} /* }}} */
760760

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

1329-
spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, zend_ce_iterator, zend_ce_serializable, zend_ce_arrayaccess);
1372+
spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, spl_ce_SeekableIterator, zend_ce_serializable, zend_ce_arrayaccess);
13301373
spl_ce_SplObjectStorage->create_object = spl_SplObjectStorage_new;
13311374
spl_ce_SplObjectStorage->default_object_handlers = &spl_handler_SplObjectStorage;
13321375

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)