소스 검색

Add any support in php runtime. (#3486)

* Add any support in php runtime.

* Remove unused file in config.m4

* Fix comments

* Fix error for tsrmls build

* Add newly added file to Makefile.am
Paul Yang 8 년 전
부모
커밋
c7457ef65a

+ 2 - 0
Makefile.am

@@ -600,6 +600,7 @@ php_EXTRA_DIST=                                                       \
   php/ext/google/protobuf/upb.c                                       \
   php/ext/google/protobuf/protobuf.c                                  \
   php/src/phpdoc.dist.xml                                             \
+  php/src/Google/Protobuf/Any.php                                     \
   php/src/Google/Protobuf/Descriptor.php                              \
   php/src/Google/Protobuf/DescriptorPool.php                          \
   php/src/Google/Protobuf/EnumDescriptor.php                          \
@@ -664,6 +665,7 @@ php_EXTRA_DIST=                                                       \
   php/src/Google/Protobuf/Internal/SourceCodeInfo.php                 \
   php/src/Google/Protobuf/Internal/UninterpretedOption_NamePart.php   \
   php/src/Google/Protobuf/Internal/UninterpretedOption.php            \
+  php/src/GPBMetadata/Google/Protobuf/Any.php                         \
   php/src/GPBMetadata/Google/Protobuf/Internal/Descriptor.php         \
   php/tests/array_test.php                                            \
   php/tests/autoload.php                                              \

+ 20 - 37
php/ext/google/protobuf/def.c

@@ -115,33 +115,6 @@ static void append_map_entry_name(char *result, const char *field_name,
     check_upb_status(&status, msg);      \
   } while (0)
 
-// Define PHP class
-#define DEFINE_PROTOBUF_INIT_CLASS(CLASSNAME, CAMELNAME, LOWERNAME) \
-  PHP_PROTO_INIT_CLASS_START(CLASSNAME, CAMELNAME, LOWERNAME)       \
-  PHP_PROTO_INIT_CLASS_END
-
-#define DEFINE_PROTOBUF_CREATE(NAME, LOWERNAME)  \
-  PHP_PROTO_OBJECT_CREATE_START(NAME, LOWERNAME) \
-  LOWERNAME##_init_c_instance(intern TSRMLS_CC); \
-  PHP_PROTO_OBJECT_CREATE_END(NAME, LOWERNAME)
-
-#define DEFINE_PROTOBUF_FREE(CAMELNAME, LOWERNAME)  \
-  PHP_PROTO_OBJECT_FREE_START(CAMELNAME, LOWERNAME) \
-  LOWERNAME##_free_c(intern TSRMLS_CC);             \
-  PHP_PROTO_OBJECT_FREE_END
-
-#define DEFINE_PROTOBUF_DTOR(CAMELNAME, LOWERNAME)  \
-  PHP_PROTO_OBJECT_DTOR_START(CAMELNAME, LOWERNAME) \
-  PHP_PROTO_OBJECT_DTOR_END
-
-#define DEFINE_CLASS(NAME, LOWERNAME, string_name) \
-  zend_class_entry *LOWERNAME##_type;              \
-  zend_object_handlers *LOWERNAME##_handlers;      \
-  DEFINE_PROTOBUF_FREE(NAME, LOWERNAME)            \
-  DEFINE_PROTOBUF_DTOR(NAME, LOWERNAME)            \
-  DEFINE_PROTOBUF_CREATE(NAME, LOWERNAME)          \
-  DEFINE_PROTOBUF_INIT_CLASS(string_name, NAME, LOWERNAME)
-
 // -----------------------------------------------------------------------------
 // GPBType
 // -----------------------------------------------------------------------------
@@ -657,7 +630,7 @@ zend_object *internal_generated_pool_php;
 #endif
 InternalDescriptorPool *generated_pool;  // The actual generated pool
 
-static void init_generated_pool_once(TSRMLS_D) {
+void init_generated_pool_once(TSRMLS_D) {
   if (generated_pool == NULL) {
 #if PHP_MAJOR_VERSION < 7
     MAKE_STD_ZVAL(generated_pool_php);
@@ -843,18 +816,11 @@ static void convert_to_class_name_inplace(const char *package,
   memcpy(classname + i, prefix, prefix_len);
 }
 
-PHP_METHOD(InternalDescriptorPool, internalAddGeneratedFile) {
-  char *data = NULL;
-  PHP_PROTO_SIZE data_len;
+void internal_add_generated_file(const char *data, PHP_PROTO_SIZE data_len,
+                                 InternalDescriptorPool *pool TSRMLS_DC) {
   upb_filedef **files;
   size_t i;
 
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &data, &data_len) ==
-      FAILURE) {
-    return;
-  }
-
-  InternalDescriptorPool *pool = UNBOX(InternalDescriptorPool, getThis());
   CHECK_UPB(files = upb_loaddescriptor(data, data_len, &pool, &status),
             "Parse binary descriptors to internal descriptors failed");
 
@@ -913,6 +879,8 @@ PHP_METHOD(InternalDescriptorPool, internalAddGeneratedFile) {
       desc->klass = PHP_PROTO_CE_UNREF(pce);                                   \
     }                                                                          \
     add_ce_obj(desc->klass, desc_php);                                         \
+    add_proto_obj(upb_##def_type_lower##_fullname(desc->def_type_lower),       \
+                  desc_php);                                                   \
     efree(classname);                                                          \
     break;                                                                     \
   }
@@ -939,6 +907,21 @@ PHP_METHOD(InternalDescriptorPool, internalAddGeneratedFile) {
   upb_gfree(files);
 }
 
+PHP_METHOD(InternalDescriptorPool, internalAddGeneratedFile) {
+  char *data = NULL;
+  PHP_PROTO_SIZE data_len;
+  upb_filedef **files;
+  size_t i;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &data, &data_len) ==
+      FAILURE) {
+    return;
+  }
+
+  InternalDescriptorPool *pool = UNBOX(InternalDescriptorPool, getThis());
+  internal_add_generated_file(data, data_len, pool TSRMLS_CC);
+}
+
 PHP_METHOD(DescriptorPool, getDescriptorByClassName) {
   DescriptorPool *public_pool = UNBOX(DescriptorPool, getThis());
   InternalDescriptorPool *pool = public_pool->intern;

+ 24 - 17
php/ext/google/protobuf/encode_decode.c

@@ -1445,9 +1445,9 @@ static const upb_handlers* msgdef_json_serialize_handlers(
 // PHP encode/decode methods
 // -----------------------------------------------------------------------------
 
-PHP_METHOD(Message, serializeToString) {
+void serialize_to_string(zval* val, zval* return_value TSRMLS_DC) {
   Descriptor* desc =
-      UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(Z_OBJCE_P(getThis())));
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(Z_OBJCE_P(val)));
 
   stringsink sink;
   stringsink_init(&sink);
@@ -1461,7 +1461,7 @@ PHP_METHOD(Message, serializeToString) {
     stackenv_init(&se, "Error occurred during encoding: %s");
     encoder = upb_pb_encoder_create(&se.env, serialize_handlers, &sink.sink);
 
-    putmsg(getThis(), desc, upb_pb_encoder_input(encoder), 0 TSRMLS_CC);
+    putmsg(val, desc, upb_pb_encoder_input(encoder), 0 TSRMLS_CC);
 
     PHP_PROTO_RETVAL_STRINGL(sink.ptr, sink.len, 1);
 
@@ -1470,6 +1470,26 @@ PHP_METHOD(Message, serializeToString) {
   }
 }
 
+PHP_METHOD(Message, serializeToString) {
+  serialize_to_string(getThis(), return_value TSRMLS_CC);
+}
+
+void merge_from_string(const char* data, int data_len, const Descriptor* desc,
+                       MessageHeader* msg) {
+  const upb_pbdecodermethod* method = msgdef_decodermethod(desc);
+  const upb_handlers* h = upb_pbdecodermethod_desthandlers(method);
+  stackenv se;
+  upb_sink sink;
+  upb_pbdecoder* decoder;
+  stackenv_init(&se, "Error occurred during parsing: %s");
+
+  upb_sink_reset(&sink, h, msg);
+  decoder = upb_pbdecoder_create(&se.env, method, &sink);
+  upb_bufsrc_putbuf(data, data_len, upb_pbdecoder_input(decoder));
+
+  stackenv_uninit(&se);
+}
+
 PHP_METHOD(Message, mergeFromString) {
   Descriptor* desc =
       UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(Z_OBJCE_P(getThis())));
@@ -1483,20 +1503,7 @@ PHP_METHOD(Message, mergeFromString) {
     return;
   }
 
-  {
-    const upb_pbdecodermethod* method = msgdef_decodermethod(desc);
-    const upb_handlers* h = upb_pbdecodermethod_desthandlers(method);
-    stackenv se;
-    upb_sink sink;
-    upb_pbdecoder* decoder;
-    stackenv_init(&se, "Error occurred during parsing: %s");
-
-    upb_sink_reset(&sink, h, msg);
-    decoder = upb_pbdecoder_create(&se.env, method, &sink);
-    upb_bufsrc_putbuf(data, data_len, upb_pbdecoder_input(decoder));
-
-    stackenv_uninit(&se);
-  }
+  merge_from_string(data, data_len, desc, msg);
 }
 
 PHP_METHOD(Message, serializeToJsonString) {

+ 276 - 1
php/ext/google/protobuf/message.c

@@ -32,9 +32,11 @@
 #include <stdlib.h>
 
 #include "protobuf.h"
+#include "utf8.h"
 
-static zend_class_entry* message_type;
+zend_class_entry* message_type;
 zend_object_handlers* message_handlers;
+static const char TYPE_URL_PREFIX[] = "type.googleapis.com/";
 
 static  zend_function_entry message_methods[] = {
   PHP_ME(Message, clear, NULL, ZEND_ACC_PUBLIC)
@@ -342,3 +344,276 @@ PHP_METHOD(Message, whichOneof) {
       msg->descriptor->layout, message_data(msg), oneof TSRMLS_CC);
   PHP_PROTO_RETURN_STRING(oneof_case_name, 1);
 }
+
+// -----------------------------------------------------------------------------
+// Any
+// -----------------------------------------------------------------------------
+
+static  zend_function_entry any_methods[] = {
+  PHP_ME(Any, __construct, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Any, getTypeUrl, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Any, setTypeUrl, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Any, getValue, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Any, setValue, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Any, pack,     NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Any, unpack,   NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Any, is,       NULL, ZEND_ACC_PUBLIC)
+  {NULL, NULL, NULL}
+};
+
+zend_class_entry* any_type;
+
+// Init class entry.
+PHP_PROTO_INIT_SUBMSGCLASS_START("Google\\Protobuf\\Any", Any, any)
+  zend_class_implements(any_type TSRMLS_CC, 1, message_type);
+  zend_declare_property_string(any_type, "type_url", strlen("type_url"),
+                               "" ,ZEND_ACC_PRIVATE TSRMLS_CC);
+  zend_declare_property_string(any_type, "value", strlen("value"),
+                               "" ,ZEND_ACC_PRIVATE TSRMLS_CC);
+PHP_PROTO_INIT_SUBMSGCLASS_END
+
+void hex_to_binary(const char* hex, char** binary, int* binary_len) {
+  int i;
+  int hex_len = strlen(hex);
+  *binary_len = hex_len / 2;
+  *binary = ALLOC_N(char, *binary_len);
+  for (i = 0; i < *binary_len; i++) {
+    char value = 0;
+    if (hex[i * 2] >= '0' && hex[i * 2] <= '9') {
+      value += (hex[i * 2] - '0') * 16;
+    } else {
+      value += (hex[i * 2] - 'a' + 10) * 16;
+    }
+    if (hex[i * 2 + 1] >= '0' && hex[i * 2 + 1] <= '9') {
+      value += hex[i * 2 + 1] - '0';
+    } else {
+      value += hex[i * 2 + 1] - 'a' + 10;
+    }
+    (*binary)[i] = value;
+  }
+}
+
+PHP_METHOD(Any, __construct) {
+  PHP_PROTO_HASHTABLE_VALUE desc_php = get_ce_obj(any_type);
+  if (desc_php == NULL) {
+    init_generated_pool_once(TSRMLS_C);
+    const char* generated_file =
+      "0acd010a19676f6f676c652f70726f746f6275662f616e792e70726f746f"
+      "120f676f6f676c652e70726f746f62756622260a03416e7912100a087479"
+      "70655f75726c180120012809120d0a0576616c756518022001280c426f0a"
+      "13636f6d2e676f6f676c652e70726f746f6275664208416e7950726f746f"
+      "50015a256769746875622e636f6d2f676f6c616e672f70726f746f627566"
+      "2f7074797065732f616e79a20203475042aa021e476f6f676c652e50726f"
+      "746f6275662e57656c6c4b6e6f776e5479706573620670726f746f33";
+    char* binary;
+    int binary_len;
+    hex_to_binary(generated_file, &binary, &binary_len);
+
+    internal_add_generated_file(binary, binary_len, generated_pool TSRMLS_CC);
+    FREE(binary);
+  }
+
+  MessageHeader* intern = UNBOX(MessageHeader, getThis());
+  custom_data_init(any_type, intern PHP_PROTO_TSRMLS_CC);
+}
+
+PHP_METHOD(Any, getTypeUrl) {
+  zval member;
+  PHP_PROTO_ZVAL_STRING(&member, "type_url", 1);
+
+  PHP_PROTO_FAKE_SCOPE_BEGIN(any_type);
+#if PHP_MAJOR_VERSION < 7
+  zval* value = message_handlers->read_property(getThis(), &member, BP_VAR_R,
+                                                NULL PHP_PROTO_TSRMLS_CC);
+#else
+  zval* value = message_handlers->read_property(getThis(), &member, BP_VAR_R,
+                                                NULL, NULL PHP_PROTO_TSRMLS_CC);
+#endif
+  PHP_PROTO_FAKE_SCOPE_END;
+
+  PHP_PROTO_RETVAL_ZVAL(value);
+}
+
+PHP_METHOD(Any, setTypeUrl) {
+  char *type_url = NULL;
+  PHP_PROTO_SIZE type_url_len;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &type_url,
+                            &type_url_len) == FAILURE) {
+    return;
+  }
+
+  zval member;
+  zval value;
+  PHP_PROTO_ZVAL_STRING(&member, "type_url", 1);
+  PHP_PROTO_ZVAL_STRINGL(&value, type_url, type_url_len, 1);
+
+  PHP_PROTO_FAKE_SCOPE_BEGIN(any_type);
+  message_handlers->write_property(getThis(), &member, &value,
+                                   NULL PHP_PROTO_TSRMLS_CC);
+  PHP_PROTO_FAKE_SCOPE_END;
+
+  PHP_PROTO_RETVAL_ZVAL(getThis());
+}
+
+PHP_METHOD(Any, getValue) {
+  zval member;
+  PHP_PROTO_ZVAL_STRING(&member, "value", 1);
+
+  PHP_PROTO_FAKE_SCOPE_BEGIN(any_type);
+  zval* value =
+      php_proto_message_read_property(getThis(), &member PHP_PROTO_TSRMLS_CC);
+  PHP_PROTO_FAKE_SCOPE_END;
+
+  PHP_PROTO_RETVAL_ZVAL(value);
+}
+
+PHP_METHOD(Any, setValue) {
+  char *value = NULL;
+  PHP_PROTO_SIZE value_len;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &value,
+                            &value_len) == FAILURE) {
+    return;
+  }
+
+  zval member;
+  zval value_to_set;
+  PHP_PROTO_ZVAL_STRING(&member, "value", 1);
+  PHP_PROTO_ZVAL_STRINGL(&value_to_set, value, value_len, 1);
+
+  PHP_PROTO_FAKE_SCOPE_BEGIN(any_type);
+  message_handlers->write_property(getThis(), &member, &value_to_set,
+                                   NULL PHP_PROTO_TSRMLS_CC);
+  PHP_PROTO_FAKE_SCOPE_END;
+
+  PHP_PROTO_RETVAL_ZVAL(getThis());
+}
+
+PHP_METHOD(Any, unpack) {
+  // Get type url.
+  zval type_url_member;
+  PHP_PROTO_ZVAL_STRING(&type_url_member, "type_url", 1);
+  PHP_PROTO_FAKE_SCOPE_BEGIN(any_type);
+  zval* type_url_php = php_proto_message_read_property(
+      getThis(), &type_url_member PHP_PROTO_TSRMLS_CC);
+  PHP_PROTO_FAKE_SCOPE_END;
+
+  // Get fully-qualified name from type url.
+  size_t url_prefix_len = strlen(TYPE_URL_PREFIX);
+  const char* type_url = Z_STRVAL_P(type_url_php);
+  size_t type_url_len = Z_STRLEN_P(type_url_php);
+
+  if (url_prefix_len > type_url_len ||
+      strncmp(TYPE_URL_PREFIX, type_url, url_prefix_len) != 0) {
+    zend_throw_exception(
+        NULL, "Type url needs to be type.googleapis.com/fully-qulified",
+        0 TSRMLS_CC);
+    return;
+  }
+
+  const char* fully_qualified_name = type_url + url_prefix_len;
+  PHP_PROTO_HASHTABLE_VALUE desc_php = get_proto_obj(fully_qualified_name);
+  if (desc_php == NULL) {
+    zend_throw_exception(
+        NULL, "Specified message in any hasn't been added to descriptor pool",
+        0 TSRMLS_CC);
+    return;
+  }
+  Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, desc_php);
+  zend_class_entry* klass = desc->klass;
+  ZVAL_OBJ(return_value, klass->create_object(klass TSRMLS_CC));
+  MessageHeader* msg = UNBOX(MessageHeader, return_value);
+  custom_data_init(klass, msg PHP_PROTO_TSRMLS_CC);
+
+  // Get value.
+  zval value_member;
+  PHP_PROTO_ZVAL_STRING(&value_member, "value", 1);
+  PHP_PROTO_FAKE_SCOPE_RESTART(any_type);
+  zval* value = php_proto_message_read_property(
+      getThis(), &value_member PHP_PROTO_TSRMLS_CC);
+  PHP_PROTO_FAKE_SCOPE_END;
+
+  merge_from_string(Z_STRVAL_P(value), Z_STRLEN_P(value), desc, msg);
+}
+
+PHP_METHOD(Any, pack) {
+  zval* val;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &val) ==
+      FAILURE) {
+    return;
+  }
+
+  if (!instanceof_function(Z_OBJCE_P(val), message_type TSRMLS_CC)) {
+    zend_error(E_USER_ERROR, "Given value is not an instance of Message.");
+    return;
+  }
+
+  // Set value by serialized data.
+  zval data;
+  serialize_to_string(val, &data TSRMLS_CC);
+
+  zval member;
+  PHP_PROTO_ZVAL_STRING(&member, "value", 1);
+
+  PHP_PROTO_FAKE_SCOPE_BEGIN(any_type);
+  message_handlers->write_property(getThis(), &member, &data,
+                                   NULL PHP_PROTO_TSRMLS_CC);
+  PHP_PROTO_FAKE_SCOPE_END;
+
+  // Set type url.
+  Descriptor* desc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(Z_OBJCE_P(val)));
+  const char* fully_qualified_name = upb_msgdef_fullname(desc->msgdef);
+  size_t type_url_len =
+      strlen(TYPE_URL_PREFIX) + strlen(fully_qualified_name) + 1;
+  char* type_url = ALLOC_N(char, type_url_len);
+  sprintf(type_url, "%s%s", TYPE_URL_PREFIX, fully_qualified_name);
+  zval type_url_php;
+  PHP_PROTO_ZVAL_STRING(&type_url_php, type_url, 1);
+  PHP_PROTO_ZVAL_STRING(&member, "type_url", 1);
+
+  PHP_PROTO_FAKE_SCOPE_RESTART(any_type);
+  message_handlers->write_property(getThis(), &member, &type_url_php,
+                                   NULL PHP_PROTO_TSRMLS_CC);
+  PHP_PROTO_FAKE_SCOPE_END;
+  FREE(type_url);
+}
+
+PHP_METHOD(Any, is) {
+  zend_class_entry *klass = NULL;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "C", &klass) ==
+      FAILURE) {
+    return;
+  }
+
+  PHP_PROTO_HASHTABLE_VALUE desc_php = get_ce_obj(klass);
+  if (desc_php == NULL) {
+    RETURN_BOOL(false);
+  }
+
+  // Create corresponded type url.
+  Descriptor* desc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(klass));
+  const char* fully_qualified_name = upb_msgdef_fullname(desc->msgdef);
+  size_t type_url_len =
+      strlen(TYPE_URL_PREFIX) + strlen(fully_qualified_name) + 1;
+  char* type_url = ALLOC_N(char, type_url_len);
+  sprintf(type_url, "%s%s", TYPE_URL_PREFIX, fully_qualified_name);
+
+  // Fetch stored type url.
+  zval member;
+  PHP_PROTO_ZVAL_STRING(&member, "type_url", 1);
+  PHP_PROTO_FAKE_SCOPE_BEGIN(any_type);
+  zval* value =
+      php_proto_message_read_property(getThis(), &member PHP_PROTO_TSRMLS_CC);
+  PHP_PROTO_FAKE_SCOPE_END;
+
+  // Compare two type url.
+  bool is = strcmp(type_url, Z_STRVAL_P(value)) == 0;
+  FREE(type_url);
+
+  RETURN_BOOL(is);
+}

+ 40 - 0
php/ext/google/protobuf/protobuf.c

@@ -46,6 +46,9 @@ static HashTable* upb_def_to_php_obj_map;
 // Global map from message/enum's php class entry to corresponding wrapper
 // Descriptor/EnumDescriptor instances.
 static HashTable* ce_to_php_obj_map;
+// Global map from message/enum's proto fully-qualified name to corresponding
+// wrapper Descriptor/EnumDescriptor instances.
+static HashTable* proto_to_php_obj_map;
 
 // -----------------------------------------------------------------------------
 // Global maps.
@@ -80,6 +83,22 @@ static void add_to_list(HashTable* t, void* value) {
                                         (void**)&pDest);
 }
 
+static void add_to_strtable(HashTable* t, const char* key, int key_size,
+                            void* value) {
+  zval* pDest = NULL;
+  php_proto_zend_hash_update_mem(t, key, key_size, &value, sizeof(void*),
+                                 (void**)&pDest);
+}
+
+static void* get_from_strtable(const HashTable* t, const char* key, int key_size) {
+  void** value;
+  if (php_proto_zend_hash_find_mem(t, key, key_size, (void**)&value) ==
+      FAILURE) {
+    return NULL;
+  }
+  return *value;
+}
+
 void add_def_obj(const void* def, PHP_PROTO_HASHTABLE_VALUE value) {
 #if PHP_MAJOR_VERSION < 7
   Z_ADDREF_P(value);
@@ -110,6 +129,20 @@ bool class_added(const void* ce) {
   return exist_in_table(ce_to_php_obj_map, ce);
 }
 
+void add_proto_obj(const char* proto, PHP_PROTO_HASHTABLE_VALUE value) {
+#if PHP_MAJOR_VERSION < 7
+  Z_ADDREF_P(value);
+#else
+  ++GC_REFCOUNT(value);
+#endif
+  add_to_strtable(proto_to_php_obj_map, proto, strlen(proto), value);
+}
+
+PHP_PROTO_HASHTABLE_VALUE get_proto_obj(const char* proto) {
+  return (PHP_PROTO_HASHTABLE_VALUE)get_from_strtable(proto_to_php_obj_map,
+                                                      proto, strlen(proto));
+}
+
 // -----------------------------------------------------------------------------
 // Utilities.
 // -----------------------------------------------------------------------------
@@ -163,6 +196,9 @@ static PHP_RINIT_FUNCTION(protobuf) {
   ALLOC_HASHTABLE(ce_to_php_obj_map);
   zend_hash_init(ce_to_php_obj_map, 16, NULL, HASHTABLE_VALUE_DTOR, 0);
 
+  ALLOC_HASHTABLE(proto_to_php_obj_map);
+  zend_hash_init(proto_to_php_obj_map, 16, NULL, HASHTABLE_VALUE_DTOR, 0);
+
   generated_pool = NULL;
   generated_pool_php = NULL;
   internal_generated_pool_php = NULL;
@@ -177,6 +213,9 @@ static PHP_RSHUTDOWN_FUNCTION(protobuf) {
   zend_hash_destroy(ce_to_php_obj_map);
   FREE_HASHTABLE(ce_to_php_obj_map);
 
+  zend_hash_destroy(proto_to_php_obj_map);
+  FREE_HASHTABLE(proto_to_php_obj_map);
+
 #if PHP_MAJOR_VERSION < 7
   if (generated_pool_php != NULL) {
     zval_dtor(generated_pool_php);
@@ -217,6 +256,7 @@ static PHP_MINIT_FUNCTION(protobuf) {
   repeated_field_init(TSRMLS_C);
   repeated_field_iter_init(TSRMLS_C);
   util_init(TSRMLS_C);
+  any_init(TSRMLS_C);
 
   return 0;
 }

+ 164 - 1
php/ext/google/protobuf/protobuf.h

@@ -77,15 +77,28 @@
 #define php_proto_zend_hash_index_update_zval(ht, h, pData) \
   zend_hash_index_update(ht, h, &(pData), sizeof(void*), NULL)
 
+#define php_proto_zend_hash_update_zval(ht, key, key_len, value) \
+  zend_hash_update(ht, key, key_len, value, sizeof(void*), NULL)
+
 #define php_proto_zend_hash_index_update_mem(ht, h, pData, nDataSize, pDest) \
   zend_hash_index_update(ht, h, pData, nDataSize, pDest)
 
+#define php_proto_zend_hash_update_mem(ht, key, key_len, pData, nDataSize, \
+                                       pDest)                              \
+  zend_hash_update(ht, key, key_len, pData, nDataSize, pDest)
+
 #define php_proto_zend_hash_index_find_zval(ht, h, pDest) \
   zend_hash_index_find(ht, h, pDest)
 
 #define php_proto_zend_hash_index_find_mem(ht, h, pDest) \
   zend_hash_index_find(ht, h, pDest)
 
+#define php_proto_zend_hash_find_zval(ht, key, key_len, pDest) \
+  zend_hash_find(ht, key, key_len, pDest)
+
+#define php_proto_zend_hash_find_mem(ht, key, key_len, pDest) \
+  zend_hash_find(ht, key, key_len, pDest)
+
 #define php_proto_zend_hash_next_index_insert_zval(ht, pData) \
   zend_hash_next_index_insert(ht, pData, sizeof(void*), NULL)
 
@@ -103,6 +116,17 @@
 #define PHP_PROTO_WRAP_OBJECT_END \
   };
 
+#define PHP_PROTO_INIT_SUBMSGCLASS_START(CLASSNAME, CAMELNAME, LOWWERNAME)   \
+  void LOWWERNAME##_init(TSRMLS_D) {                                         \
+    zend_class_entry class_type;                                             \
+    const char* class_name = CLASSNAME;                                      \
+    INIT_CLASS_ENTRY_EX(class_type, CLASSNAME, strlen(CLASSNAME),            \
+                        LOWWERNAME##_methods);                               \
+    LOWWERNAME##_type = zend_register_internal_class(&class_type TSRMLS_CC); \
+    LOWWERNAME##_type->create_object = message_create;
+#define PHP_PROTO_INIT_SUBMSGCLASS_END \
+  }
+
 #define PHP_PROTO_INIT_CLASS_START(CLASSNAME, CAMELNAME, LOWWERNAME)         \
   void LOWWERNAME##_init(TSRMLS_D) {                                         \
     zend_class_entry class_type;                                             \
@@ -202,6 +226,8 @@
 #define php_proto_zend_lookup_class(name, name_length, ce) \
   zend_lookup_class(name, name_length, ce TSRMLS_CC)
 
+#define PHP_PROTO_RETVAL_ZVAL(value) ZVAL_ZVAL(return_value, value, 1, 0)
+
 #else  // PHP_MAJOR_VERSION >= 7
 
 #define php_proto_zend_literal void**
@@ -243,6 +269,23 @@ static inline int php_proto_zend_hash_index_update_mem(HashTable* ht, ulong h,
   return result != NULL ? SUCCESS : FAILURE;
 }
 
+static inline int php_proto_zend_hash_update_zval(HashTable* ht,
+                                                  const char* key, uint key_len,
+                                                  zval* pData) {
+  zend_string* internal_key = zend_string_init(key, key_len, 0);
+  zend_hash_update(ht, internal_key, pData);
+}
+
+static inline int php_proto_zend_hash_update_mem(HashTable* ht, const char* key,
+                                                 uint key_len, void* pData,
+                                                 uint nDataSize, void** pDest) {
+  zend_string* internal_key = zend_string_init(key, key_len, 0);
+  void* result = zend_hash_update_mem(ht, internal_key, pData, nDataSize);
+  zend_string_release(internal_key);
+  if (pDest != NULL) *pDest = result;
+  return result != NULL ? SUCCESS : FAILURE;
+}
+
 static inline int php_proto_zend_hash_index_find_zval(const HashTable* ht,
                                                       ulong h, void** pDest) {
   zval* result = zend_hash_index_find(ht, h);
@@ -258,6 +301,25 @@ static inline int php_proto_zend_hash_index_find_mem(const HashTable* ht,
   return result != NULL ? SUCCESS : FAILURE;
 }
 
+static inline int php_proto_zend_hash_find_zval(const HashTable* ht,
+                                                const char* key, uint key_len,
+                                                void** pDest) {
+  zend_string* internal_key = zend_string_init(key, key_len, 1);
+  zval* result = zend_hash_find(ht, internal_key);
+  if (pDest != NULL) *pDest = result;
+  return result != NULL ? SUCCESS : FAILURE;
+}
+
+static inline int php_proto_zend_hash_find_mem(const HashTable* ht,
+                                                const char* key, uint key_len,
+                                                void** pDest) {
+  zend_string* internal_key = zend_string_init(key, key_len, 1);
+  void* result = zend_hash_find_ptr(ht, internal_key);
+  zend_string_release(internal_key);
+  if (pDest != NULL) *pDest = result;
+  return result != NULL ? SUCCESS : FAILURE;
+}
+
 static inline int php_proto_zend_hash_next_index_insert_zval(HashTable* ht,
                                                              void* pData) {
   zval tmp;
@@ -292,6 +354,17 @@ static inline int php_proto_zend_hash_get_current_data_ex(HashTable* ht,
   zend_object std;                \
   };
 
+#define PHP_PROTO_INIT_SUBMSGCLASS_START(CLASSNAME, CAMELNAME, LOWWERNAME)   \
+  void LOWWERNAME##_init(TSRMLS_D) {                                         \
+    zend_class_entry class_type;                                             \
+    const char* class_name = CLASSNAME;                                      \
+    INIT_CLASS_ENTRY_EX(class_type, CLASSNAME, strlen(CLASSNAME),            \
+                        LOWWERNAME##_methods);                               \
+    LOWWERNAME##_type = zend_register_internal_class(&class_type TSRMLS_CC); \
+    LOWWERNAME##_type->create_object = message_create;
+#define PHP_PROTO_INIT_SUBMSGCLASS_END \
+  }
+
 #define PHP_PROTO_INIT_CLASS_START(CLASSNAME, CAMELNAME, LOWWERNAME)         \
   void LOWWERNAME##_init(TSRMLS_D) {                                         \
     zend_class_entry class_type;                                             \
@@ -390,12 +463,60 @@ static inline int php_proto_zend_lookup_class(
   return *ce != NULL ? SUCCESS : FAILURE;
 }
 
+#define PHP_PROTO_RETVAL_ZVAL(value) ZVAL_COPY(return_value, value)
+
 #endif  // PHP_MAJOR_VERSION >= 7
 
+#if PHP_MAJOR_VERSION < 7 || (PHP_MAJOR_VERSION == 7 && PHP_MINOR_VERSION == 0)
+#define PHP_PROTO_FAKE_SCOPE_BEGIN(klass)  \
+  zend_class_entry* old_scope = EG(scope); \
+  EG(scope) = klass;
+#define PHP_PROTO_FAKE_SCOPE_RESTART(klass) \
+  old_scope = EG(scope);                    \
+  EG(scope) = klass;
+#define PHP_PROTO_FAKE_SCOPE_END EG(scope) = old_scope;
+#else
+#define PHP_PROTO_FAKE_SCOPE_BEGIN(klass)       \
+  zend_class_entry* old_scope = EG(fake_scope); \
+  EG(fake_scope) = klass;
+#define PHP_PROTO_FAKE_SCOPE_RESTART(klass) \
+  old_scope = EG(fake_scope);               \
+  EG(fake_scope) = klass;
+#define PHP_PROTO_FAKE_SCOPE_END EG(fake_scope) = old_scope;
+#endif
+
+// Define PHP class
+#define DEFINE_PROTOBUF_INIT_CLASS(CLASSNAME, CAMELNAME, LOWERNAME) \
+  PHP_PROTO_INIT_CLASS_START(CLASSNAME, CAMELNAME, LOWERNAME)       \
+  PHP_PROTO_INIT_CLASS_END
+
+#define DEFINE_PROTOBUF_CREATE(NAME, LOWERNAME)  \
+  PHP_PROTO_OBJECT_CREATE_START(NAME, LOWERNAME) \
+  LOWERNAME##_init_c_instance(intern TSRMLS_CC); \
+  PHP_PROTO_OBJECT_CREATE_END(NAME, LOWERNAME)
+
+#define DEFINE_PROTOBUF_FREE(CAMELNAME, LOWERNAME)  \
+  PHP_PROTO_OBJECT_FREE_START(CAMELNAME, LOWERNAME) \
+  LOWERNAME##_free_c(intern TSRMLS_CC);             \
+  PHP_PROTO_OBJECT_FREE_END
+
+#define DEFINE_PROTOBUF_DTOR(CAMELNAME, LOWERNAME)  \
+  PHP_PROTO_OBJECT_DTOR_START(CAMELNAME, LOWERNAME) \
+  PHP_PROTO_OBJECT_DTOR_END
+
+#define DEFINE_CLASS(NAME, LOWERNAME, string_name) \
+  zend_class_entry *LOWERNAME##_type;              \
+  zend_object_handlers *LOWERNAME##_handlers;      \
+  DEFINE_PROTOBUF_FREE(NAME, LOWERNAME)            \
+  DEFINE_PROTOBUF_DTOR(NAME, LOWERNAME)            \
+  DEFINE_PROTOBUF_CREATE(NAME, LOWERNAME)          \
+  DEFINE_PROTOBUF_INIT_CLASS(string_name, NAME, LOWERNAME)
+
 // -----------------------------------------------------------------------------
 // Forward Declaration
 // ----------------------------------------------------------------------------
 
+struct Any;
 struct DescriptorPool;
 struct Descriptor;
 struct EnumDescriptor;
@@ -411,6 +532,7 @@ struct Map;
 struct MapIter;
 struct Oneof;
 
+typedef struct Any Any;
 typedef struct DescriptorPool DescriptorPool;
 typedef struct Descriptor Descriptor;
 typedef struct EnumDescriptor EnumDescriptor;
@@ -434,6 +556,7 @@ ZEND_BEGIN_MODULE_GLOBALS(protobuf)
 ZEND_END_MODULE_GLOBALS(protobuf)
 
 // Init module and PHP classes.
+void any_init(TSRMLS_D);
 void descriptor_init(TSRMLS_D);
 void enum_descriptor_init(TSRMLS_D);
 void descriptor_pool_init(TSRMLS_D);
@@ -442,11 +565,11 @@ void field_descriptor_init(TSRMLS_D);
 void gpb_type_init(TSRMLS_D);
 void map_field_init(TSRMLS_D);
 void map_field_iter_init(TSRMLS_D);
+void message_init(TSRMLS_D);
 void oneof_descriptor_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);
 
 // Global map from upb {msg,enum}defs to wrapper Descriptor/EnumDescriptor
 // instances.
@@ -459,6 +582,11 @@ void add_ce_obj(const void* ce, PHP_PROTO_HASHTABLE_VALUE value);
 PHP_PROTO_HASHTABLE_VALUE get_ce_obj(const void* ce);
 bool class_added(const void* ce);
 
+// Global map from message/enum's proto fully-qualified name to corresponding
+// wrapper Descriptor/EnumDescriptor instances.
+void add_proto_obj(const char* proto, PHP_PROTO_HASHTABLE_VALUE value);
+PHP_PROTO_HASHTABLE_VALUE get_proto_obj(const char* proto);
+
 extern zend_class_entry* map_field_type;
 extern zend_class_entry* repeated_field_type;
 
@@ -482,6 +610,10 @@ PHP_PROTO_WRAP_OBJECT_END
 PHP_METHOD(InternalDescriptorPool, getGeneratedPool);
 PHP_METHOD(InternalDescriptorPool, internalAddGeneratedFile);
 
+void internal_add_generated_file(const char* data, PHP_PROTO_SIZE data_len,
+                                 InternalDescriptorPool* pool TSRMLS_DC);
+void init_generated_pool_once(TSRMLS_D);
+
 // wrapper of generated pool
 #if PHP_MAJOR_VERSION < 7
 extern zval* generated_pool_php;
@@ -567,6 +699,7 @@ void custom_data_init(const zend_class_entry* ce,
 void build_class_from_descriptor(
     PHP_PROTO_HASHTABLE_VALUE php_descriptor TSRMLS_DC);
 
+extern zend_class_entry* message_type;
 extern zend_object_handlers* message_handlers;
 
 // -----------------------------------------------------------------------------
@@ -674,6 +807,9 @@ PHP_METHOD(Message, __construct);
 // This is called from the message class creation code.
 const upb_pbdecodermethod *new_fillmsg_decodermethod(Descriptor *desc,
                                                      const void *owner);
+void serialize_to_string(zval* val, zval* return_value TSRMLS_DC);
+void merge_from_string(const char* data, int data_len, const Descriptor* desc,
+                       MessageHeader* msg);
 
 PHP_METHOD(Message, serializeToString);
 PHP_METHOD(Message, mergeFromString);
@@ -880,6 +1016,21 @@ extern zend_class_entry* oneof_descriptor_type;
 // have a number of 0.
 #define ONEOF_CASE_NONE 0
 
+// -----------------------------------------------------------------------------
+// Well Known Type.
+// -----------------------------------------------------------------------------
+
+PHP_METHOD(Any, __construct);
+PHP_METHOD(Any, getTypeUrl);
+PHP_METHOD(Any, setTypeUrl);
+PHP_METHOD(Any, getValue);
+PHP_METHOD(Any, setValue);
+PHP_METHOD(Any, unpack);
+PHP_METHOD(Any, pack);
+PHP_METHOD(Any, is);
+
+extern zend_class_entry* any_type;
+
 // -----------------------------------------------------------------------------
 // Upb.
 // -----------------------------------------------------------------------------
@@ -910,4 +1061,16 @@ const zend_class_entry* field_type_class(
                       .bucket.obj.object))
 #endif
 
+// Message handler
+static inline zval* php_proto_message_read_property(
+    zval* msg, zval* member PHP_PROTO_TSRMLS_DC) {
+#if PHP_MAJOR_VERSION < 7
+  return message_handlers->read_property(msg, member, BP_VAR_R,
+                                         NULL PHP_PROTO_TSRMLS_CC);
+#else
+  return message_handlers->read_property(msg, member, BP_VAR_R, NULL,
+                                         NULL PHP_PROTO_TSRMLS_CC);
+#endif
+}
+
 #endif  // __GOOGLE_PROTOBUF_PHP_PROTOBUF_H__

+ 30 - 0
php/src/GPBMetadata/Google/Protobuf/Any.php

@@ -0,0 +1,30 @@
+<?php
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: google/protobuf/any.proto
+
+namespace GPBMetadata\Google\Protobuf;
+
+class Any
+{
+    public static $is_initialized = false;
+
+    public static function initOnce() {
+        $pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
+
+        if (static::$is_initialized == true) {
+          return;
+        }
+        $pool->internalAddGeneratedFile(hex2bin(
+            "0acd010a19676f6f676c652f70726f746f6275662f616e792e70726f746f" .
+            "120f676f6f676c652e70726f746f62756622260a03416e7912100a087479" .
+            "70655f75726c180120012809120d0a0576616c756518022001280c426f0a" .
+            "13636f6d2e676f6f676c652e70726f746f6275664208416e7950726f746f" .
+            "50015a256769746875622e636f6d2f676f6c616e672f70726f746f627566" .
+            "2f7074797065732f616e79a20203475042aa021e476f6f676c652e50726f" .
+            "746f6275662e57656c6c4b6e6f776e5479706573620670726f746f33"
+        ));
+
+        static::$is_initialized = true;
+    }
+}
+

+ 274 - 0
php/src/Google/Protobuf/Any.php

@@ -0,0 +1,274 @@
+<?php
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: google/protobuf/any.proto
+
+namespace Google\Protobuf;
+
+use Google\Protobuf\Internal\DescriptorPool;
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\GPBUtil;
+use Google\Protobuf\Internal\Message;
+use Google\Protobuf\Internal\RepeatedField;
+
+/**
+ * `Any` contains an arbitrary serialized protocol buffer message along with a
+ * URL that describes the type of the serialized message.
+ * Protobuf library provides support to pack/unpack Any values in the form
+ * of utility functions or additional generated methods of the Any type.
+ * Example 1: Pack and unpack a message in C++.
+ *     Foo foo = ...;
+ *     Any any;
+ *     any.PackFrom(foo);
+ *     ...
+ *     if (any.UnpackTo(&foo)) {
+ *       ...
+ *     }
+ * Example 2: Pack and unpack a message in Java.
+ *     Foo foo = ...;
+ *     Any any = Any.pack(foo);
+ *     ...
+ *     if (any.is(Foo.class)) {
+ *       foo = any.unpack(Foo.class);
+ *     }
+ *  Example 3: Pack and unpack a message in Python.
+ *     foo = Foo(...)
+ *     any = Any()
+ *     any.Pack(foo)
+ *     ...
+ *     if any.Is(Foo.DESCRIPTOR):
+ *       any.Unpack(foo)
+ *       ...
+ * The pack methods provided by protobuf library will by default use
+ * 'type.googleapis.com/full.type.name' as the type URL and the unpack
+ * methods only use the fully qualified type name after the last '/'
+ * in the type URL, for example "foo.bar.com/x/y.z" will yield type
+ * name "y.z".
+ * JSON
+ * ====
+ * The JSON representation of an `Any` value uses the regular
+ * representation of the deserialized, embedded message, with an
+ * additional field `&#64;type` which contains the type URL. Example:
+ *     package google.profile;
+ *     message Person {
+ *       string first_name = 1;
+ *       string last_name = 2;
+ *     }
+ *     {
+ *       "&#64;type": "type.googleapis.com/google.profile.Person",
+ *       "firstName": <string>,
+ *       "lastName": <string>
+ *     }
+ * If the embedded message type is well-known and has a custom JSON
+ * representation, that representation will be embedded adding a field
+ * `value` which holds the custom JSON in addition to the `&#64;type`
+ * field. Example (for message [google.protobuf.Duration][]):
+ *     {
+ *       "&#64;type": "type.googleapis.com/google.protobuf.Duration",
+ *       "value": "1.212s"
+ *     }
+ *
+ * Generated from protobuf message <code>google.protobuf.Any</code>
+ */
+class Any extends \Google\Protobuf\Internal\Message
+{
+    /**
+     * A URL/resource name whose content describes the type of the
+     * serialized protocol buffer message.
+     * For URLs which use the scheme `http`, `https`, or no scheme, the
+     * following restrictions and interpretations apply:
+     * * If no scheme is provided, `https` is assumed.
+     * * The last segment of the URL's path must represent the fully
+     *   qualified name of the type (as in `path/google.protobuf.Duration`).
+     *   The name should be in a canonical form (e.g., leading "." is
+     *   not accepted).
+     * * An HTTP GET on the URL must yield a [google.protobuf.Type][]
+     *   value in binary format, or produce an error.
+     * * Applications are allowed to cache lookup results based on the
+     *   URL, or have them precompiled into a binary to avoid any
+     *   lookup. Therefore, binary compatibility needs to be preserved
+     *   on changes to types. (Use versioned type names to manage
+     *   breaking changes.)
+     * Schemes other than `http`, `https` (or the empty scheme) might be
+     * used with implementation specific semantics.
+     *
+     * Generated from protobuf field <code>string type_url = 1;</code>
+     */
+    private $type_url = '';
+    /**
+     * Must be a valid serialized protocol buffer of the above specified type.
+     *
+     * Generated from protobuf field <code>bytes value = 2;</code>
+     */
+    private $value = '';
+
+    const TYPE_URL_PREFIX = 'type.googleapis.com/';
+
+    public function __construct() {
+        \GPBMetadata\Google\Protobuf\Any::initOnce();
+        parent::__construct();
+    }
+
+    /**
+     * A URL/resource name whose content describes the type of the
+     * serialized protocol buffer message.
+     * For URLs which use the scheme `http`, `https`, or no scheme, the
+     * following restrictions and interpretations apply:
+     * * If no scheme is provided, `https` is assumed.
+     * * The last segment of the URL's path must represent the fully
+     *   qualified name of the type (as in `path/google.protobuf.Duration`).
+     *   The name should be in a canonical form (e.g., leading "." is
+     *   not accepted).
+     * * An HTTP GET on the URL must yield a [google.protobuf.Type][]
+     *   value in binary format, or produce an error.
+     * * Applications are allowed to cache lookup results based on the
+     *   URL, or have them precompiled into a binary to avoid any
+     *   lookup. Therefore, binary compatibility needs to be preserved
+     *   on changes to types. (Use versioned type names to manage
+     *   breaking changes.)
+     * Schemes other than `http`, `https` (or the empty scheme) might be
+     * used with implementation specific semantics.
+     *
+     * Generated from protobuf field <code>string type_url = 1;</code>
+     * @return string
+     */
+    public function getTypeUrl()
+    {
+        return $this->type_url;
+    }
+
+    /**
+     * A URL/resource name whose content describes the type of the
+     * serialized protocol buffer message.
+     * For URLs which use the scheme `http`, `https`, or no scheme, the
+     * following restrictions and interpretations apply:
+     * * If no scheme is provided, `https` is assumed.
+     * * The last segment of the URL's path must represent the fully
+     *   qualified name of the type (as in `path/google.protobuf.Duration`).
+     *   The name should be in a canonical form (e.g., leading "." is
+     *   not accepted).
+     * * An HTTP GET on the URL must yield a [google.protobuf.Type][]
+     *   value in binary format, or produce an error.
+     * * Applications are allowed to cache lookup results based on the
+     *   URL, or have them precompiled into a binary to avoid any
+     *   lookup. Therefore, binary compatibility needs to be preserved
+     *   on changes to types. (Use versioned type names to manage
+     *   breaking changes.)
+     * Schemes other than `http`, `https` (or the empty scheme) might be
+     * used with implementation specific semantics.
+     *
+     * Generated from protobuf field <code>string type_url = 1;</code>
+     * @param string $var
+     * @return $this
+     */
+    public function setTypeUrl($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->type_url = $var;
+
+        return $this;
+    }
+
+    /**
+     * Must be a valid serialized protocol buffer of the above specified type.
+     *
+     * Generated from protobuf field <code>bytes value = 2;</code>
+     * @return string
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    /**
+     * Must be a valid serialized protocol buffer of the above specified type.
+     *
+     * Generated from protobuf field <code>bytes value = 2;</code>
+     * @param string $var
+     * @return $this
+     */
+    public function setValue($var)
+    {
+        GPBUtil::checkString($var, False);
+        $this->value = $var;
+
+        return $this;
+    }
+
+    /**
+     * This method will try to resolve the type_url in Any message to get the
+     * targeted message type. If failed, an error will be thrown. Otherwise,
+     * the method will create a message of the targeted type and fill it with
+     * the decoded value in Any.
+     * @return unpacked message
+     * @throws Exception Type url needs to be type.googleapis.com/fully-qulified.
+     * @throws Exception Class hasn't been added to descriptor pool.
+     * @throws Exception cannot decode data in value field.
+     */
+    public function unpack()
+    {
+        // Get fully qualifed name from type url.
+        $type_url_len = strlen($this->type_url);
+        $url_prifix_len = strlen(Any::TYPE_URL_PREFIX);
+        if ($type_url_len < url_prifix_len ||
+            substr($this->type_url, 0, $url_prifix_len) !=
+                Any::TYPE_URL_PREFIX) {
+            throw new \Exception(
+                "Type url needs to be type.googleapis.com/fully-qulified");
+        }
+        $fully_qualifed_name =
+            substr($this->type_url, $url_prifix_len, $type_url_len);
+
+        // Create message according to fully qualified name.
+        $pool = DescriptorPool::getGeneratedPool();
+        $desc = $pool->getDescriptorByProtoName( ".".$fully_qualifed_name);
+        if (is_null($desc)) {
+            throw new \Exception("Class ".$fully_qualifed_name
+                                     ." hasn't been added to descriptor pool");
+        }
+        $klass = $desc->getClass();
+        $msg = new $klass();
+
+        // Merge data into message.
+        $msg->mergeFromString($this->value);
+        return $msg;
+    }
+
+    /**
+     * The type_url will be created according to the given message’s type and
+     * the value is encoded data from the given message..
+     * @param message: A proto message.
+     */
+    public function pack($msg)
+    {
+        if (!$msg instanceof Message) {
+            trigger_error("Given parameter is not a message instance.",
+                          E_USER_ERROR);
+            return;
+        }
+
+        // Set value using serialzed message.
+        $this->value = $msg->serializeToString();
+
+        // Set type url.
+        $pool = DescriptorPool::getGeneratedPool();
+        $desc = $pool->getDescriptorByClassName(get_class($msg));
+        $fully_qualifed_name = $desc->getFullName();
+        $this->type_url = Any::TYPE_URL_PREFIX.substr(
+            $fully_qualifed_name, 1, strlen($fully_qualifed_name));
+    }
+
+    /**
+     * This method returns whether the type_url in any_message is corresponded
+     * to the given class.
+     * @param klass: The fully qualified PHP class name of a proto message type.
+     */
+    public function is($klass)
+    {
+        $pool = DescriptorPool::getGeneratedPool();
+        $desc = $pool->getDescriptorByClassName($klass);
+        $fully_qualifed_name = $desc->getFullName();
+        $type_url = Any::TYPE_URL_PREFIX.substr(
+            $fully_qualifed_name, 1, strlen($fully_qualifed_name));
+        return $this->type_url === $type_url;
+    }
+}

+ 1 - 1
php/tests/gdb_test.sh

@@ -3,7 +3,7 @@
 # gdb --args php -dextension=../ext/google/protobuf/modules/protobuf.so `which
 # phpunit` --bootstrap autoload.php tmp_test.php
 #
-gdb --args php -dextension=../ext/google/protobuf/modules/protobuf.so `which phpunit` --bootstrap autoload.php encode_decode_test.php
+gdb --args php -dextension=../ext/google/protobuf/modules/protobuf.so `which phpunit` --bootstrap autoload.php well_known_test.php
 #
 # gdb --args php -dextension=../ext/google/protobuf/modules/protobuf.so memory_leak_test.php
 #

+ 73 - 1
php/tests/well_known_test.php

@@ -1,8 +1,16 @@
 <?php
 
+require_once('test_base.php');
+require_once('test_util.php');
+
 use Google\Protobuf\GPBEmpty;
+use Google\Protobuf\Any;
+
+use Foo\TestMessage;
+
+class NotMessage {}
 
-class WellKnownTest extends PHPUnit_Framework_TestCase {
+class WellKnownTest extends TestBase {
 
     public function testNone()
     {
@@ -14,4 +22,68 @@ class WellKnownTest extends PHPUnit_Framework_TestCase {
         $msg = new TestImportDescriptorProto();
     }
 
+    public function testAny()
+    {
+        // Create embed message
+        $embed = new TestMessage();
+        $this->setFields($embed);
+        $data = $embed->serializeToString();
+
+        // Set any via normal setter.
+        $any = new Any();
+
+        $this->assertSame(
+            $any, $any->setTypeUrl("type.googleapis.com/foo.TestMessage"));
+        $this->assertSame("type.googleapis.com/foo.TestMessage",
+                          $any->getTypeUrl());
+
+        $this->assertSame($any, $any->setValue($data));
+        $this->assertSame($data, $any->getValue());
+
+        // Test unpack.
+        $msg = $any->unpack();
+        $this->assertTrue($msg instanceof TestMessage);
+        $this->expectFields($msg);
+
+        // Test pack.
+        $any = new Any();
+        $any->pack($embed);
+        $this->assertSame($data, $any->getValue());
+        $this->assertSame("type.googleapis.com/foo.TestMessage", $any->getTypeUrl());
+
+        // Test is.
+        $this->assertTrue($any->is(TestMessage::class));
+        $this->assertFalse($any->is(Any::class));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testAnyUnpackInvalidTypeUrl()
+    {
+        $any = new Any();
+        $any->setTypeUrl("invalid");
+        $any->unpack();
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testAnyUnpackMessageNotAdded()
+    {
+        $any = new Any();
+        $any->setTypeUrl("type.googleapis.com/MessageNotAdded");
+        $any->unpack();
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testAnyUnpackDecodeError()
+    {
+        $any = new Any();
+        $any->setTypeUrl("type.googleapis.com/foo.TestMessage");
+        $any->setValue("abc");
+        $any->unpack();
+    }
 }