Port php c extension to php8 (#7793)

* Only ported c extension to php8.
* Didn't fixed the issue of throwing warnings for missing arginfo in bundled files.
* Tests not fixed, because syntax of phpunit (<7 vs >9.3) are not compatible.
* In next release, needs to drop php5 and php7.0 support (in order to use phpunit > 7)
diff --git a/.gitignore b/.gitignore
index 7235f6b..9c1484a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -137,8 +137,10 @@
 
 # php test output
 composer.lock
+php/tests/.phpunit.result.cache
 php/tests/generated/
 php/tests/old_protoc
+php/tests/phpunit-9.phar
 php/tests/protobuf/
 php/tests/core
 php/tests/vgcore*
diff --git a/Makefile.am b/Makefile.am
index 15b5a42..bced216 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -915,23 +915,23 @@
   php/src/Google/Protobuf/UInt64Value.php                             \
   php/src/Google/Protobuf/Value.php                                   \
   php/src/phpdoc.dist.xml                                             \
-  php/tests/array_test.php                                            \
+  php/tests/ArrayTest.php                                             \
   php/tests/autoload.php                                              \
   php/tests/bootstrap_phpunit.php                                     \
   php/tests/compatibility_test.sh                                     \
   php/tests/compile_extension.sh                                      \
-  php/tests/descriptors_test.php                                      \
-  php/tests/encode_decode_test.php                                    \
+  php/tests/DescriptorsTest.php                                       \
+  php/tests/EncodeDecodeTest.php                                      \
   php/tests/gdb_test.sh                                               \
   php/tests/generate_protos.sh                                        \
-  php/tests/generated_class_test.php                                  \
-  php/tests/generated_phpdoc_test.php                                 \
-  php/tests/generated_service_test.php                                \
-  php/tests/map_field_test.php                                        \
+  php/tests/GeneratedClassTest.php                                    \
+  php/tests/GeneratedPhpdocTest.php                                   \
+  php/tests/GeneratedServiceTest.php                                  \
+  php/tests/MapFieldTest.php                                          \
   php/tests/memory_leak_test.php                                      \
   php/tests/multirequest.php                                          \
   php/tests/multirequest.sh                                           \
-  php/tests/php_implementation_test.php                               \
+  php/tests/PhpImplementationTest.php                                 \
   php/tests/proto/empty/echo.proto                                    \
   php/tests/proto/test.proto                                          \
   php/tests/proto/test_descriptors.proto                              \
@@ -955,8 +955,8 @@
   php/tests/test_util.php                                             \
   php/tests/undefined_test.php                                        \
   php/tests/valgrind.supp                                             \
-  php/tests/well_known_test.php                                       \
-  php/tests/wrapper_type_setters_test.php
+  php/tests/WellKnownTest.php                                         \
+  php/tests/WrapperTypeSettersTest.php
 
 python_EXTRA_DIST=                                                           \
   python/MANIFEST.in                                                         \
diff --git a/kokoro/linux/php80/build.sh b/kokoro/linux/php80/build.sh
new file mode 100755
index 0000000..6499b39
--- /dev/null
+++ b/kokoro/linux/php80/build.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# This is the top-level script we give to Kokoro as the entry point for
+# running the "pull request" project:
+#
+# This script selects a specific Dockerfile (for building a Docker image) and
+# a script to run inside that image.  Then we delegate to the general
+# build_and_run_docker.sh script.
+
+# Change to repo root
+cd $(dirname $0)/../../..
+
+export DOCKERHUB_ORGANIZATION=protobuftesting
+export DOCKERFILE_DIR=kokoro/linux/dockerfile/test/php
+export DOCKER_RUN_SCRIPT=kokoro/linux/pull_request_in_docker.sh
+export OUTPUT_DIR=testoutput
+export TEST_SET="php8.0_all"
+./kokoro/linux/build_and_run_docker.sh
diff --git a/kokoro/linux/php80/continuous.cfg b/kokoro/linux/php80/continuous.cfg
new file mode 100644
index 0000000..8426318
--- /dev/null
+++ b/kokoro/linux/php80/continuous.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/php80/build.sh"
+timeout_mins: 120
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.xml"
+  }
+}
diff --git a/kokoro/linux/php80/presubmit.cfg b/kokoro/linux/php80/presubmit.cfg
new file mode 100644
index 0000000..8426318
--- /dev/null
+++ b/kokoro/linux/php80/presubmit.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/php80/build.sh"
+timeout_mins: 120
+
+action {
+  define_artifacts {
+    regex: "**/sponge_log.xml"
+  }
+}
diff --git a/php/ext/google/protobuf/array.c b/php/ext/google/protobuf/array.c
index 443bdb1..4615ed3 100644
--- a/php/ext/google/protobuf/array.c
+++ b/php/ext/google/protobuf/array.c
@@ -94,11 +94,12 @@
   zend_object_std_dtor(&intern->std);
 }
 
-static HashTable *RepeatedField_GetProperties(zval *object) {
+static HashTable *RepeatedField_GetProperties(PROTO_VAL *object) {
   return NULL;  // We do not have a properties table.
 }
 
-static zval *RepeatedField_GetPropertyPtrPtr(zval *object, zval *member,
+static zval *RepeatedField_GetPropertyPtrPtr(PROTO_VAL *object,
+                                             PROTO_STR *member,
                                              int type, void **cache_slot) {
   return NULL;  // We don't offer direct references to our properties.
 }
@@ -392,6 +393,15 @@
   RETURN_ZVAL(&ret, 0, 1);
 }
 
+ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1)
+  ZEND_ARG_INFO(0, type)
+  ZEND_ARG_INFO(0, class)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_append, 0, 0, 1)
+  ZEND_ARG_INFO(0, newval)
+ZEND_END_ARG_INFO()
+
 ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetGet, 0, 0, 1)
   ZEND_ARG_INFO(0, index)
 ZEND_END_ARG_INFO()
@@ -405,8 +415,8 @@
 ZEND_END_ARG_INFO()
 
 static zend_function_entry repeated_field_methods[] = {
-  PHP_ME(RepeatedField, __construct,  NULL,              ZEND_ACC_PUBLIC)
-  PHP_ME(RepeatedField, append,       NULL,              ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, __construct,  arginfo_construct, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, append,       arginfo_append,    ZEND_ACC_PUBLIC)
   PHP_ME(RepeatedField, offsetExists, arginfo_offsetGet, ZEND_ACC_PUBLIC)
   PHP_ME(RepeatedField, offsetGet,    arginfo_offsetGet, ZEND_ACC_PUBLIC)
   PHP_ME(RepeatedField, offsetSet,    arginfo_offsetSet, ZEND_ACC_PUBLIC)
diff --git a/php/ext/google/protobuf/convert.c b/php/ext/google/protobuf/convert.c
index dd076c5..1c2f628 100644
--- a/php/ext/google/protobuf/convert.c
+++ b/php/ext/google/protobuf/convert.c
@@ -92,20 +92,55 @@
   RETURN_ZVAL(val, 1, 0);
 }
 
+ZEND_BEGIN_ARG_INFO_EX(arginfo_checkPrimitive, 0, 0, 1)
+  ZEND_ARG_INFO(0, value)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_checkMessage, 0, 0, 2)
+  ZEND_ARG_INFO(0, value)
+  ZEND_ARG_INFO(0, class)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_checkMapField, 0, 0, 3)
+  ZEND_ARG_INFO(0, value)
+  ZEND_ARG_INFO(0, key_type)
+  ZEND_ARG_INFO(0, value_type)
+  ZEND_ARG_INFO(0, value_class)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_checkRepeatedField, 0, 0, 2)
+  ZEND_ARG_INFO(0, value)
+  ZEND_ARG_INFO(0, type)
+  ZEND_ARG_INFO(0, class)
+ZEND_END_ARG_INFO()
+
 static zend_function_entry util_methods[] = {
-  PHP_ME(Util, checkInt32,  NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkUint32, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkInt64,  NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkUint64, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkEnum,   NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkFloat,  NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkDouble, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkBool,   NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkString, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkBytes,  NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkMessage, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkMapField, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
-  PHP_ME(Util, checkRepeatedField, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkInt32,  arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkUint32, arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkInt64,  arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkUint64, arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkEnum,   arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkFloat,  arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkDouble, arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkBool,   arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkString, arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkBytes,  arginfo_checkPrimitive,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkMessage, arginfo_checkMessage,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkMapField, arginfo_checkMapField,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkRepeatedField, arginfo_checkRepeatedField,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
   ZEND_FE_END
 };
 
diff --git a/php/ext/google/protobuf/map.c b/php/ext/google/protobuf/map.c
index 7919d51..f29c18c 100644
--- a/php/ext/google/protobuf/map.c
+++ b/php/ext/google/protobuf/map.c
@@ -90,12 +90,12 @@
   zend_object_std_dtor(&intern->std);
 }
 
-static zval *Map_GetPropertyPtrPtr(zval *object, zval *member, int type,
-                                      void **cache_slot) {
+static zval *Map_GetPropertyPtrPtr(PROTO_VAL *object, PROTO_STR *member,
+                                   int type, void **cache_slot) {
   return NULL;  // We don't offer direct references to our properties.
 }
 
-static HashTable *map_get_properties(zval *object) {
+static HashTable *Map_GetProperties(PROTO_VAL *object) {
   return NULL;  // We do not have a properties table.
 }
 
@@ -378,6 +378,12 @@
   RETURN_ZVAL(&ret, 0, 1);
 }
 
+ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 2)
+  ZEND_ARG_INFO(0, key_type)
+  ZEND_ARG_INFO(0, value_type)
+  ZEND_ARG_INFO(0, value_class)
+ZEND_END_ARG_INFO()
+
 ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetGet, 0, 0, 1)
   ZEND_ARG_INFO(0, index)
 ZEND_END_ARG_INFO()
@@ -391,7 +397,7 @@
 ZEND_END_ARG_INFO()
 
 static zend_function_entry MapField_methods[] = {
-  PHP_ME(MapField, __construct,  NULL,              ZEND_ACC_PUBLIC)
+  PHP_ME(MapField, __construct,  arginfo_construct, ZEND_ACC_PUBLIC)
   PHP_ME(MapField, offsetExists, arginfo_offsetGet, ZEND_ACC_PUBLIC)
   PHP_ME(MapField, offsetGet,    arginfo_offsetGet, ZEND_ACC_PUBLIC)
   PHP_ME(MapField, offsetSet,    arginfo_offsetSet, ZEND_ACC_PUBLIC)
@@ -572,7 +578,7 @@
   h = &MapField_object_handlers;
   memcpy(h, &std_object_handlers, sizeof(zend_object_handlers));
   h->dtor_obj = MapField_destructor;
-  h->get_properties = map_get_properties;
+  h->get_properties = Map_GetProperties;
   h->get_property_ptr_ptr = Map_GetPropertyPtrPtr;
 
   INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\Internal\\MapFieldIter",
diff --git a/php/ext/google/protobuf/message.c b/php/ext/google/protobuf/message.c
index 3a0eb93..63d2b08 100644
--- a/php/ext/google/protobuf/message.c
+++ b/php/ext/google/protobuf/message.c
@@ -95,10 +95,10 @@
  *
  * Helper function to look up a field given a member name (as a string).
  */
-static const upb_fielddef *get_field(Message *msg, zval *member) {
+static const upb_fielddef *get_field(Message *msg, PROTO_STR *member) {
   const upb_msgdef *m = msg->desc->msgdef;
   const upb_fielddef *f =
-      upb_msgdef_ntof(m, Z_STRVAL_P(member), Z_STRLEN_P(member));
+      upb_msgdef_ntof(m, PROTO_STRVAL_P(member), PROTO_STRLEN_P(member));
 
   if (!f) {
     zend_throw_exception_ex(NULL, 0, "No such property %s.",
@@ -126,9 +126,10 @@
  *       return isset($this->optional_int32);
  *   }
  */
-static int Message_has_property(zval *obj, zval *member, int has_set_exists,
+static int Message_has_property(PROTO_VAL *obj, PROTO_STR *member,
+                                int has_set_exists,
                                 void **cache_slot) {
-  Message* intern = (Message*)Z_OBJ_P(obj);
+  Message* intern = PROTO_MSG_P(obj);
   const upb_fielddef *f = get_field(intern, member);
 
   if (!f) return 0;
@@ -161,8 +162,9 @@
  *       unset($this->optional_int32);
  *   }
  */
-static void Message_unset_property(zval *obj, zval *member, void **cache_slot) {
-  Message* intern = (Message*)Z_OBJ_P(obj);
+static void Message_unset_property(PROTO_VAL *obj, PROTO_STR *member,
+                                   void **cache_slot) {
+  Message* intern = PROTO_MSG_P(obj);
   const upb_fielddef *f = get_field(intern, member);
 
   if (!f) return;
@@ -196,9 +198,9 @@
  * We lookup the field and return the scalar, RepeatedField, or MapField for
  * this field.
  */
-static zval *Message_read_property(zval *obj, zval *member, int type,
-                                   void **cache_slot, zval *rv) {
-  Message* intern = (Message*)Z_OBJ_P(obj);
+static zval *Message_read_property(PROTO_VAL *obj, PROTO_STR *member,
+                                   int type, void **cache_slot, zval *rv) {
+  Message* intern = PROTO_MSG_P(obj);
   const upb_fielddef *f = get_field(intern, member);
   upb_arena *arena = Arena_Get(&intern->arena);
 
@@ -240,29 +242,41 @@
  * The C extension version of checkInt32() doesn't actually check anything, so
  * we perform all checking and conversion in this function.
  */
-static void Message_write_property(zval *obj, zval *member, zval *val,
-                                   void **cache_slot) {
-  Message* intern = (Message*)Z_OBJ_P(obj);
+static PROTO_RETURN_VAL Message_write_property(
+    PROTO_VAL *obj, PROTO_STR *member, zval *val, void **cache_slot) {
+  Message* intern = PROTO_MSG_P(obj);
   const upb_fielddef *f = get_field(intern, member);
   upb_arena *arena = Arena_Get(&intern->arena);
   upb_msgval msgval;
 
-  if (!f) return;
+  if (!f) goto error;
 
   if (upb_fielddef_ismap(f)) {
     msgval.map_val = MapField_GetUpbMap(val, f, arena);
-    if (!msgval.map_val) return;
+    if (!msgval.map_val) goto error;
   } else if (upb_fielddef_isseq(f)) {
     msgval.array_val = RepeatedField_GetUpbArray(val, f, arena);
-    if (!msgval.array_val) return;
+    if (!msgval.array_val) goto error;
   } else {
     upb_fieldtype_t type = upb_fielddef_type(f);
     const Descriptor *subdesc = Descriptor_GetFromFieldDef(f);
     bool ok = Convert_PhpToUpb(val, &msgval, type, subdesc, arena);
-    if (!ok) return;
+    if (!ok) goto error;
   }
 
   upb_msg_set(intern->msg, f, msgval, arena);
+#if PHP_VERSION_ID < 704000
+  return;
+#else
+  return val;
+#endif
+
+error:
+#if PHP_VERSION_ID < 704000
+  return;
+#else
+  return &EG(error_zval);
+#endif
 }
 
 /**
@@ -272,7 +286,8 @@
  * reference to our internal properties. We don't support this, so we return
  * NULL.
  */
-static zval *Message_get_property_ptr_ptr(zval *object, zval *member, int type,
+static zval *Message_get_property_ptr_ptr(PROTO_VAL *object, PROTO_STR *member,
+                                          int type,
                                           void **cache_slot) {
   return NULL;  // We do not have a properties table.
 }
@@ -283,7 +298,7 @@
  * Object handler for the get_properties event in PHP. This returns a HashTable
  * of our internal properties. We don't support this, so we return NULL.
  */
-static HashTable *Message_get_properties(zval *object) {
+static HashTable *Message_get_properties(PROTO_VAL *object) {
   return NULL;  // We don't offer direct references to our properties.
 }
 
@@ -870,20 +885,36 @@
   upb_msg_set(intern->msg, f, msgval, arena);
 }
 
+ZEND_BEGIN_ARG_INFO_EX(arginfo_void, 0, 0, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_mergeFrom, 0, 0, 1)
+  ZEND_ARG_INFO(0, data)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_read, 0, 0, 1)
+  ZEND_ARG_INFO(0, field)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_write, 0, 0, 2)
+  ZEND_ARG_INFO(0, field)
+  ZEND_ARG_INFO(0, value)
+ZEND_END_ARG_INFO()
+
 static zend_function_entry Message_methods[] = {
-  PHP_ME(Message, clear, NULL, ZEND_ACC_PUBLIC)
-  PHP_ME(Message, discardUnknownFields, NULL, ZEND_ACC_PUBLIC)
-  PHP_ME(Message, serializeToString, NULL, ZEND_ACC_PUBLIC)
-  PHP_ME(Message, mergeFromString, NULL, ZEND_ACC_PUBLIC)
-  PHP_ME(Message, serializeToJsonString, NULL, ZEND_ACC_PUBLIC)
-  PHP_ME(Message, mergeFromJsonString, NULL, ZEND_ACC_PUBLIC)
-  PHP_ME(Message, mergeFrom, NULL, ZEND_ACC_PUBLIC)
-  PHP_ME(Message, readWrapperValue, NULL, ZEND_ACC_PROTECTED)
-  PHP_ME(Message, writeWrapperValue, NULL, ZEND_ACC_PROTECTED)
-  PHP_ME(Message, readOneof, NULL, ZEND_ACC_PROTECTED)
-  PHP_ME(Message, writeOneof, NULL, ZEND_ACC_PROTECTED)
-  PHP_ME(Message, whichOneof, NULL, ZEND_ACC_PROTECTED)
-  PHP_ME(Message, __construct, NULL, ZEND_ACC_PROTECTED)
+  PHP_ME(Message, clear,                 arginfo_void,      ZEND_ACC_PUBLIC)
+  PHP_ME(Message, discardUnknownFields,  arginfo_void,      ZEND_ACC_PUBLIC)
+  PHP_ME(Message, serializeToString,     arginfo_void,      ZEND_ACC_PUBLIC)
+  PHP_ME(Message, mergeFromString,       arginfo_mergeFrom, ZEND_ACC_PUBLIC)
+  PHP_ME(Message, serializeToJsonString, arginfo_void,      ZEND_ACC_PUBLIC)
+  PHP_ME(Message, mergeFromJsonString,   arginfo_mergeFrom, ZEND_ACC_PUBLIC)
+  PHP_ME(Message, mergeFrom,             arginfo_mergeFrom, ZEND_ACC_PUBLIC)
+  PHP_ME(Message, readWrapperValue,      arginfo_read,      ZEND_ACC_PROTECTED)
+  PHP_ME(Message, writeWrapperValue,     arginfo_write,     ZEND_ACC_PROTECTED)
+  PHP_ME(Message, readOneof,             arginfo_read,      ZEND_ACC_PROTECTED)
+  PHP_ME(Message, writeOneof,            arginfo_write,     ZEND_ACC_PROTECTED)
+  PHP_ME(Message, whichOneof,            arginfo_read,      ZEND_ACC_PROTECTED)
+  PHP_ME(Message, __construct,           arginfo_void,      ZEND_ACC_PROTECTED)
   ZEND_FE_END
 };
 
diff --git a/php/ext/google/protobuf/protobuf.c b/php/ext/google/protobuf/protobuf.c
index 15c8f9b..fa1cc11 100644
--- a/php/ext/google/protobuf/protobuf.c
+++ b/php/ext/google/protobuf/protobuf.c
@@ -273,11 +273,18 @@
       zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
 
   if (!ret && ce->create_object) {
+#if PHP_VERSION_ID < 80000
     zval tmp;
     zval zv;
     ZVAL_OBJ(&tmp, ce->create_object(ce));
     zend_call_method_with_0_params(&tmp, ce, NULL, "__construct", &zv);
     zval_ptr_dtor(&tmp);
+#else
+    zval zv;
+    zend_object *tmp = ce->create_object(ce);
+    zend_call_method_with_0_params(tmp, ce, NULL, "__construct", &zv);
+    OBJ_RELEASE(tmp);
+#endif
     zval_ptr_dtor(&zv);
     ret = zend_hash_find_ptr(&PROTOBUF_G(name_msg_cache), ce->name);
   }
diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h
index 6a7afae..15cb344 100644
--- a/php/ext/google/protobuf/protobuf.h
+++ b/php/ext/google/protobuf/protobuf.h
@@ -43,6 +43,34 @@
 #define GC_DELREF(h) --GC_REFCOUNT(h)
 #endif
 
+// Since php 7.4, the write_property() object handler now returns the assigned
+// value (after possible type coercions) rather than void.
+// https://github.com/php/php-src/blob/PHP-7.4.0/UPGRADING.INTERNALS#L171-L173
+#if PHP_VERSION_ID < 70400
+#define PROTO_RETURN_VAL void
+#else
+#define PROTO_RETURN_VAL zval*
+#endif
+
+// Sine php 8.0, the Object Handlers API was changed to receive zend_object*
+// instead of zval* and zend_string* instead of zval* for property names.
+// https://github.com/php/php-src/blob/php-8.0.0beta1/UPGRADING.INTERNALS#L37-L39
+#if PHP_VERSION_ID < 80000
+#define PROTO_VAL zval 
+#define PROTO_STR zval
+#define PROTO_MSG_P(obj) (Message*)Z_OBJ_P(obj)
+#define PROTO_STRVAL_P(obj) Z_STRVAL_P(obj)
+#define PROTO_STRLEN_P(obj) Z_STRLEN_P(obj)
+#else
+#define PROTO_VAL zend_object
+#define PROTO_STR zend_string
+#define PROTO_MSG_P(obj) (Message*)(obj)
+#define PROTO_STRVAL_P(obj) ZSTR_VAL(obj)
+#define PROTO_STRLEN_P(obj) ZSTR_LEN(obj)
+#endif
+
+#define PHP_PROTOBUF_VERSION "4.0.0RC2"
+
 // ptr -> PHP object cache. This is a weak map that caches lazily-created
 // wrapper objects around upb types:
 //  * upb_msg* -> Message
diff --git a/php/phpunit.xml b/php/phpunit.xml
index 769037c..8e75835 100644
--- a/php/phpunit.xml
+++ b/php/phpunit.xml
@@ -3,16 +3,16 @@
          colors="true">
   <testsuites>
     <testsuite name="protobuf-tests">
-      <file>tests/php_implementation_test.php</file>
-      <file>tests/array_test.php</file>
-      <file>tests/encode_decode_test.php</file>
-      <file>tests/generated_class_test.php</file>
-      <file>tests/generated_phpdoc_test.php</file>
-      <file>tests/map_field_test.php</file>
-      <file>tests/well_known_test.php</file>
-      <file>tests/descriptors_test.php</file>
-      <file>tests/generated_service_test.php</file>
-      <file>tests/wrapper_type_setters_test.php</file>
+      <file>tests/PhpImplementationTest.php</file>
+      <file>tests/ArrayTest.php</file>
+      <file>tests/EncodeDecodeTest.php</file>
+      <file>tests/GeneratedClassTest.php</file>
+      <file>tests/GeneratedPhpdocTest.php</file>
+      <file>tests/MapFieldTest.php</file>
+      <file>tests/WellKnownTest.php</file>
+      <file>tests/DescriptorsTest.php</file>
+      <file>tests/GeneratedServiceTest.php</file>
+      <file>tests/WrapperTypeSettersTest.php</file>
     </testsuite>
   </testsuites>
 </phpunit>
diff --git a/php/tests/array_test.php b/php/tests/ArrayTest.php
similarity index 99%
rename from php/tests/array_test.php
rename to php/tests/ArrayTest.php
index b251404..2cb4b39 100644
--- a/php/tests/array_test.php
+++ b/php/tests/ArrayTest.php
@@ -7,7 +7,7 @@
 use Foo\TestMessage;
 use Foo\TestMessage\Sub;
 
-class RepeatedFieldTest extends \PHPUnit\Framework\TestCase
+class ArrayTest extends \PHPUnit\Framework\TestCase
 {
 
     #########################################################
diff --git a/php/tests/descriptors_test.php b/php/tests/DescriptorsTest.php
similarity index 100%
rename from php/tests/descriptors_test.php
rename to php/tests/DescriptorsTest.php
diff --git a/php/tests/encode_decode_test.php b/php/tests/EncodeDecodeTest.php
similarity index 100%
rename from php/tests/encode_decode_test.php
rename to php/tests/EncodeDecodeTest.php
diff --git a/php/tests/generated_class_test.php b/php/tests/GeneratedClassTest.php
similarity index 100%
rename from php/tests/generated_class_test.php
rename to php/tests/GeneratedClassTest.php
diff --git a/php/tests/generated_phpdoc_test.php b/php/tests/GeneratedPhpdocTest.php
similarity index 100%
rename from php/tests/generated_phpdoc_test.php
rename to php/tests/GeneratedPhpdocTest.php
diff --git a/php/tests/generated_service_test.php b/php/tests/GeneratedServiceTest.php
similarity index 100%
rename from php/tests/generated_service_test.php
rename to php/tests/GeneratedServiceTest.php
diff --git a/php/tests/map_field_test.php b/php/tests/MapFieldTest.php
similarity index 100%
rename from php/tests/map_field_test.php
rename to php/tests/MapFieldTest.php
diff --git a/php/tests/php_implementation_test.php b/php/tests/PhpImplementationTest.php
similarity index 100%
rename from php/tests/php_implementation_test.php
rename to php/tests/PhpImplementationTest.php
diff --git a/php/tests/well_known_test.php b/php/tests/WellKnownTest.php
similarity index 100%
rename from php/tests/well_known_test.php
rename to php/tests/WellKnownTest.php
diff --git a/php/tests/wrapper_type_setters_test.php b/php/tests/WrapperTypeSettersTest.php
similarity index 100%
rename from php/tests/wrapper_type_setters_test.php
rename to php/tests/WrapperTypeSettersTest.php
diff --git a/php/tests/test.sh b/php/tests/test.sh
index 4beeed5..2951132 100755
--- a/php/tests/test.sh
+++ b/php/tests/test.sh
@@ -18,6 +18,9 @@
   7.3.*|7.4.*)
     PHPUNIT=phpunit-8.phar
     ;;
+  8.0.*)
+    PHPUNIT=phpunit-9.phar
+    ;;
   *)
     echo "ERROR: Unsupported PHP version $PHP_VERSION"
     exit 1
@@ -26,7 +29,7 @@
 
 [ -f $PHPUNIT ] || wget https://phar.phpunit.de/$PHPUNIT
 
-tests=( array_test.php encode_decode_test.php generated_class_test.php map_field_test.php well_known_test.php descriptors_test.php wrapper_type_setters_test.php)
+tests=( ArrayTest.php EncodeDecodeTest.php GeneratedClassTest.php MapFieldTest.php WellKnownTest.php DescriptorsTest.php WrapperTypeSettersTest.php)
 
 for t in "${tests[@]}"
 do
diff --git a/tests.sh b/tests.sh
index ca787ca..3d47b6c 100755
--- a/tests.sh
+++ b/tests.sh
@@ -701,6 +701,51 @@
   popd
 }
 
+build_php8.0() {
+  use_php 8.0
+  pushd php
+  rm -rf vendor
+  composer update
+  composer test
+  popd
+  (cd conformance && make test_php)
+}
+
+build_php8.0_c() {
+  IS_64BIT=$1
+  use_php 8.0
+  php/tests/test.sh
+  pushd conformance
+  if [ "$IS_64BIT" = "true" ]
+  then
+    make test_php_c
+  else
+    make test_php_c_32
+  fi
+  popd
+}
+
+build_php8.0_c_64() {
+  build_php8.0_c true
+}
+
+build_php8.0_mixed() {
+  use_php 8.0
+  pushd php
+  rm -rf vendor
+  composer update
+  tests/compile_extension.sh
+  tests/generate_protos.sh
+  php -dextension=./ext/google/protobuf/modules/protobuf.so ./vendor/bin/phpunit
+  popd
+}
+
+build_php8.0_all() {
+  build_php8.0
+  build_php8.0_c_64
+  build_php8.0_mixed
+}
+
 build_php_all_32() {
   build_php5.5
   build_php5.6