Skip to content

Implement SeekableIterator for SplObjectStorage #13665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ PHP NEWS
. Add support for AEGIS-128L and AEGIS-256 (jedisct1)
. Enable AES-GCM on aarch64 with the ARM crypto extensions (jedisct1)

- SPL:
. Implement SeekableIterator for SplObjectStorage. (nielsdos)

- Standard:
. Implement GH-12188 (Indication for the int size in phpinfo()). (timwolla)
. Partly fix GH-12143 (Incorrect round() result for 0.49999999999999994).
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,10 @@ PHP 8.4 UPGRADE NOTES
. sodium_crypto_aead_aes256gcm_*() functions are now enabled on aarch64 CPUs
with the ARM cryptographic extensions.

- SPL:
. Added seek() method to SplObjectStorage, now it implements
SeekableIterator.

- Standard:
. Added the http_get_last_response_headers() and
http_clear_last_response_headers() that allows retrieving the same content
Expand Down
45 changes: 44 additions & 1 deletion ext/spl/spl_observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,49 @@ PHP_METHOD(SplObjectStorage, next)
intern->index++;
} /* }}} */

/* {{{ Seek to position. */
PHP_METHOD(SplObjectStorage, seek)
{
zend_long position;
spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);

if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &position) == FAILURE) {
RETURN_THROWS();
}

if (position < 0 || position >= zend_hash_num_elements(&intern->storage)) {
zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Seek position " ZEND_LONG_FMT " is out of range", position);
RETURN_THROWS();
}

if (position == 0) {
/* fast path */
zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
intern->index = 0;
} else if (position > intern->index) {
/* unlike the optimization below, it's not cheap to go to the end */
do {
zend_hash_move_forward_ex(&intern->storage, &intern->pos);
intern->index++;
} while (position > intern->index);
} else if (position < intern->index) {
/* optimization: check if it's more profitable to reset and do a forwards seek instead, it's cheap to reset */
if (intern->index - position > position) {
zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
intern->index = 0;
do {
zend_hash_move_forward_ex(&intern->storage, &intern->pos);
intern->index++;
} while (position > intern->index);
} else {
do {
zend_hash_move_backwards_ex(&intern->storage, &intern->pos);
intern->index--;
} while (position < intern->index);
}
}
} /* }}} */

/* {{{ Serializes storage */
PHP_METHOD(SplObjectStorage, serialize)
{
Expand Down Expand Up @@ -1326,7 +1369,7 @@ PHP_MINIT_FUNCTION(spl_observer)
spl_ce_SplObserver = register_class_SplObserver();
spl_ce_SplSubject = register_class_SplSubject();

spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, zend_ce_iterator, zend_ce_serializable, zend_ce_arrayaccess);
spl_ce_SplObjectStorage = register_class_SplObjectStorage(zend_ce_countable, spl_ce_SeekableIterator, zend_ce_serializable, zend_ce_arrayaccess);
spl_ce_SplObjectStorage->create_object = spl_SplObjectStorage_new;
spl_ce_SplObjectStorage->default_object_handlers = &spl_handler_SplObjectStorage;

Expand Down
4 changes: 3 additions & 1 deletion ext/spl/spl_observer.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function detach(SplObserver $observer): void;
public function notify(): void;
}

class SplObjectStorage implements Countable, Iterator, Serializable, ArrayAccess
class SplObjectStorage implements Countable, SeekableIterator, Serializable, ArrayAccess
{
/** @tentative-return-type */
public function attach(object $object, mixed $info = null): void {}
Expand Down Expand Up @@ -64,6 +64,8 @@ public function current(): object {}
/** @tentative-return-type */
public function next(): void {}

public function seek(int $offset): void {}

/** @tentative-return-type */
public function unserialize(string $data): void {}

Expand Down
12 changes: 9 additions & 3 deletions ext/spl/spl_observer_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

139 changes: 139 additions & 0 deletions ext/spl/tests/SplObjectStorage_seek.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
--TEST--
SplObjectStorage::seek() basic functionality
--FILE--
<?php

class Test {
public function __construct(public string $marker) {}
}

$a = new Test("a");
$b = new Test("b");
$c = new Test("c");
$d = new Test("d");
$e = new Test("e");

$storage = new SplObjectStorage();
$storage[$a] = 1;
$storage[$b] = 2;
$storage[$c] = 3;
$storage[$d] = 4;
$storage[$e] = 5;

echo "--- Error cases ---\n";

try {
$storage->seek(-1);
} catch (OutOfBoundsException $e) {
echo $e->getMessage(), "\n";
}
try {
$storage->seek(5);
} catch (OutOfBoundsException $e) {
echo $e->getMessage(), "\n";
}

var_dump($storage->key());
var_dump($storage->current());

echo "--- Normal cases ---\n";

$storage->seek(2);
var_dump($storage->key());
var_dump($storage->current());

$storage->seek(1);
var_dump($storage->key());
var_dump($storage->current());

$storage->seek(4);
var_dump($storage->key());
var_dump($storage->current());

$storage->seek(0);
var_dump($storage->key());
var_dump($storage->current());

$storage->seek(3);
var_dump($storage->key());
var_dump($storage->current());

$storage->seek(3);
var_dump($storage->key());
var_dump($storage->current());

echo "--- With holes cases ---\n";

$storage->detach($b);
$storage->detach($d);

foreach (range(0, 2) as $index) {
$storage->seek($index);
var_dump($storage->key());
var_dump($storage->current());
}

try {
$storage->seek(3);
} catch (OutOfBoundsException $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
--- Error cases ---
Seek position -1 is out of range
Seek position 5 is out of range
int(0)
object(Test)#1 (1) {
["marker"]=>
string(1) "a"
}
--- Normal cases ---
int(2)
object(Test)#3 (1) {
["marker"]=>
string(1) "c"
}
int(1)
object(Test)#2 (1) {
["marker"]=>
string(1) "b"
}
int(4)
object(Test)#5 (1) {
["marker"]=>
string(1) "e"
}
int(0)
object(Test)#1 (1) {
["marker"]=>
string(1) "a"
}
int(3)
object(Test)#4 (1) {
["marker"]=>
string(1) "d"
}
int(3)
object(Test)#4 (1) {
["marker"]=>
string(1) "d"
}
--- With holes cases ---
int(0)
object(Test)#1 (1) {
["marker"]=>
string(1) "a"
}
int(1)
object(Test)#3 (1) {
["marker"]=>
string(1) "c"
}
int(2)
object(Test)#5 (1) {
["marker"]=>
string(1) "e"
}
Seek position 3 is out of range
Loading