Implement RepeatedFieldIter for c extension. (#2333)

diff --git a/php/ext/google/protobuf/array.c b/php/ext/google/protobuf/array.c
index 215dcd4..e4a88c3 100644
--- a/php/ext/google/protobuf/array.c
+++ b/php/ext/google/protobuf/array.c
@@ -54,6 +54,16 @@
   PHP_ME(RepeatedField, offsetSet,    arginfo_offsetSet, ZEND_ACC_PUBLIC)
   PHP_ME(RepeatedField, offsetUnset,  arginfo_offsetGet, ZEND_ACC_PUBLIC)
   PHP_ME(RepeatedField, count,        arginfo_void,      ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, getIterator,  arginfo_void,      ZEND_ACC_PUBLIC)
+  ZEND_FE_END
+};
+
+static zend_function_entry repeated_field_iter_methods[] = {
+  PHP_ME(RepeatedFieldIter, rewind,      arginfo_void, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedFieldIter, current,     arginfo_void, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedFieldIter, key,         arginfo_void, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedFieldIter, next,        arginfo_void, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedFieldIter, valid,       arginfo_void, ZEND_ACC_PUBLIC)
   ZEND_FE_END
 };
 
@@ -70,11 +80,15 @@
 static HashTable *repeated_field_get_gc(zval *object, zval ***table,
                                         int *n TSRMLS_DC);
 
+static zend_object_value repeated_field_iter_create(zend_class_entry *ce TSRMLS_DC);
+static void repeated_field_iter_free(void *object TSRMLS_DC);
+
 // -----------------------------------------------------------------------------
 // RepeatedField creation/desctruction
 // -----------------------------------------------------------------------------
 
 zend_class_entry* repeated_field_type;
+zend_class_entry* repeated_field_iter_type;
 zend_object_handlers* repeated_field_handlers;
 
 void repeated_field_init(TSRMLS_D) {
@@ -86,8 +100,8 @@
   repeated_field_type = zend_register_internal_class(&class_type TSRMLS_CC);
   repeated_field_type->create_object = repeated_field_create;
 
-  zend_class_implements(repeated_field_type TSRMLS_CC, 2, spl_ce_ArrayAccess,
-                        spl_ce_Countable);
+  zend_class_implements(repeated_field_type TSRMLS_CC, 3, spl_ce_ArrayAccess,
+                        zend_ce_aggregate, spl_ce_Countable);
 
   repeated_field_handlers = PEMALLOC(zend_object_handlers);
   memcpy(repeated_field_handlers, zend_get_std_object_handlers(),
@@ -386,3 +400,112 @@
 
   RETURN_LONG(zend_hash_num_elements(HASH_OF(intern->array)));
 }
+
+/**
+ * Return the beginning iterator.
+ * This will also be called for: foreach($arr)
+ * @return object Beginning iterator.
+ */
+PHP_METHOD(RepeatedField, getIterator) {
+  zval *iter_php = NULL;
+  MAKE_STD_ZVAL(iter_php);
+  Z_TYPE_P(iter_php) = IS_OBJECT;
+  Z_OBJVAL_P(iter_php) = repeated_field_iter_type->create_object(
+      repeated_field_iter_type TSRMLS_CC);
+
+  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedFieldIter *iter = zend_object_store_get_object(iter_php TSRMLS_CC);
+  iter->repeated_field = intern;
+  iter->position = 0;
+
+  RETURN_ZVAL(iter_php, 1, 1);
+}
+
+// -----------------------------------------------------------------------------
+// RepeatedFieldIter creation/desctruction
+// -----------------------------------------------------------------------------
+
+void repeated_field_iter_init(TSRMLS_D) {
+  zend_class_entry class_type;
+  const char* class_name = "Google\\Protobuf\\Internal\\RepeatedFieldIter";
+  INIT_CLASS_ENTRY_EX(class_type, class_name, strlen(class_name),
+                      repeated_field_iter_methods);
+
+  repeated_field_iter_type =
+      zend_register_internal_class(&class_type TSRMLS_CC);
+  repeated_field_iter_type->create_object = repeated_field_iter_create;
+
+  zend_class_implements(repeated_field_iter_type TSRMLS_CC, 1,
+                        zend_ce_iterator);
+}
+
+static zend_object_value repeated_field_iter_create(
+    zend_class_entry *ce TSRMLS_DC) {
+  zend_object_value retval = {0};
+  RepeatedFieldIter *intern;
+
+  intern = emalloc(sizeof(RepeatedFieldIter));
+  memset(intern, 0, sizeof(RepeatedFieldIter));
+
+  zend_object_std_init(&intern->std, ce TSRMLS_CC);
+  object_properties_init(&intern->std, ce);
+
+  intern->repeated_field = NULL;
+  intern->position = 0;
+
+  retval.handle = zend_objects_store_put(
+      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,
+      (zend_objects_free_object_storage_t)repeated_field_iter_free,
+      NULL TSRMLS_CC);
+  retval.handlers = zend_get_std_object_handlers();
+
+  return retval;
+}
+
+static void repeated_field_iter_free(void *object TSRMLS_DC) {
+  RepeatedFieldIter *intern = object;
+  zend_object_std_dtor(&intern->std TSRMLS_CC);
+  efree(object);
+}
+
+// -----------------------------------------------------------------------------
+// PHP RepeatedFieldIter Methods
+// -----------------------------------------------------------------------------
+
+PHP_METHOD(RepeatedFieldIter, rewind) {
+  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  intern->position = 0;
+}
+
+PHP_METHOD(RepeatedFieldIter, current) {
+  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedField *repeated_field = intern->repeated_field;
+
+  long index;
+  void *memory;
+
+  HashTable *table = HASH_OF(repeated_field->array);
+
+  if (zend_hash_index_find(table, intern->position, (void **)&memory) ==
+      FAILURE) {
+    zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index);
+    return;
+  }
+  native_slot_get(repeated_field->type, memory, return_value_ptr TSRMLS_CC);
+}
+
+PHP_METHOD(RepeatedFieldIter, key) {
+  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RETURN_LONG(intern->position);
+}
+
+PHP_METHOD(RepeatedFieldIter, next) {
+  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  ++intern->position;
+}
+
+PHP_METHOD(RepeatedFieldIter, valid) {
+  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RETURN_BOOL(zend_hash_num_elements(HASH_OF(intern->repeated_field->array)) >
+              intern->position);
+}
diff --git a/php/ext/google/protobuf/protobuf.c b/php/ext/google/protobuf/protobuf.c
index 019bca2..ea85b99 100644
--- a/php/ext/google/protobuf/protobuf.c
+++ b/php/ext/google/protobuf/protobuf.c
@@ -156,6 +156,7 @@
 static PHP_MINIT_FUNCTION(protobuf) {
   map_field_init(TSRMLS_C);
   repeated_field_init(TSRMLS_C);
+  repeated_field_iter_init(TSRMLS_C);
   gpb_type_init(TSRMLS_C);
   message_init(TSRMLS_C);
   descriptor_pool_init(TSRMLS_C);
diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h
index 8a1d926..93027bc 100644
--- a/php/ext/google/protobuf/protobuf.h
+++ b/php/ext/google/protobuf/protobuf.h
@@ -51,6 +51,7 @@
 struct MessageHeader;
 struct MessageLayout;
 struct RepeatedField;
+struct RepeatedFieldIter;
 struct MapField;
 
 typedef struct DescriptorPool DescriptorPool;
@@ -61,6 +62,7 @@
 typedef struct MessageHeader MessageHeader;
 typedef struct MessageLayout MessageLayout;
 typedef struct RepeatedField RepeatedField;
+typedef struct RepeatedFieldIter RepeatedFieldIter;
 typedef struct MapField MapField;
 
 // -----------------------------------------------------------------------------
@@ -77,6 +79,7 @@
 void gpb_type_init(TSRMLS_D);
 void map_field_init(TSRMLS_D);
 void repeated_field_init(TSRMLS_D);
+void repeated_field_iter_init(TSRMLS_D);
 void util_init(TSRMLS_D);
 void message_init(TSRMLS_D);
 
@@ -366,6 +369,12 @@
                                    // (for message field only).
 };
 
+struct RepeatedFieldIter {
+  zend_object std;
+  RepeatedField* repeated_field;
+  long position;
+};
+
 void repeated_field_create_with_type(zend_class_entry* ce,
                                      const upb_fielddef* field,
                                      zval** repeated_field TSRMLS_DC);
@@ -383,6 +392,13 @@
 PHP_METHOD(RepeatedField, offsetSet);
 PHP_METHOD(RepeatedField, offsetUnset);
 PHP_METHOD(RepeatedField, count);
+PHP_METHOD(RepeatedField, getIterator);
+
+PHP_METHOD(RepeatedFieldIter, rewind);
+PHP_METHOD(RepeatedFieldIter, current);
+PHP_METHOD(RepeatedFieldIter, key);
+PHP_METHOD(RepeatedFieldIter, next);
+PHP_METHOD(RepeatedFieldIter, valid);
 
 // -----------------------------------------------------------------------------
 // Oneof Field.
diff --git a/php/tests/array_test.php b/php/tests/array_test.php
index a118b54..a79a08b 100644
--- a/php/tests/array_test.php
+++ b/php/tests/array_test.php
@@ -65,6 +65,17 @@
         $this->assertSame(3, $arr[6]);
         $arr [7]= MAX_INT32_STRING;
         $this->assertSame(MAX_INT32, $arr[7]);
+
+        // Test foreach.
+        $arr = new RepeatedField(GPBType::INT32);
+        for ($i = 0; $i < 3; $i++) {
+          $arr []= $i;
+        }
+        $i = 0;
+        foreach ($arr as $val) {
+          $this->assertSame($i++, $val);
+        }
+        $this->assertSame(3, $i);
     }
 
     /**
diff --git a/tests.sh b/tests.sh
index 1335c06..7a12f26 100755
--- a/tests.sh
+++ b/tests.sh
@@ -408,7 +408,8 @@
 build_php5.6_mac() {
   # Install PHP
   curl -s https://php-osx.liip.ch/install.sh | bash -s 5.6
-  export PATH="/usr/local/php5-5.6.25-20160831-101628/bin:$PATH"
+  PHP_FOLDER=`find /usr/local -type d -name "php5-5.6*"`  # The folder name may change upon time
+  export PATH="$PHP_FOLDER/bin:$PATH"
 
   # Install phpunit
   curl https://phar.phpunit.de/phpunit.phar -L -o phpunit.phar