Skip to content

Commit ff19ec2

Browse files
committed
Introduce InternalIterator
Userland classes that implement Traversable must do so either through Iterator or IteratorAggregate. The same requirement does not exist for internal classes: They can implement the internal get_iterator mechanism, without exposing either the Iterator or IteratorAggregate APIs. This makes them usable in get_iterator(), but incompatible with any Iterator based APIs. A lot of internal classes do this, because exposing the userland APIs is simply a lot of work. This patch alleviates this issue by providing a generic InternalIterator class, which acts as an adapater between get_iterator and Iterator, and can be easily used by many internal classes. At the same time, we extend the requirement that Traversable implies Iterator or IteratorAggregate to internal classes as well. Closes GH-5216.
1 parent 4730b06 commit ff19ec2

33 files changed

+488
-30
lines changed

UPGRADING

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,17 @@ PHP 8.0 UPGRADE NOTES
766766
longer referenced.
767767
. The deprecated parameter `$version` of curl_version() has been removed.
768768

769+
- Date:
770+
. DatePeriod now implements IteratorAggregate (instead of Traversable).
771+
772+
- DOM:
773+
. DOMNamedNodeMap now implements IteratorAggregate (instead of Traversable).
774+
. DOMNodeList now implements IteratorAggregate (instead of Traversable).
775+
776+
- Intl:
777+
. IntlBreakIterator now implements IteratorAggregate (instead of Traversable).
778+
. ResourceBundle now implements IteratorAggregate (instead of Traversable).
779+
769780
- Enchant:
770781
. The enchant extension now uses libenchant-2 by default when available.
771782
libenchant version 1 is still supported but is deprecated and could
@@ -786,9 +797,13 @@ PHP 8.0 UPGRADE NOTES
786797
- MBString:
787798
. The Unicode data tables have been updated to version 13.0.0.
788799

800+
- PDO:
801+
. PDOStatement now implements IteratorAggregate (instead of Traversable).
802+
789803
- MySQLi / PDO MySQL:
790804
. When mysqlnd is not used (which is the default and recommended option),
791805
the minimum supported libmysqlclient version is now 5.1.
806+
. mysqli_result now implements IteratorAggregate (instead of Traversable).
792807

793808
- PGSQL / PDO PGSQL:
794809
. The PGSQL and PDO PGSQL extensions now require at least libpq 9.1.

UPGRADING.INTERNALS

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ PHP 8.0 INTERNALS UPGRADE NOTES
1818
o. cast_object() object handler is now required
1919
p. ARG_COUNT() macro removed
2020
q. GC_COLLECTABLE flag
21+
r. Cannot implement Traversable only
2122

2223
2. Build system changes
2324
a. Abstract
@@ -122,6 +123,16 @@ PHP 8.0 INTERNALS UPGRADE NOTES
122123
Assignments to GC_TYPE_INFO() might need to be changed to properly
123124
set the value of the GC_NOT_COLLECTABLE flag.
124125

126+
r. Just for for userland classes, it is no longer allowed to implement only
127+
the Traversable interface. Instead, it is necessary to implement either
128+
Iterator or IteratorAggregate. You can do the latter by implementing
129+
zend_ce_aggregate and providing the following method implementation:
130+
131+
ZEND_METHOD(MyClass, getIterator) {
132+
ZEND_PARSE_PARAMETERS_NONE();
133+
zend_create_internal_iterator_zval(return_value, ZEND_THIS);
134+
}
135+
125136
========================
126137
2. Build system changes
127138
========================

Zend/zend_interfaces.c

Lines changed: 178 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ ZEND_API zend_class_entry *zend_ce_arrayaccess;
2929
ZEND_API zend_class_entry *zend_ce_serializable;
3030
ZEND_API zend_class_entry *zend_ce_countable;
3131
ZEND_API zend_class_entry *zend_ce_stringable;
32+
ZEND_API zend_class_entry *zend_ce_internal_iterator;
33+
34+
static zend_object_handlers zend_internal_iterator_handlers;
3235

3336
/* {{{ zend_call_method
3437
Only returns the returned zval if retval_ptr != NULL */
@@ -246,20 +249,16 @@ ZEND_API zend_object_iterator *zend_user_it_get_new_iterator(zend_class_entry *c
246249
/* {{{ zend_implement_traversable */
247250
static int zend_implement_traversable(zend_class_entry *interface, zend_class_entry *class_type)
248251
{
249-
/* check that class_type is traversable at c-level or implements at least one of 'aggregate' and 'Iterator' */
250-
uint32_t i;
251-
252-
if (class_type->get_iterator || (class_type->parent && class_type->parent->get_iterator)) {
253-
return SUCCESS;
254-
}
255252
/* Abstract class can implement Traversable only, in which case the extending class must
256253
* implement Iterator or IteratorAggregate. */
257254
if (class_type->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) {
258255
return SUCCESS;
259256
}
257+
258+
/* Check that class_type implements at least one of 'IteratorAggregate' or 'Iterator' */
260259
if (class_type->num_interfaces) {
261260
ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_RESOLVED_INTERFACES);
262-
for (i = 0; i < class_type->num_interfaces; i++) {
261+
for (uint32_t i = 0; i < class_type->num_interfaces; i++) {
263262
if (class_type->interfaces[i] == zend_ce_aggregate || class_type->interfaces[i] == zend_ce_iterator) {
264263
return SUCCESS;
265264
}
@@ -441,9 +440,169 @@ static int zend_implement_serializable(zend_class_entry *interface, zend_class_e
441440
}
442441
/* }}}*/
443442

443+
typedef struct {
444+
zend_object std;
445+
zend_object_iterator *iter;
446+
zend_bool rewind_called;
447+
} zend_internal_iterator;
448+
449+
static zend_object *zend_internal_iterator_create(zend_class_entry *ce) {
450+
zend_internal_iterator *intern = emalloc(sizeof(zend_internal_iterator));
451+
zend_object_std_init(&intern->std, ce);
452+
intern->std.handlers = &zend_internal_iterator_handlers;
453+
intern->iter = NULL;
454+
intern->rewind_called = 0;
455+
return &intern->std;
456+
}
457+
458+
ZEND_API int zend_create_internal_iterator_zval(zval *return_value, zval *obj) {
459+
zend_class_entry *scope = EG(current_execute_data)->func->common.scope;
460+
ZEND_ASSERT(scope->get_iterator != zend_user_it_get_new_iterator);
461+
zend_object_iterator *iter = scope->get_iterator(Z_OBJCE_P(obj), obj, /* by_ref */ 0);
462+
if (!iter) {
463+
return FAILURE;
464+
}
465+
466+
zend_internal_iterator *intern =
467+
(zend_internal_iterator *) zend_internal_iterator_create(zend_ce_internal_iterator);
468+
intern->iter = iter;
469+
ZVAL_OBJ(return_value, &intern->std);
470+
return SUCCESS;
471+
}
472+
473+
static void zend_internal_iterator_free(zend_object *obj) {
474+
zend_internal_iterator *intern = (zend_internal_iterator *) obj;
475+
if (intern->iter) {
476+
zend_iterator_dtor(intern->iter);
477+
}
478+
zend_object_std_dtor(&intern->std);
479+
}
480+
481+
static zend_internal_iterator *zend_internal_iterator_fetch(zval *This) {
482+
zend_internal_iterator *intern = (zend_internal_iterator *) Z_OBJ_P(This);
483+
if (!intern->iter) {
484+
zend_throw_error(NULL, "The InternalIterator object has not been properly initialized");
485+
return NULL;
486+
}
487+
return intern;
488+
}
489+
490+
/* Many iterators will not behave correctly if rewind() is not called, make sure it happens. */
491+
static int zend_internal_iterator_ensure_rewound(zend_internal_iterator *intern) {
492+
if (!intern->rewind_called) {
493+
zend_object_iterator *iter = intern->iter;
494+
intern->rewind_called = 1;
495+
if (iter->funcs->rewind) {
496+
iter->funcs->rewind(iter);
497+
if (UNEXPECTED(EG(exception))) {
498+
return FAILURE;
499+
}
500+
}
501+
}
502+
return SUCCESS;
503+
}
504+
505+
506+
ZEND_METHOD(InternalIterator, __construct) {
507+
zend_throw_error(NULL, "Cannot manually construct InternalIterator");
508+
}
509+
510+
ZEND_METHOD(InternalIterator, current) {
511+
ZEND_PARSE_PARAMETERS_NONE();
512+
513+
zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS);
514+
if (!intern) {
515+
RETURN_THROWS();
516+
}
517+
518+
if (zend_internal_iterator_ensure_rewound(intern) == FAILURE) {
519+
RETURN_THROWS();
520+
}
521+
522+
zval *data = intern->iter->funcs->get_current_data(intern->iter);
523+
if (data) {
524+
ZVAL_COPY_DEREF(return_value, data);
525+
}
526+
}
527+
528+
ZEND_METHOD(InternalIterator, key) {
529+
ZEND_PARSE_PARAMETERS_NONE();
530+
531+
zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS);
532+
if (!intern) {
533+
RETURN_THROWS();
534+
}
535+
536+
if (zend_internal_iterator_ensure_rewound(intern) == FAILURE) {
537+
RETURN_THROWS();
538+
}
539+
540+
if (intern->iter->funcs->get_current_key) {
541+
intern->iter->funcs->get_current_key(intern->iter, return_value);
542+
} else {
543+
RETURN_LONG(intern->iter->index);
544+
}
545+
}
546+
547+
ZEND_METHOD(InternalIterator, next) {
548+
ZEND_PARSE_PARAMETERS_NONE();
549+
550+
zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS);
551+
if (!intern) {
552+
RETURN_THROWS();
553+
}
554+
555+
if (zend_internal_iterator_ensure_rewound(intern) == FAILURE) {
556+
RETURN_THROWS();
557+
}
558+
559+
intern->iter->funcs->move_forward(intern->iter);
560+
intern->iter->index++;
561+
}
562+
563+
ZEND_METHOD(InternalIterator, valid) {
564+
ZEND_PARSE_PARAMETERS_NONE();
565+
566+
zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS);
567+
if (!intern) {
568+
RETURN_THROWS();
569+
}
570+
571+
if (zend_internal_iterator_ensure_rewound(intern) == FAILURE) {
572+
RETURN_THROWS();
573+
}
574+
575+
RETURN_BOOL(intern->iter->funcs->valid(intern->iter) == SUCCESS);
576+
}
577+
578+
ZEND_METHOD(InternalIterator, rewind) {
579+
ZEND_PARSE_PARAMETERS_NONE();
580+
581+
zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS);
582+
if (!intern) {
583+
RETURN_THROWS();
584+
}
585+
586+
if (!intern->iter->funcs->rewind) {
587+
/* Allow calling rewind() if no iteration has happened yet,
588+
* even if the iterator does not support rewinding. */
589+
if (intern->iter->index != 0) {
590+
zend_throw_error(NULL, "Iterator does not support rewinding");
591+
RETURN_THROWS();
592+
}
593+
intern->iter->index = 0;
594+
return;
595+
}
596+
597+
intern->iter->funcs->rewind(intern->iter);
598+
intern->iter->index = 0;
599+
}
600+
444601
/* {{{ zend_register_interfaces */
445602
ZEND_API void zend_register_interfaces(void)
446603
{
604+
zend_class_entry ce;
605+
447606
REGISTER_MAGIC_INTERFACE(traversable, Traversable);
448607

449608
REGISTER_MAGIC_INTERFACE(aggregate, IteratorAggregate);
@@ -454,7 +613,6 @@ ZEND_API void zend_register_interfaces(void)
454613

455614
REGISTER_MAGIC_INTERFACE(serializable, Serializable);
456615

457-
zend_class_entry ce;
458616
INIT_CLASS_ENTRY(ce, "ArrayAccess", class_ArrayAccess_methods);
459617
zend_ce_arrayaccess = zend_register_internal_interface(&ce);
460618

@@ -463,5 +621,17 @@ ZEND_API void zend_register_interfaces(void)
463621

464622
INIT_CLASS_ENTRY(ce, "Stringable", class_Stringable_methods);
465623
zend_ce_stringable = zend_register_internal_interface(&ce);
624+
625+
INIT_CLASS_ENTRY(ce, "InternalIterator", class_InternalIterator_methods);
626+
zend_ce_internal_iterator = zend_register_internal_class(&ce);
627+
zend_class_implements(zend_ce_internal_iterator, 1, zend_ce_iterator);
628+
zend_ce_internal_iterator->ce_flags |= ZEND_ACC_FINAL;
629+
zend_ce_internal_iterator->create_object = zend_internal_iterator_create;
630+
zend_ce_internal_iterator->serialize = zend_class_serialize_deny;
631+
zend_ce_internal_iterator->unserialize = zend_class_unserialize_deny;
632+
633+
memcpy(&zend_internal_iterator_handlers, zend_get_std_object_handlers(),
634+
sizeof(zend_object_handlers));
635+
zend_internal_iterator_handlers.free_obj = zend_internal_iterator_free;
466636
}
467637
/* }}} */

Zend/zend_interfaces.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ ZEND_API int zend_user_unserialize(zval *object, zend_class_entry *ce, const uns
7878
ZEND_API int zend_class_serialize_deny(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data);
7979
ZEND_API int zend_class_unserialize_deny(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);
8080

81+
ZEND_API int zend_create_internal_iterator_zval(zval *return_value, zval *obj);
82+
8183
END_EXTERN_C()
8284

8385
#endif /* ZEND_INTERFACES_H */

Zend/zend_interfaces.stub.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,20 @@ interface Stringable
6363
{
6464
public function __toString(): string;
6565
}
66+
67+
final class InternalIterator implements Iterator
68+
{
69+
private function __construct();
70+
71+
/** @return mixed */
72+
public function current();
73+
74+
/** @return mixed */
75+
public function key();
76+
77+
public function next(): void;
78+
79+
public function valid(): bool;
80+
81+
public function rewind(): void;
82+
}

Zend/zend_interfaces_arginfo.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,27 @@ ZEND_END_ARG_INFO()
3838
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Stringable___toString, 0, 0, IS_STRING, 0)
3939
ZEND_END_ARG_INFO()
4040

41+
#define arginfo_class_InternalIterator___construct arginfo_class_IteratorAggregate_getIterator
4142

43+
#define arginfo_class_InternalIterator_current arginfo_class_IteratorAggregate_getIterator
44+
45+
#define arginfo_class_InternalIterator_key arginfo_class_IteratorAggregate_getIterator
46+
47+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_InternalIterator_next, 0, 0, IS_VOID, 0)
48+
ZEND_END_ARG_INFO()
49+
50+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_InternalIterator_valid, 0, 0, _IS_BOOL, 0)
51+
ZEND_END_ARG_INFO()
52+
53+
#define arginfo_class_InternalIterator_rewind arginfo_class_InternalIterator_next
54+
55+
56+
ZEND_METHOD(InternalIterator, __construct);
57+
ZEND_METHOD(InternalIterator, current);
58+
ZEND_METHOD(InternalIterator, key);
59+
ZEND_METHOD(InternalIterator, next);
60+
ZEND_METHOD(InternalIterator, valid);
61+
ZEND_METHOD(InternalIterator, rewind);
4262

4363

4464
static const zend_function_entry class_Traversable_methods[] = {
@@ -88,3 +108,14 @@ static const zend_function_entry class_Stringable_methods[] = {
88108
ZEND_ABSTRACT_ME_WITH_FLAGS(Stringable, __toString, arginfo_class_Stringable___toString, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT)
89109
ZEND_FE_END
90110
};
111+
112+
113+
static const zend_function_entry class_InternalIterator_methods[] = {
114+
ZEND_ME(InternalIterator, __construct, arginfo_class_InternalIterator___construct, ZEND_ACC_PRIVATE)
115+
ZEND_ME(InternalIterator, current, arginfo_class_InternalIterator_current, ZEND_ACC_PUBLIC)
116+
ZEND_ME(InternalIterator, key, arginfo_class_InternalIterator_key, ZEND_ACC_PUBLIC)
117+
ZEND_ME(InternalIterator, next, arginfo_class_InternalIterator_next, ZEND_ACC_PUBLIC)
118+
ZEND_ME(InternalIterator, valid, arginfo_class_InternalIterator_valid, ZEND_ACC_PUBLIC)
119+
ZEND_ME(InternalIterator, rewind, arginfo_class_InternalIterator_rewind, ZEND_ACC_PUBLIC)
120+
ZEND_FE_END
121+
};

Zend/zend_weakrefs.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,15 @@ ZEND_METHOD(WeakMap, count)
576576
RETURN_LONG(count);
577577
}
578578

579+
ZEND_METHOD(WeakMap, getIterator)
580+
{
581+
if (zend_parse_parameters_none() == FAILURE) {
582+
return;
583+
}
584+
585+
zend_create_internal_iterator_zval(return_value, ZEND_THIS);
586+
}
587+
579588
void zend_register_weakref_ce(void) /* {{{ */
580589
{
581590
zend_class_entry ce;
@@ -597,16 +606,14 @@ void zend_register_weakref_ce(void) /* {{{ */
597606
INIT_CLASS_ENTRY(ce, "WeakMap", class_WeakMap_methods);
598607
zend_ce_weakmap = zend_register_internal_class(&ce);
599608
zend_ce_weakmap->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NO_DYNAMIC_PROPERTIES;
609+
zend_class_implements(
610+
zend_ce_weakmap, 3, zend_ce_arrayaccess, zend_ce_countable, zend_ce_aggregate);
600611

601612
zend_ce_weakmap->create_object = zend_weakmap_create_object;
602613
zend_ce_weakmap->get_iterator = zend_weakmap_get_iterator;
603614
zend_ce_weakmap->serialize = zend_class_serialize_deny;
604615
zend_ce_weakmap->unserialize = zend_class_unserialize_deny;
605616

606-
/* Must happen after get_iterator is assigned. */
607-
zend_class_implements(
608-
zend_ce_weakmap, 3, zend_ce_arrayaccess, zend_ce_countable, zend_ce_traversable);
609-
610617
memcpy(&zend_weakmap_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
611618
zend_weakmap_handlers.offset = XtOffsetOf(zend_weakmap, std);
612619
zend_weakmap_handlers.free_obj = zend_weakmap_free_obj;

0 commit comments

Comments
 (0)