diff --git a/NEWS b/NEWS index b47f621ca523..27675244f832 100644 --- a/NEWS +++ b/NEWS @@ -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). diff --git a/UPGRADING b/UPGRADING index b6d9aa961dc0..70bf5c574322 100644 --- a/UPGRADING +++ b/UPGRADING @@ -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 diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index 261d9b19968a..f1e42889a5e4 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -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) { @@ -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; diff --git a/ext/spl/spl_observer.stub.php b/ext/spl/spl_observer.stub.php index 24d4fd08aa74..9e78272cf350 100644 --- a/ext/spl/spl_observer.stub.php +++ b/ext/spl/spl_observer.stub.php @@ -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 {} @@ -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 {} diff --git a/ext/spl/spl_observer_arginfo.h b/ext/spl/spl_observer_arginfo.h index 913e481e57d5..730f4c279c25 100644 --- a/ext/spl/spl_observer_arginfo.h +++ b/ext/spl/spl_observer_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1fc23a91e7531eeb73729d4ad44addf0190c3a62 */ + * Stub hash: a846c9dd240b6f0666cd5e805abfacabe360cf4c */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplObserver_update, 0, 1, IS_VOID, 0) ZEND_ARG_OBJ_INFO(0, subject, SplSubject, 0) @@ -59,6 +59,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_SplObjectStorage_next arginfo_class_SplSubject_notify +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SplObjectStorage_seek, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplObjectStorage_unserialize, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -147,6 +151,7 @@ ZEND_METHOD(SplObjectStorage, valid); ZEND_METHOD(SplObjectStorage, key); ZEND_METHOD(SplObjectStorage, current); ZEND_METHOD(SplObjectStorage, next); +ZEND_METHOD(SplObjectStorage, seek); ZEND_METHOD(SplObjectStorage, unserialize); ZEND_METHOD(SplObjectStorage, serialize); ZEND_METHOD(SplObjectStorage, offsetGet); @@ -194,6 +199,7 @@ static const zend_function_entry class_SplObjectStorage_methods[] = { ZEND_ME(SplObjectStorage, key, arginfo_class_SplObjectStorage_key, ZEND_ACC_PUBLIC) ZEND_ME(SplObjectStorage, current, arginfo_class_SplObjectStorage_current, ZEND_ACC_PUBLIC) ZEND_ME(SplObjectStorage, next, arginfo_class_SplObjectStorage_next, ZEND_ACC_PUBLIC) + ZEND_ME(SplObjectStorage, seek, arginfo_class_SplObjectStorage_seek, ZEND_ACC_PUBLIC) ZEND_ME(SplObjectStorage, unserialize, arginfo_class_SplObjectStorage_unserialize, ZEND_ACC_PUBLIC) ZEND_ME(SplObjectStorage, serialize, arginfo_class_SplObjectStorage_serialize, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("offsetExists", zim_SplObjectStorage_contains, arginfo_class_SplObjectStorage_offsetExists, ZEND_ACC_PUBLIC, NULL, NULL) @@ -244,13 +250,13 @@ static zend_class_entry *register_class_SplSubject(void) return class_entry; } -static zend_class_entry *register_class_SplObjectStorage(zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_Iterator, zend_class_entry *class_entry_Serializable, zend_class_entry *class_entry_ArrayAccess) +static zend_class_entry *register_class_SplObjectStorage(zend_class_entry *class_entry_Countable, zend_class_entry *class_entry_SeekableIterator, zend_class_entry *class_entry_Serializable, zend_class_entry *class_entry_ArrayAccess) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "SplObjectStorage", class_SplObjectStorage_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - zend_class_implements(class_entry, 4, class_entry_Countable, class_entry_Iterator, class_entry_Serializable, class_entry_ArrayAccess); + zend_class_implements(class_entry, 4, class_entry_Countable, class_entry_SeekableIterator, class_entry_Serializable, class_entry_ArrayAccess); return class_entry; } diff --git a/ext/spl/tests/SplObjectStorage_seek.phpt b/ext/spl/tests/SplObjectStorage_seek.phpt new file mode 100644 index 000000000000..f51a285d0600 --- /dev/null +++ b/ext/spl/tests/SplObjectStorage_seek.phpt @@ -0,0 +1,139 @@ +--TEST-- +SplObjectStorage::seek() basic functionality +--FILE-- +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