Browse Source

Fix php well known type conformance tests (#3828) (#3840)

* Fix php well known type conformance tests

* Properly generate code for test.proto

* Provide GPBMetadata files in c extensions for generated files to import.

* Remove unnecessary test

* Clean up code

* Add declaration for initOnce.

* Refactoring
Paul Yang 8 years ago
parent
commit
2df472690e

+ 2 - 2
conformance/Makefile.am

@@ -256,7 +256,7 @@ if USE_EXTERNAL_PROTOC
 protoc_middleman: $(conformance_protoc_inputs) $(conformance_proto2_protoc_inputs) $(well_known_type_protoc_inputs) google-protobuf
 protoc_middleman: $(conformance_protoc_inputs) $(conformance_proto2_protoc_inputs) $(well_known_type_protoc_inputs) google-protobuf
 	$(PROTOC) -I$(srcdir) -I$(top_srcdir) --cpp_out=. --java_out=. --ruby_out=. --objc_out=. --python_out=. --php_out=. --js_out=import_style=commonjs,binary:. $(conformance_protoc_inputs)
 	$(PROTOC) -I$(srcdir) -I$(top_srcdir) --cpp_out=. --java_out=. --ruby_out=. --objc_out=. --python_out=. --php_out=. --js_out=import_style=commonjs,binary:. $(conformance_protoc_inputs)
 	$(PROTOC) -I$(srcdir) -I$(top_srcdir) --cpp_out=. --java_out=. --objc_out=. --python_out=. --js_out=import_style=commonjs,binary:. $(conformance_proto2_protoc_inputs)
 	$(PROTOC) -I$(srcdir) -I$(top_srcdir) --cpp_out=. --java_out=. --objc_out=. --python_out=. --js_out=import_style=commonjs,binary:. $(conformance_proto2_protoc_inputs)
-	$(PROTOC) -I$(srcdir) -I$(top_srcdir) --cpp_out=. --java_out=. --ruby_out=. --python_out=. --php_out=. --js_out=import_style=commonjs,binary:google-protobuf $(well_known_type_protoc_inputs)
+	$(PROTOC) -I$(srcdir) -I$(top_srcdir) --cpp_out=. --java_out=. --ruby_out=. --python_out=. --js_out=import_style=commonjs,binary:google-protobuf $(well_known_type_protoc_inputs)
 	## $(PROTOC) -I$(srcdir) -I$(top_srcdir) --java_out=lite:lite $(conformance_protoc_inputs) $(well_known_type_protoc_inputs)
 	## $(PROTOC) -I$(srcdir) -I$(top_srcdir) --java_out=lite:lite $(conformance_protoc_inputs) $(well_known_type_protoc_inputs)
 	touch protoc_middleman
 	touch protoc_middleman
 
 
@@ -268,7 +268,7 @@ else
 protoc_middleman: $(top_srcdir)/src/protoc$(EXEEXT) $(conformance_protoc_inputs) $(conformance_proto2_protoc_inputs) $(well_known_type_protoc_inputs) google-protobuf
 protoc_middleman: $(top_srcdir)/src/protoc$(EXEEXT) $(conformance_protoc_inputs) $(conformance_proto2_protoc_inputs) $(well_known_type_protoc_inputs) google-protobuf
 	oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --cpp_out=$$oldpwd --java_out=$$oldpwd --ruby_out=$$oldpwd --objc_out=$$oldpwd --python_out=$$oldpwd --php_out=$$oldpwd --js_out=import_style=commonjs,binary:$$oldpwd $(conformance_protoc_inputs) )
 	oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --cpp_out=$$oldpwd --java_out=$$oldpwd --ruby_out=$$oldpwd --objc_out=$$oldpwd --python_out=$$oldpwd --php_out=$$oldpwd --js_out=import_style=commonjs,binary:$$oldpwd $(conformance_protoc_inputs) )
 	oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --cpp_out=$$oldpwd --java_out=$$oldpwd --objc_out=. --python_out=$$oldpwd --js_out=import_style=commonjs,binary:$$oldpwd $(conformance_proto2_protoc_inputs) )
 	oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --cpp_out=$$oldpwd --java_out=$$oldpwd --objc_out=. --python_out=$$oldpwd --js_out=import_style=commonjs,binary:$$oldpwd $(conformance_proto2_protoc_inputs) )
-	oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --cpp_out=$$oldpwd --java_out=$$oldpwd --ruby_out=$$oldpwd --python_out=$$oldpwd --php_out=$$oldpwd --js_out=import_style=commonjs,binary:$$oldpwd/google-protobuf $(well_known_type_protoc_inputs) )
+	oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --cpp_out=$$oldpwd --java_out=$$oldpwd --ruby_out=$$oldpwd --python_out=$$oldpwd --js_out=import_style=commonjs,binary:$$oldpwd/google-protobuf $(well_known_type_protoc_inputs) )
 	## @mkdir -p lite
 	## @mkdir -p lite
 	## oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --java_out=lite:$$oldpwd/lite $(conformance_protoc_inputs) $(well_known_type_protoc_inputs) )
 	## oldpwd=`pwd` && ( cd $(srcdir) && $$oldpwd/../src/protoc$(EXEEXT) -I. -I$(top_srcdir)/src --java_out=lite:$$oldpwd/lite $(conformance_protoc_inputs) $(well_known_type_protoc_inputs) )
 	touch protoc_middleman
 	touch protoc_middleman

+ 1 - 1
conformance/autoload.php

@@ -2,7 +2,7 @@
 
 
 define("GOOGLE_INTERNAL_NAMESPACE", "Google\\Protobuf\\Internal\\");
 define("GOOGLE_INTERNAL_NAMESPACE", "Google\\Protobuf\\Internal\\");
 define("GOOGLE_NAMESPACE", "Google\\Protobuf\\");
 define("GOOGLE_NAMESPACE", "Google\\Protobuf\\");
-define("GOOGLE_GPBMETADATA_NAMESPACE", "GPBMetadata\\Google\\Protobuf\\Internal\\");
+define("GOOGLE_GPBMETADATA_NAMESPACE", "GPBMetadata\\Google\\Protobuf\\");
 
 
 function protobuf_autoloader_impl($class, $prefix) {
 function protobuf_autoloader_impl($class, $prefix) {
     $length = strlen($prefix);
     $length = strlen($prefix);

+ 6 - 25
conformance/conformance_php.php

@@ -3,24 +3,6 @@
 require_once("Conformance/WireFormat.php");
 require_once("Conformance/WireFormat.php");
 require_once("Conformance/ConformanceResponse.php");
 require_once("Conformance/ConformanceResponse.php");
 require_once("Conformance/ConformanceRequest.php");
 require_once("Conformance/ConformanceRequest.php");
-require_once("Google/Protobuf/Any.php");
-require_once("Google/Protobuf/Duration.php");
-require_once("Google/Protobuf/FieldMask.php");
-require_once("Google/Protobuf/Struct.php");
-require_once("Google/Protobuf/Value.php");
-require_once("Google/Protobuf/ListValue.php");
-require_once("Google/Protobuf/NullValue.php");
-require_once("Google/Protobuf/Timestamp.php");
-require_once("Google/Protobuf/DoubleValue.php");
-require_once("Google/Protobuf/BytesValue.php");
-require_once("Google/Protobuf/FloatValue.php");
-require_once("Google/Protobuf/Int64Value.php");
-require_once("Google/Protobuf/UInt32Value.php");
-require_once("Google/Protobuf/BoolValue.php");
-require_once("Google/Protobuf/DoubleValue.php");
-require_once("Google/Protobuf/Int32Value.php");
-require_once("Google/Protobuf/StringValue.php");
-require_once("Google/Protobuf/UInt64Value.php");
 require_once("Protobuf_test_messages/Proto3/ForeignMessage.php");
 require_once("Protobuf_test_messages/Proto3/ForeignMessage.php");
 require_once("Protobuf_test_messages/Proto3/ForeignEnum.php");
 require_once("Protobuf_test_messages/Proto3/ForeignEnum.php");
 require_once("Protobuf_test_messages/Proto3/TestAllTypesProto3.php");
 require_once("Protobuf_test_messages/Proto3/TestAllTypesProto3.php");
@@ -28,12 +10,6 @@ require_once("Protobuf_test_messages/Proto3/TestAllTypesProto3_NestedMessage.php
 require_once("Protobuf_test_messages/Proto3/TestAllTypesProto3_NestedEnum.php");
 require_once("Protobuf_test_messages/Proto3/TestAllTypesProto3_NestedEnum.php");
 
 
 require_once("GPBMetadata/Conformance.php");
 require_once("GPBMetadata/Conformance.php");
-require_once("GPBMetadata/Google/Protobuf/Any.php");
-require_once("GPBMetadata/Google/Protobuf/Duration.php");
-require_once("GPBMetadata/Google/Protobuf/FieldMask.php");
-require_once("GPBMetadata/Google/Protobuf/Struct.php");
-require_once("GPBMetadata/Google/Protobuf/Timestamp.php");
-require_once("GPBMetadata/Google/Protobuf/Wrappers.php");
 require_once("GPBMetadata/Google/Protobuf/TestMessagesProto3.php");
 require_once("GPBMetadata/Google/Protobuf/TestMessagesProto3.php");
 
 
 use  \Conformance\WireFormat;
 use  \Conformance\WireFormat;
@@ -78,7 +54,12 @@ function doTest($request)
     } elseif ($request->getRequestedOutputFormat() == WireFormat::PROTOBUF) {
     } elseif ($request->getRequestedOutputFormat() == WireFormat::PROTOBUF) {
       $response->setProtobufPayload($test_message->serializeToString());
       $response->setProtobufPayload($test_message->serializeToString());
     } elseif ($request->getRequestedOutputFormat() == WireFormat::JSON) {
     } elseif ($request->getRequestedOutputFormat() == WireFormat::JSON) {
-      $response->setJsonPayload($test_message->serializeToJsonString());
+      try {
+          $response->setJsonPayload($test_message->serializeToJsonString());
+      } catch (Exception $e) {
+          $response->setSerializeError($e->getMessage());
+          return $response;
+      }
     }
     }
 
 
     return $response;
     return $response;

+ 1 - 86
conformance/failure_list_php.txt

@@ -7,92 +7,7 @@ Recommended.Proto3.JsonInput.DurationHas3FractionalDigits.Validator
 Recommended.Proto3.JsonInput.DurationHas6FractionalDigits.Validator
 Recommended.Proto3.JsonInput.DurationHas6FractionalDigits.Validator
 Recommended.Proto3.JsonInput.DurationHas9FractionalDigits.Validator
 Recommended.Proto3.JsonInput.DurationHas9FractionalDigits.Validator
 Recommended.Proto3.JsonInput.DurationHasZeroFractionalDigit.Validator
 Recommended.Proto3.JsonInput.DurationHasZeroFractionalDigit.Validator
-Required.DurationProtoInputTooLarge.JsonOutput
-Required.DurationProtoInputTooSmall.JsonOutput
-Required.Proto3.JsonInput.Any.JsonOutput
-Required.Proto3.JsonInput.Any.ProtobufOutput
-Required.Proto3.JsonInput.AnyNested.JsonOutput
-Required.Proto3.JsonInput.AnyNested.ProtobufOutput
-Required.Proto3.JsonInput.AnyUnorderedTypeTag.JsonOutput
-Required.Proto3.JsonInput.AnyUnorderedTypeTag.ProtobufOutput
-Required.Proto3.JsonInput.AnyWithDuration.JsonOutput
-Required.Proto3.JsonInput.AnyWithDuration.ProtobufOutput
-Required.Proto3.JsonInput.AnyWithFieldMask.JsonOutput
-Required.Proto3.JsonInput.AnyWithFieldMask.ProtobufOutput
-Required.Proto3.JsonInput.AnyWithInt32ValueWrapper.JsonOutput
-Required.Proto3.JsonInput.AnyWithInt32ValueWrapper.ProtobufOutput
-Required.Proto3.JsonInput.AnyWithStruct.JsonOutput
-Required.Proto3.JsonInput.AnyWithStruct.ProtobufOutput
-Required.Proto3.JsonInput.AnyWithTimestamp.JsonOutput
-Required.Proto3.JsonInput.AnyWithTimestamp.ProtobufOutput
-Required.Proto3.JsonInput.AnyWithValueForInteger.JsonOutput
-Required.Proto3.JsonInput.AnyWithValueForInteger.ProtobufOutput
-Required.Proto3.JsonInput.AnyWithValueForJsonObject.JsonOutput
-Required.Proto3.JsonInput.AnyWithValueForJsonObject.ProtobufOutput
-Required.Proto3.JsonInput.DurationMaxValue.JsonOutput
-Required.Proto3.JsonInput.DurationMaxValue.ProtobufOutput
-Required.Proto3.JsonInput.DurationMinValue.JsonOutput
-Required.Proto3.JsonInput.DurationMinValue.ProtobufOutput
-Required.Proto3.JsonInput.DurationRepeatedValue.JsonOutput
-Required.Proto3.JsonInput.DurationRepeatedValue.ProtobufOutput
-Required.Proto3.JsonInput.FieldMask.JsonOutput
-Required.Proto3.JsonInput.FieldMask.ProtobufOutput
-Required.Proto3.JsonInput.OptionalBoolWrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalBoolWrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalBytesWrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalBytesWrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalDoubleWrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalDoubleWrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalFloatWrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalFloatWrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalInt32Wrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalInt32Wrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalInt64Wrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalInt64Wrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalStringWrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalStringWrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalUint32Wrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalUint32Wrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalUint64Wrapper.JsonOutput
-Required.Proto3.JsonInput.OptionalUint64Wrapper.ProtobufOutput
-Required.Proto3.JsonInput.OptionalWrapperTypesWithNonDefaultValue.JsonOutput
-Required.Proto3.JsonInput.OptionalWrapperTypesWithNonDefaultValue.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedBoolWrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedBoolWrapper.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedBytesWrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedBytesWrapper.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedDoubleWrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedDoubleWrapper.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedFloatWrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedFloatWrapper.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedInt32Wrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedInt32Wrapper.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedInt64Wrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedInt64Wrapper.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedStringWrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedStringWrapper.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedUint32Wrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedUint32Wrapper.ProtobufOutput
-Required.Proto3.JsonInput.RepeatedUint64Wrapper.JsonOutput
-Required.Proto3.JsonInput.RepeatedUint64Wrapper.ProtobufOutput
-Required.Proto3.JsonInput.Struct.JsonOutput
-Required.Proto3.JsonInput.Struct.ProtobufOutput
-Required.Proto3.JsonInput.ValueAcceptBool.JsonOutput
-Required.Proto3.JsonInput.ValueAcceptBool.ProtobufOutput
-Required.Proto3.JsonInput.ValueAcceptFloat.JsonOutput
-Required.Proto3.JsonInput.ValueAcceptFloat.ProtobufOutput
-Required.Proto3.JsonInput.ValueAcceptInteger.JsonOutput
-Required.Proto3.JsonInput.ValueAcceptInteger.ProtobufOutput
-Required.Proto3.JsonInput.ValueAcceptList.JsonOutput
-Required.Proto3.JsonInput.ValueAcceptList.ProtobufOutput
-Required.Proto3.JsonInput.ValueAcceptNull.JsonOutput
-Required.Proto3.JsonInput.ValueAcceptNull.ProtobufOutput
-Required.Proto3.JsonInput.ValueAcceptObject.JsonOutput
-Required.Proto3.JsonInput.ValueAcceptObject.ProtobufOutput
-Required.Proto3.JsonInput.ValueAcceptString.JsonOutput
-Required.Proto3.JsonInput.ValueAcceptString.ProtobufOutput
-Required.TimestampProtoInputTooLarge.JsonOutput
-Required.TimestampProtoInputTooSmall.JsonOutput
+Recommended.Proto3.JsonInput.FieldMaskInvalidCharacter
 Required.Proto3.JsonInput.FloatFieldTooLarge
 Required.Proto3.JsonInput.FloatFieldTooLarge
 Required.Proto3.JsonInput.FloatFieldTooSmall
 Required.Proto3.JsonInput.FloatFieldTooSmall
 Required.Proto3.JsonInput.DoubleFieldTooSmall
 Required.Proto3.JsonInput.DoubleFieldTooSmall

+ 42 - 0
php/ext/google/protobuf/message.c

@@ -2029,3 +2029,45 @@ PHP_PROTO_ONEOF_FIELD_ACCESSORS(Value, value, BoolValue, "bool_value")
 PHP_PROTO_ONEOF_FIELD_ACCESSORS(Value, value, StructValue, "struct_value")
 PHP_PROTO_ONEOF_FIELD_ACCESSORS(Value, value, StructValue, "struct_value")
 PHP_PROTO_ONEOF_FIELD_ACCESSORS(Value, value, ListValue, "list_value")
 PHP_PROTO_ONEOF_FIELD_ACCESSORS(Value, value, ListValue, "list_value")
 PHP_PROTO_ONEOF_ACCESSORS(Value, value, Kind, "kind")
 PHP_PROTO_ONEOF_ACCESSORS(Value, value, Kind, "kind")
+
+// -----------------------------------------------------------------------------
+// GPBMetadata files for well known types
+// -----------------------------------------------------------------------------
+
+#define DEFINE_GPBMETADATA_FILE(LOWERNAME, CAMELNAME, CLASSNAME)      \
+  zend_class_entry* gpb_metadata_##LOWERNAME##_type;                  \
+  static zend_function_entry gpb_metadata_##LOWERNAME##_methods[] = { \
+    PHP_ME(GPBMetadata_##CAMELNAME, initOnce, NULL,                   \
+           ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)                         \
+    ZEND_FE_END                                                       \
+  };                                                                  \
+  void gpb_metadata_##LOWERNAME##_init(TSRMLS_D) {                    \
+    zend_class_entry class_type;                                      \
+    INIT_CLASS_ENTRY(class_type, CLASSNAME,                           \
+                     gpb_metadata_##LOWERNAME##_methods);             \
+    gpb_metadata_##LOWERNAME##_type =                                 \
+        zend_register_internal_class(&class_type TSRMLS_CC);          \
+  }                                                                   \
+  PHP_METHOD(GPBMetadata_##CAMELNAME, initOnce) {                     \
+    init_file_##LOWERNAME(TSRMLS_C);                                  \
+  }
+
+DEFINE_GPBMETADATA_FILE(any, Any, "GPBMetadata\\Google\\Protobuf\\Any");
+DEFINE_GPBMETADATA_FILE(api, Api, "GPBMetadata\\Google\\Protobuf\\Api");
+DEFINE_GPBMETADATA_FILE(duration, Duration,
+                        "GPBMetadata\\Google\\Protobuf\\Duration");
+DEFINE_GPBMETADATA_FILE(field_mask, FieldMask,
+                        "GPBMetadata\\Google\\Protobuf\\FieldMask");
+DEFINE_GPBMETADATA_FILE(empty, Empty,
+                        "GPBMetadata\\Google\\Protobuf\\GPBEmpty");
+DEFINE_GPBMETADATA_FILE(source_context, SourceContext,
+                        "GPBMetadata\\Google\\Protobuf\\SourceContext");
+DEFINE_GPBMETADATA_FILE(struct, Struct,
+                        "GPBMetadata\\Google\\Protobuf\\Struct");
+DEFINE_GPBMETADATA_FILE(timestamp, Timestamp,
+                        "GPBMetadata\\Google\\Protobuf\\Timestamp");
+DEFINE_GPBMETADATA_FILE(type, Type, "GPBMetadata\\Google\\Protobuf\\Type");
+DEFINE_GPBMETADATA_FILE(wrappers, Wrappers,
+                        "GPBMetadata\\Google\\Protobuf\\Wrappers");
+
+#undef DEFINE_GPBMETADATA_FILE

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

@@ -300,6 +300,17 @@ static PHP_MINIT_FUNCTION(protobuf) {
   repeated_field_iter_init(TSRMLS_C);
   repeated_field_iter_init(TSRMLS_C);
   util_init(TSRMLS_C);
   util_init(TSRMLS_C);
 
 
+  gpb_metadata_any_init(TSRMLS_C);
+  gpb_metadata_api_init(TSRMLS_C);
+  gpb_metadata_duration_init(TSRMLS_C);
+  gpb_metadata_field_mask_init(TSRMLS_C);
+  gpb_metadata_empty_init(TSRMLS_C);
+  gpb_metadata_source_context_init(TSRMLS_C);
+  gpb_metadata_struct_init(TSRMLS_C);
+  gpb_metadata_timestamp_init(TSRMLS_C);
+  gpb_metadata_type_init(TSRMLS_C);
+  gpb_metadata_wrappers_init(TSRMLS_C);
+
   any_init(TSRMLS_C);
   any_init(TSRMLS_C);
   api_init(TSRMLS_C);
   api_init(TSRMLS_C);
   bool_value_init(TSRMLS_C);
   bool_value_init(TSRMLS_C);

+ 22 - 2
php/ext/google/protobuf/protobuf.h

@@ -610,7 +610,6 @@ typedef struct BytesValue BytesValue;
 typedef struct Descriptor Descriptor;
 typedef struct Descriptor Descriptor;
 typedef struct Descriptor Descriptor;
 typedef struct Descriptor Descriptor;
 typedef struct DescriptorPool DescriptorPool;
 typedef struct DescriptorPool DescriptorPool;
-typedef struct DescriptorPool DescriptorPool;
 typedef struct DoubleValue DoubleValue;
 typedef struct DoubleValue DoubleValue;
 typedef struct Duration Duration;
 typedef struct Duration Duration;
 typedef struct Enum Enum;
 typedef struct Enum Enum;
@@ -630,7 +629,6 @@ typedef struct GPBEmpty GPBEmpty;
 typedef struct Int32Value Int32Value;
 typedef struct Int32Value Int32Value;
 typedef struct Int64Value Int64Value;
 typedef struct Int64Value Int64Value;
 typedef struct InternalDescriptorPool InternalDescriptorPool;
 typedef struct InternalDescriptorPool InternalDescriptorPool;
-typedef struct InternalDescriptorPool InternalDescriptorPool;
 typedef struct ListValue ListValue;
 typedef struct ListValue ListValue;
 typedef struct Map Map;
 typedef struct Map Map;
 typedef struct Map Map;
 typedef struct Map Map;
@@ -714,6 +712,17 @@ void uint64_value_init(TSRMLS_D);
 void util_init(TSRMLS_D);
 void util_init(TSRMLS_D);
 void value_init(TSRMLS_D);
 void value_init(TSRMLS_D);
 
 
+void gpb_metadata_any_init(TSRMLS_D);
+void gpb_metadata_api_init(TSRMLS_D);
+void gpb_metadata_duration_init(TSRMLS_D);
+void gpb_metadata_field_mask_init(TSRMLS_D);
+void gpb_metadata_empty_init(TSRMLS_D);
+void gpb_metadata_source_context_init(TSRMLS_D);
+void gpb_metadata_struct_init(TSRMLS_D);
+void gpb_metadata_timestamp_init(TSRMLS_D);
+void gpb_metadata_type_init(TSRMLS_D);
+void gpb_metadata_wrappers_init(TSRMLS_D);
+
 // Global map from upb {msg,enum}defs to wrapper Descriptor/EnumDescriptor
 // Global map from upb {msg,enum}defs to wrapper Descriptor/EnumDescriptor
 // instances.
 // instances.
 void add_def_obj(const void* def, PHP_PROTO_HASHTABLE_VALUE value);
 void add_def_obj(const void* def, PHP_PROTO_HASHTABLE_VALUE value);
@@ -1170,6 +1179,17 @@ extern zend_class_entry* oneof_descriptor_type;
 // Well Known Type.
 // Well Known Type.
 // -----------------------------------------------------------------------------
 // -----------------------------------------------------------------------------
 
 
+PHP_METHOD(GPBMetadata_Any, initOnce);
+PHP_METHOD(GPBMetadata_Api, initOnce);
+PHP_METHOD(GPBMetadata_Duration, initOnce);
+PHP_METHOD(GPBMetadata_FieldMask, initOnce);
+PHP_METHOD(GPBMetadata_Empty, initOnce);
+PHP_METHOD(GPBMetadata_SourceContext, initOnce);
+PHP_METHOD(GPBMetadata_Struct, initOnce);
+PHP_METHOD(GPBMetadata_Timestamp, initOnce);
+PHP_METHOD(GPBMetadata_Type, initOnce);
+PHP_METHOD(GPBMetadata_Wrappers, initOnce);
+
 PHP_METHOD(Any, __construct);
 PHP_METHOD(Any, __construct);
 PHP_METHOD(Any, getTypeUrl);
 PHP_METHOD(Any, getTypeUrl);
 PHP_METHOD(Any, setTypeUrl);
 PHP_METHOD(Any, setTypeUrl);

+ 4 - 4
php/src/Google/Protobuf/Any.php

@@ -207,9 +207,9 @@ class Any extends \Google\Protobuf\Internal\Message
     public function unpack()
     public function unpack()
     {
     {
         // Get fully qualifed name from type url.
         // Get fully qualifed name from type url.
-        $url_prifix_len = strlen(Any::TYPE_URL_PREFIX);
+        $url_prifix_len = strlen(GPBUtil::TYPE_URL_PREFIX);
         if (substr($this->type_url, 0, $url_prifix_len) !=
         if (substr($this->type_url, 0, $url_prifix_len) !=
-                Any::TYPE_URL_PREFIX) {
+                GPBUtil::TYPE_URL_PREFIX) {
             throw new \Exception(
             throw new \Exception(
                 "Type url needs to be type.googleapis.com/fully-qulified");
                 "Type url needs to be type.googleapis.com/fully-qulified");
         }
         }
@@ -251,7 +251,7 @@ class Any extends \Google\Protobuf\Internal\Message
         $pool = DescriptorPool::getGeneratedPool();
         $pool = DescriptorPool::getGeneratedPool();
         $desc = $pool->getDescriptorByClassName(get_class($msg));
         $desc = $pool->getDescriptorByClassName(get_class($msg));
         $fully_qualifed_name = $desc->getFullName();
         $fully_qualifed_name = $desc->getFullName();
-        $this->type_url = Any::TYPE_URL_PREFIX.substr(
+        $this->type_url = GPBUtil::TYPE_URL_PREFIX.substr(
             $fully_qualifed_name, 1, strlen($fully_qualifed_name));
             $fully_qualifed_name, 1, strlen($fully_qualifed_name));
     }
     }
 
 
@@ -265,7 +265,7 @@ class Any extends \Google\Protobuf\Internal\Message
         $pool = DescriptorPool::getGeneratedPool();
         $pool = DescriptorPool::getGeneratedPool();
         $desc = $pool->getDescriptorByClassName($klass);
         $desc = $pool->getDescriptorByClassName($klass);
         $fully_qualifed_name = $desc->getFullName();
         $fully_qualifed_name = $desc->getFullName();
-        $type_url = Any::TYPE_URL_PREFIX.substr(
+        $type_url = GPBUtil::TYPE_URL_PREFIX.substr(
             $fully_qualifed_name, 1, strlen($fully_qualifed_name));
             $fully_qualifed_name, 1, strlen($fully_qualifed_name));
         return $this->type_url === $type_url;
         return $this->type_url === $type_url;
     }
     }

+ 33 - 14
php/src/Google/Protobuf/Internal/GPBJsonWire.php

@@ -38,19 +38,26 @@ class GPBJsonWire
     public static function serializeFieldToStream(
     public static function serializeFieldToStream(
         $value,
         $value,
         $field,
         $field,
-        &$output)
+        &$output, $has_field_name = true)
     {
     {
-        $output->writeRaw("\"", 1);
-        $field_name = GPBJsonWire::formatFieldName($field);
-        $output->writeRaw($field_name, strlen($field_name));
-        $output->writeRaw("\":", 2);
-        return static::serializeFieldValueToStream($value, $field, $output);
+        if ($has_field_name) {
+            $output->writeRaw("\"", 1);
+            $field_name = GPBJsonWire::formatFieldName($field);
+            $output->writeRaw($field_name, strlen($field_name));
+            $output->writeRaw("\":", 2);
+        }
+        return static::serializeFieldValueToStream(
+            $value,
+            $field,
+            $output,
+            !$has_field_name);
     }
     }
 
 
-    private static function serializeFieldValueToStream(
+    public static function serializeFieldValueToStream(
         $values,
         $values,
         $field,
         $field,
-        &$output)
+        &$output,
+        $is_well_known = false)
     {
     {
         if ($field->isMap()) {
         if ($field->isMap()) {
             $output->writeRaw("{", 1);
             $output->writeRaw("{", 1);
@@ -84,7 +91,8 @@ class GPBJsonWire
                 if (!static::serializeSingularFieldValueToStream(
                 if (!static::serializeSingularFieldValueToStream(
                     $key,
                     $key,
                     $key_field,
                     $key_field,
-                    $output)) {
+                    $output,
+                    $is_well_known)) {
                     return false;
                     return false;
                 }
                 }
                 if ($additional_quote) {
                 if ($additional_quote) {
@@ -94,7 +102,8 @@ class GPBJsonWire
                 if (!static::serializeSingularFieldValueToStream(
                 if (!static::serializeSingularFieldValueToStream(
                     $value,
                     $value,
                     $value_field,
                     $value_field,
-                    $output)) {
+                    $output,
+                    $is_well_known)) {
                     return false;
                     return false;
                 }
                 }
             }
             }
@@ -112,7 +121,8 @@ class GPBJsonWire
                 if (!static::serializeSingularFieldValueToStream(
                 if (!static::serializeSingularFieldValueToStream(
                     $value,
                     $value,
                     $field,
                     $field,
-                    $output)) {
+                    $output,
+                    $is_well_known)) {
                     return false;
                     return false;
                 }
                 }
             }
             }
@@ -122,14 +132,15 @@ class GPBJsonWire
             return static::serializeSingularFieldValueToStream(
             return static::serializeSingularFieldValueToStream(
                 $values,
                 $values,
                 $field,
                 $field,
-                $output);
+                $output,
+                $is_well_known);
         }
         }
     }
     }
 
 
     private static function serializeSingularFieldValueToStream(
     private static function serializeSingularFieldValueToStream(
         $value,
         $value,
         $field,
         $field,
-        &$output)
+        &$output, $is_well_known = false)
     {
     {
         switch ($field->getType()) {
         switch ($field->getType()) {
             case GPBType::SFIXED32:
             case GPBType::SFIXED32:
@@ -186,6 +197,10 @@ class GPBJsonWire
                 break;
                 break;
             case GPBType::ENUM:
             case GPBType::ENUM:
                 $enum_desc = $field->getEnumType();
                 $enum_desc = $field->getEnumType();
+                if ($enum_desc->getClass() === "Google\Protobuf\NullValue") {
+                    $output->writeRaw("null", 4);
+                    break;
+                }
                 $enum_value_desc = $enum_desc->getValueByNumber($value);
                 $enum_value_desc = $enum_desc->getValueByNumber($value);
                 if (!is_null($enum_value_desc)) {
                 if (!is_null($enum_value_desc)) {
                     $str_value = $enum_value_desc->getName();
                     $str_value = $enum_value_desc->getName();
@@ -205,7 +220,11 @@ class GPBJsonWire
                 }
                 }
                 break;
                 break;
             case GPBType::BYTES:
             case GPBType::BYTES:
-                $value = base64_encode($value);
+                $bytes_value = base64_encode($value);
+                $output->writeRaw("\"", 1);
+                $output->writeRaw($bytes_value, strlen($bytes_value));
+                $output->writeRaw("\"", 1);
+                break;
             case GPBType::STRING:
             case GPBType::STRING:
                 $value = json_encode($value);
                 $value = json_encode($value);
                 $output->writeRaw($value, strlen($value));
                 $output->writeRaw($value, strlen($value));

+ 136 - 3
php/src/Google/Protobuf/Internal/GPBUtil.php

@@ -32,14 +32,29 @@
 
 
 namespace Google\Protobuf\Internal;
 namespace Google\Protobuf\Internal;
 
 
+use Google\Protobuf\Duration;
+use Google\Protobuf\FieldMask;
 use Google\Protobuf\Internal\GPBType;
 use Google\Protobuf\Internal\GPBType;
 use Google\Protobuf\Internal\RepeatedField;
 use Google\Protobuf\Internal\RepeatedField;
 use Google\Protobuf\Internal\MapField;
 use Google\Protobuf\Internal\MapField;
 
 
+function camel2underscore($input) {
+    preg_match_all(
+        '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!',
+        $input,
+        $matches);
+    $ret = $matches[0];
+    foreach ($ret as &$match) {
+        $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
+    }
+    return implode('_', $ret);
+}
+
 class GPBUtil
 class GPBUtil
 {
 {
     const NANOS_PER_MILLISECOND = 1000000;
     const NANOS_PER_MILLISECOND = 1000000;
     const NANOS_PER_MICROSECOND = 1000;
     const NANOS_PER_MICROSECOND = 1000;
+    const TYPE_URL_PREFIX = 'type.googleapis.com/';
 
 
     public static function divideInt64ToInt32($value, &$high, &$low, $trim = false)
     public static function divideInt64ToInt32($value, &$high, &$low, $trim = false)
     {
     {
@@ -358,7 +373,7 @@ class GPBUtil
         }
         }
         return $result;
         return $result;
     }
     }
-    
+
     public static function parseTimestamp($timestamp)
     public static function parseTimestamp($timestamp)
     {
     {
         // prevent parsing timestamps containing with the non-existant year "0000"
         // prevent parsing timestamps containing with the non-existant year "0000"
@@ -370,7 +385,7 @@ class GPBUtil
         if (substr($timestamp, -1, 1) === "z") {
         if (substr($timestamp, -1, 1) === "z") {
             throw new \Exception("Timezone cannot be a lowercase z.");
             throw new \Exception("Timezone cannot be a lowercase z.");
         }
         }
-        
+
         $nanoseconds = 0;
         $nanoseconds = 0;
         $periodIndex = strpos($timestamp, ".");
         $periodIndex = strpos($timestamp, ".");
         if ($periodIndex !== false) {
         if ($periodIndex !== false) {
@@ -411,9 +426,15 @@ class GPBUtil
         $value->setNanos($nanoseconds);
         $value->setNanos($nanoseconds);
         return $value;
         return $value;
     }
     }
-    
+
     public static function formatTimestamp($value)
     public static function formatTimestamp($value)
     {
     {
+        if (bccomp($value->getSeconds(), "253402300800") != -1) {
+          throw new GPBDecodeException("Duration number too large.");
+        }
+        if (bccomp($value->getSeconds(), "-62135596801") != 1) {
+          throw new GPBDecodeException("Duration number too small.");
+        }
         $nanoseconds = static::getNanosecondsForTimestamp($value->getNanos());
         $nanoseconds = static::getNanosecondsForTimestamp($value->getNanos());
         if (!empty($nanoseconds)) {
         if (!empty($nanoseconds)) {
             $nanoseconds = ".".$nanoseconds;
             $nanoseconds = ".".$nanoseconds;
@@ -422,6 +443,93 @@ class GPBUtil
         return $date->format("Y-m-d\TH:i:s".$nanoseconds."\Z");
         return $date->format("Y-m-d\TH:i:s".$nanoseconds."\Z");
     }
     }
 
 
+    public static function parseDuration($value)
+    {
+        if (strlen($value) < 2 || substr($value, -1) !== "s") {
+          throw new GPBDecodeException("Missing s after duration string");
+        }
+        $number = substr($value, 0, -1);
+        if (bccomp($number, "315576000001") != -1) {
+          throw new GPBDecodeException("Duration number too large.");
+        }
+        if (bccomp($number, "-315576000001") != 1) {
+          throw new GPBDecodeException("Duration number too small.");
+        }
+        $pos = strrpos($number, ".");
+        if ($pos !== false) {
+            $seconds = substr($number, 0, $pos);
+            if (bccomp($seconds, 0) < 0) {
+                $nanos = bcmul("0" . substr($number, $pos), -1000000000);
+            } else {
+                $nanos = bcmul("0" . substr($number, $pos), 1000000000);
+            }
+        } else {
+            $seconds = $number;
+            $nanos = 0;
+        }
+        $duration = new Duration();
+        $duration->setSeconds($seconds);
+        $duration->setNanos($nanos);
+        return $duration;
+    }
+
+    public static function formatDuration($value)
+    {
+        if (bccomp($value->getSeconds(), "315576000001") != -1) {
+          throw new GPBDecodeException("Duration number too large.");
+        }
+        if (bccomp($value->getSeconds(), "-315576000001") != 1) {
+          throw new GPBDecodeException("Duration number too small.");
+        }
+        return strval(bcadd($value->getSeconds(),
+                      $value->getNanos() / 1000000000.0, 9));
+    }
+
+
+
+    public static function parseFieldMask($paths_string)
+    {
+        $path_strings = explode(",", $paths_string);
+        $field_mask = new FieldMask();
+        $paths = $field_mask->getPaths();
+        foreach($path_strings as &$path_string) {
+            $field_strings = explode(".", $path_string);
+            foreach($field_strings as &$field_string) {
+                $field_string = camel2underscore($field_string);
+            }
+            $path_string = implode(".", $field_strings);
+            $paths[] = $path_string;
+        }
+        return $field_mask;
+    }
+
+    public static function formatFieldMask($field_mask)
+    {
+        $converted_paths = [];
+        foreach($field_mask->getPaths() as $path) {
+            $fields = explode('.', $path);
+            $converted_path = [];
+            foreach ($fields as $field) {
+                $segments = explode('_', $field);
+                $start = true;
+                $converted_segments = "";
+                foreach($segments as $segment) {
+                  if (!$start) {
+                    $converted = ucfirst($segment);
+                  } else {
+                    $converted = $segment;
+                    $start = false;
+                  }
+                  $converted_segments .= $converted;
+                }
+                $converted_path []= $converted_segments;
+            }
+            $converted_path = implode(".", $converted_path);
+            $converted_paths []= $converted_path;
+        }
+        return implode(",", $converted_paths);
+    }
+
     public static function getNanosecondsForTimestamp($nanoseconds)
     public static function getNanosecondsForTimestamp($nanoseconds)
     {
     {
         if ($nanoseconds == 0) {
         if ($nanoseconds == 0) {
@@ -435,4 +543,29 @@ class GPBUtil
         }
         }
         return sprintf('%09d', $nanoseconds);
         return sprintf('%09d', $nanoseconds);
     }
     }
+
+    public static function hasSpecialJsonMapping($msg)
+    {
+        return is_a($msg, 'Google\Protobuf\Any')         ||
+               is_a($msg, "Google\Protobuf\ListValue")   ||
+               is_a($msg, "Google\Protobuf\Struct")      ||
+               is_a($msg, "Google\Protobuf\Value")       ||
+               is_a($msg, "Google\Protobuf\Duration")    ||
+               is_a($msg, "Google\Protobuf\Timestamp")   ||
+               is_a($msg, "Google\Protobuf\FieldMask")   ||
+               static::hasJsonValue($msg);
+    }
+
+    public static function hasJsonValue($msg)
+    {
+        return is_a($msg, "Google\Protobuf\DoubleValue") ||
+               is_a($msg, "Google\Protobuf\FloatValue")  ||
+               is_a($msg, "Google\Protobuf\Int64Value")  ||
+               is_a($msg, "Google\Protobuf\UInt64Value") ||
+               is_a($msg, "Google\Protobuf\Int32Value")  ||
+               is_a($msg, "Google\Protobuf\UInt32Value") ||
+               is_a($msg, "Google\Protobuf\BoolValue")   ||
+               is_a($msg, "Google\Protobuf\StringValue") ||
+               is_a($msg, "Google\Protobuf\BytesValue");
+    }
 }
 }

+ 260 - 26
php/src/Google/Protobuf/Internal/Message.php

@@ -44,6 +44,10 @@ use Google\Protobuf\Internal\GPBType;
 use Google\Protobuf\Internal\GPBWire;
 use Google\Protobuf\Internal\GPBWire;
 use Google\Protobuf\Internal\MapEntry;
 use Google\Protobuf\Internal\MapEntry;
 use Google\Protobuf\Internal\RepeatedField;
 use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\ListValue;
+use Google\Protobuf\Value;
+use Google\Protobuf\Struct;
+use Google\Protobuf\NullValue;
 
 
 /**
 /**
  * Parent class of all proto messages. Users should not instantiate this class
  * Parent class of all proto messages. Users should not instantiate this class
@@ -701,16 +705,22 @@ class Message
         $field,
         $field,
         $is_map_key = false)
         $is_map_key = false)
     {
     {
-        if (is_null($value)) {
-            return $this->defaultValue($field);
-        }
         switch ($field->getType()) {
         switch ($field->getType()) {
             case GPBType::MESSAGE:
             case GPBType::MESSAGE:
                 $klass = $field->getMessageType()->getClass();
                 $klass = $field->getMessageType()->getClass();
                 $submsg = new $klass;
                 $submsg = new $klass;
 
 
-                if ($field->isTimestamp()) {
-                    if (!is_string($value)) {
+                if (is_a($submsg, "Google\Protobuf\Duration")) {
+                    if (is_null($value)) {
+                        return $this->defaultValue($field);
+                    } else if (!is_string($value)) {
+                        throw new GPBDecodeException("Expect string.");
+                    }
+                    return GPBUtil::parseDuration($value);
+                } else if ($field->isTimestamp()) {
+                    if (is_null($value)) {
+                        return $this->defaultValue($field);
+                    } else if (!is_string($value)) {
                         throw new GPBDecodeException("Expect string.");
                         throw new GPBDecodeException("Expect string.");
                     }
                     }
                     try {
                     try {
@@ -721,16 +731,31 @@ class Message
 
 
                     $submsg->setSeconds($timestamp->getSeconds());
                     $submsg->setSeconds($timestamp->getSeconds());
                     $submsg->setNanos($timestamp->getNanos());
                     $submsg->setNanos($timestamp->getNanos());
-                } else if ($klass !== "Google\Protobuf\Any") {
-                    if (!is_object($value) && !is_array($value)) {
+                } else if (is_a($submsg, "Google\Protobuf\FieldMask")) {
+                    if (is_null($value)) {
+                        return $this->defaultValue($field);
+                    }
+                    try {
+                        return GPBUtil::parseFieldMask($value);
+                    } catch (\Exception $e) {
+                        throw new GPBDecodeException("Invalid FieldMask: ".$e->getMessage());
+                    }
+                } else {
+                    if (is_null($value) &&
+                        !is_a($submsg, "Google\Protobuf\Value")) {
+                        return $this->defaultValue($field);
+                    }
+                    if (GPBUtil::hasSpecialJsonMapping($submsg)) {
+                    } elseif (!is_object($value) && !is_array($value)) {
                         throw new GPBDecodeException("Expect message.");
                         throw new GPBDecodeException("Expect message.");
                     }
                     }
-
                     $submsg->mergeFromJsonArray($value);
                     $submsg->mergeFromJsonArray($value);
                 }
                 }
                 return $submsg;
                 return $submsg;
             case GPBType::ENUM:
             case GPBType::ENUM:
-                if (is_integer($value)) {
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                } else if (is_integer($value)) {
                     return $value;
                     return $value;
                 } else {
                 } else {
                     $enum_value =
                     $enum_value =
@@ -740,11 +765,17 @@ class Message
                     return $enum_value->getNumber();
                     return $enum_value->getNumber();
                 }
                 }
             case GPBType::STRING:
             case GPBType::STRING:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if (!is_string($value)) {
                 if (!is_string($value)) {
                     throw new GPBDecodeException("Expect string");
                     throw new GPBDecodeException("Expect string");
                 }
                 }
                 return $value;
                 return $value;
             case GPBType::BYTES:
             case GPBType::BYTES:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if (!is_string($value)) {
                 if (!is_string($value)) {
                     throw new GPBDecodeException("Expect string");
                     throw new GPBDecodeException("Expect string");
                 }
                 }
@@ -755,6 +786,9 @@ class Message
                 }
                 }
                 return $proto_value;
                 return $proto_value;
             case GPBType::BOOL:
             case GPBType::BOOL:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if ($is_map_key) {
                 if ($is_map_key) {
                     if ($value === "true") {
                     if ($value === "true") {
                         return true;
                         return true;
@@ -771,6 +805,9 @@ class Message
                 }
                 }
                 return $value;
                 return $value;
             case GPBType::FLOAT:
             case GPBType::FLOAT:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if ($value === "Infinity") {
                 if ($value === "Infinity") {
                     return INF;
                     return INF;
                 }
                 }
@@ -782,6 +819,9 @@ class Message
                 }
                 }
                 return $value;
                 return $value;
             case GPBType::DOUBLE:
             case GPBType::DOUBLE:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if ($value === "Infinity") {
                 if ($value === "Infinity") {
                     return INF;
                     return INF;
                 }
                 }
@@ -793,6 +833,9 @@ class Message
                 }
                 }
                 return $value;
                 return $value;
             case GPBType::INT32:
             case GPBType::INT32:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if (!is_numeric($value)) {
                 if (!is_numeric($value)) {
                    throw new GPBDecodeException(
                    throw new GPBDecodeException(
                        "Invalid data type for int32 field");
                        "Invalid data type for int32 field");
@@ -807,6 +850,9 @@ class Message
                 }
                 }
                 return $value;
                 return $value;
             case GPBType::UINT32:
             case GPBType::UINT32:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if (!is_numeric($value)) {
                 if (!is_numeric($value)) {
                    throw new GPBDecodeException(
                    throw new GPBDecodeException(
                        "Invalid data type for uint32 field");
                        "Invalid data type for uint32 field");
@@ -817,6 +863,9 @@ class Message
                 }
                 }
                 return $value;
                 return $value;
             case GPBType::INT64:
             case GPBType::INT64:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if (!is_numeric($value)) {
                 if (!is_numeric($value)) {
                    throw new GPBDecodeException(
                    throw new GPBDecodeException(
                        "Invalid data type for int64 field");
                        "Invalid data type for int64 field");
@@ -831,6 +880,9 @@ class Message
                 }
                 }
                 return $value;
                 return $value;
             case GPBType::UINT64:
             case GPBType::UINT64:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 if (!is_numeric($value)) {
                 if (!is_numeric($value)) {
                    throw new GPBDecodeException(
                    throw new GPBDecodeException(
                        "Invalid data type for int64 field");
                        "Invalid data type for int64 field");
@@ -844,14 +896,107 @@ class Message
                 }
                 }
                 return $value;
                 return $value;
             case GPBType::FIXED64:
             case GPBType::FIXED64:
+                if (is_null($value)) {
+                    return $this->defaultValue($field);
+                }
                 return $value;
                 return $value;
             default:
             default:
                 return $value;
                 return $value;
         }
         }
     }
     }
 
 
-    private function mergeFromJsonArray($array)
+    protected function mergeFromJsonArray($array)
     {
     {
+        if (is_a($this, "Google\Protobuf\Any")) {
+            $this->clear();
+            $this->setTypeUrl($array["@type"]);
+            $msg = $this->unpack();
+            if (GPBUtil::hasSpecialJsonMapping($msg)) {
+                $msg->mergeFromJsonArray($array["value"]);
+            } else {
+                unset($array["@type"]);
+                $msg->mergeFromJsonArray($array);
+            }
+            $this->setValue($msg->serializeToString());
+            return;
+        }
+        if (is_a($this, "Google\Protobuf\DoubleValue") ||
+            is_a($this, "Google\Protobuf\FloatValue")  ||
+            is_a($this, "Google\Protobuf\Int64Value")  ||
+            is_a($this, "Google\Protobuf\UInt64Value") ||
+            is_a($this, "Google\Protobuf\Int32Value")  ||
+            is_a($this, "Google\Protobuf\UInt32Value") ||
+            is_a($this, "Google\Protobuf\BoolValue")   ||
+            is_a($this, "Google\Protobuf\StringValue")) {
+            $this->setValue($array);
+            return;
+        }
+        if (is_a($this, "Google\Protobuf\BytesValue")) {
+            $this->setValue(base64_decode($array));
+            return;
+        }
+        if (is_a($this, "Google\Protobuf\Duration")) {
+            $this->mergeFrom(GPBUtil::parseDuration($array));
+            return;
+        }
+        if (is_a($this, "Google\Protobuf\FieldMask")) {
+            $this->mergeFrom(GPBUtil::parseFieldMask($array));
+            return;
+        }
+        if (is_a($this, "Google\Protobuf\Timestamp")) {
+            $this->mergeFrom(GPBUtil::parseTimestamp($array));
+            return;
+        }
+        if (is_a($this, "Google\Protobuf\Struct")) {
+            $fields = $this->getFields();
+            foreach($array as $key => $value) {
+                $v = new Value();
+                $v->mergeFromJsonArray($value);
+                $fields[$key] = $v;
+            }
+        }
+        if (is_a($this, "Google\Protobuf\Value")) {
+            if (is_bool($array)) {
+                $this->setBoolValue($array);
+            } elseif (is_string($array)) {
+                $this->setStringValue($array);
+            } elseif (is_null($array)) {
+                $this->setNullValue(0);
+            } elseif (is_double($array) || is_integer($array)) {
+                $this->setNumberValue($array);
+            } elseif (is_array($array)) {
+                if (array_values($array) !== $array) {
+                    // Associative array
+                    $struct_value = $this->getStructValue();
+                    if (is_null($struct_value)) {
+                        $struct_value = new Struct();
+                        $this->setStructValue($struct_value);
+                    }
+                    foreach ($array as $key => $v) {
+                        $value = new Value();
+                        $value->mergeFromJsonArray($v);
+                        $values = $struct_value->getFields();
+                        $values[$key]= $value;
+                    }
+                } else {
+                    // Array
+                    $list_value = $this->getListValue();
+                    if (is_null($list_value)) {
+                        $list_value = new ListValue();
+                        $this->setListValue($list_value);
+                    }
+                    foreach ($array as $v) {
+                        $value = new Value();
+                        $value->mergeFromJsonArray($v);
+                        $values = $list_value->getValues();
+                        $values[]= $value;
+                    }
+                }
+            } else {
+                throw new GPBDecodeException("Invalid type for Value.");
+            }
+            return;
+        }
         foreach ($array as $key => $value) {
         foreach ($array as $key => $value) {
             $field = $this->desc->getFieldByJsonName($key);
             $field = $this->desc->getFieldByJsonName($key);
             if (is_null($field)) {
             if (is_null($field)) {
@@ -1037,7 +1182,8 @@ class Message
     {
     {
         $getter = $field->getGetter();
         $getter = $field->getGetter();
         $values = $this->$getter();
         $values = $this->$getter();
-        return GPBJsonWire::serializeFieldToStream($values, $field, $output);
+        return GPBJsonWire::serializeFieldToStream(
+            $values, $field, $output, !GPBUtil::hasSpecialJsonMapping($this));
     }
     }
 
 
     /**
     /**
@@ -1060,16 +1206,57 @@ class Message
      */
      */
     public function serializeToJsonStream(&$output)
     public function serializeToJsonStream(&$output)
     {
     {
-        if (get_class($this) === 'Google\Protobuf\Timestamp') {
+        if (is_a($this, 'Google\Protobuf\Any')) {
+            $output->writeRaw("{", 1);
+            $type_field = $this->desc->getFieldByNumber(1);
+            $value_msg = $this->unpack();
+
+            // Serialize type url.
+            $output->writeRaw("\"@type\":", 8);
+            $output->writeRaw("\"", 1);
+            $output->writeRaw($this->getTypeUrl(), strlen($this->getTypeUrl()));
+            $output->writeRaw("\"", 1);
+
+            // Serialize value
+            if (GPBUtil::hasSpecialJsonMapping($value_msg)) {
+                $output->writeRaw(",\"value\":", 9);
+                $value_msg->serializeToJsonStream($output);
+            } else {
+                $value_fields = $value_msg->desc->getField();
+                foreach ($value_fields as $field) {
+                    if ($value_msg->existField($field)) {
+                        $output->writeRaw(",", 1);
+                        if (!$value_msg->serializeFieldToJsonStream($output, $field)) {
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            $output->writeRaw("}", 1);
+        } elseif (is_a($this, 'Google\Protobuf\FieldMask')) {
+            $field_mask = GPBUtil::formatFieldMask($this);
+            $output->writeRaw("\"", 1);
+            $output->writeRaw($field_mask, strlen($field_mask));
+            $output->writeRaw("\"", 1);
+        } elseif (is_a($this, 'Google\Protobuf\Duration')) {
+            $duration = GPBUtil::formatDuration($this) . "s";
+            $output->writeRaw("\"", 1);
+            $output->writeRaw($duration, strlen($duration));
+            $output->writeRaw("\"", 1);
+        } elseif (get_class($this) === 'Google\Protobuf\Timestamp') {
             $timestamp = GPBUtil::formatTimestamp($this);
             $timestamp = GPBUtil::formatTimestamp($this);
             $timestamp = json_encode($timestamp);
             $timestamp = json_encode($timestamp);
             $output->writeRaw($timestamp, strlen($timestamp));
             $output->writeRaw($timestamp, strlen($timestamp));
         } else {
         } else {
-            $output->writeRaw("{", 1);
+            if (!GPBUtil::hasSpecialJsonMapping($this)) {
+                $output->writeRaw("{", 1);
+            }
             $fields = $this->desc->getField();
             $fields = $this->desc->getField();
             $first = true;
             $first = true;
             foreach ($fields as $field) {
             foreach ($fields as $field) {
-                if ($this->existField($field)) {
+                if ($this->existField($field) ||
+                    GPBUtil::hasJsonValue($this)) {
                     if ($first) {
                     if ($first) {
                         $first = false;
                         $first = false;
                     } else {
                     } else {
@@ -1080,7 +1267,9 @@ class Message
                     }
                     }
                 }
                 }
             }
             }
-            $output->writeRaw("}", 1);
+            if (!GPBUtil::hasSpecialJsonMapping($this)) {
+                $output->writeRaw("}", 1);
+            }
         }
         }
         return true;
         return true;
     }
     }
@@ -1263,6 +1452,10 @@ class Message
                 break;
                 break;
             case GPBType::ENUM:
             case GPBType::ENUM:
                 $enum_desc = $field->getEnumType();
                 $enum_desc = $field->getEnumType();
+                if ($enum_desc->getClass() === "Google\Protobuf\NullValue") {
+                    $size += 4;
+                    break;
+                }
                 $enum_value_desc = $enum_desc->getValueByNumber($value);
                 $enum_value_desc = $enum_desc->getValueByNumber($value);
                 if (!is_null($enum_value_desc)) {
                 if (!is_null($enum_value_desc)) {
                     $size += 2;  // size for ""
                     $size += 2;  // size for ""
@@ -1284,6 +1477,12 @@ class Message
                 $size += strlen($value);
                 $size += strlen($value);
                 break;
                 break;
             case GPBType::BYTES:
             case GPBType::BYTES:
+                # if (is_a($this, "Google\Protobuf\BytesValue")) {
+                #     $size += strlen(json_encode($value));
+                # } else {
+                #     $size += strlen(base64_encode($value));
+                #     $size += 2;  // size for \"\"
+                # }
                 $size += strlen(base64_encode($value));
                 $size += strlen(base64_encode($value));
                 $size += 2;  // size for \"\"
                 $size += 2;  // size for \"\"
                 break;
                 break;
@@ -1375,8 +1574,11 @@ class Message
             $values = $this->$getter();
             $values = $this->$getter();
             $count = count($values);
             $count = count($values);
             if ($count !== 0) {
             if ($count !== 0) {
-                $size += 5;                              // size for "\"\":{}".
-                $size += strlen($field->getJsonName());  // size for field name
+                if (!GPBUtil::hasSpecialJsonMapping($this)) {
+                    $size += 3;                              // size for "\"\":".
+                    $size += strlen($field->getJsonName());  // size for field name
+                }
+                $size += 2;  // size for "{}".
                 $size += $count - 1;                     // size for commas
                 $size += $count - 1;                     // size for commas
                 $getter = $field->getGetter();
                 $getter = $field->getGetter();
                 $map_entry = $field->getMessageType();
                 $map_entry = $field->getMessageType();
@@ -1408,17 +1610,22 @@ class Message
             $values = $this->$getter();
             $values = $this->$getter();
             $count = count($values);
             $count = count($values);
             if ($count !== 0) {
             if ($count !== 0) {
-                $size += 5;                              // size for "\"\":[]".
-                $size += strlen($field->getJsonName());  // size for field name
+                if (!GPBUtil::hasSpecialJsonMapping($this)) {
+                    $size += 3;                              // size for "\"\":".
+                    $size += strlen($field->getJsonName());  // size for field name
+                }
+                $size += 2;  // size for "[]".
                 $size += $count - 1;                     // size for commas
                 $size += $count - 1;                     // size for commas
                 $getter = $field->getGetter();
                 $getter = $field->getGetter();
                 foreach ($values as $value) {
                 foreach ($values as $value) {
                     $size += $this->fieldDataOnlyJsonByteSize($field, $value);
                     $size += $this->fieldDataOnlyJsonByteSize($field, $value);
                 }
                 }
             }
             }
-        } elseif ($this->existField($field)) {
-            $size += 3;                              // size for "\"\":".
-            $size += strlen($field->getJsonName());  // size for field name
+        } elseif ($this->existField($field) || GPBUtil::hasJsonValue($this)) {
+            if (!GPBUtil::hasSpecialJsonMapping($this)) {
+                $size += 3;                              // size for "\"\":".
+                $size += strlen($field->getJsonName());  // size for field name
+            }
             $getter = $field->getGetter();
             $getter = $field->getGetter();
             $value = $this->$getter();
             $value = $this->$getter();
             $size += $this->fieldDataOnlyJsonByteSize($field, $value);
             $size += $this->fieldDataOnlyJsonByteSize($field, $value);
@@ -1473,14 +1680,41 @@ class Message
     public function jsonByteSize()
     public function jsonByteSize()
     {
     {
         $size = 0;
         $size = 0;
-        if (get_class($this) === 'Google\Protobuf\Timestamp') {
+        if (is_a($this, 'Google\Protobuf\Any')) {
+            // Size for "{}".
+            $size += 2;
+
+            // Size for "\"@type\":".
+            $size += 8;
+
+            // Size for url. +2 for "" /.
+            $size += strlen($this->getTypeUrl()) + 2;
+
+            $value_msg = $this->unpack();
+            if (GPBUtil::hasSpecialJsonMapping($value_msg)) {
+                // Size for "\",value\":".
+                $size += 9;
+                $size += $value_msg->jsonByteSize();
+            } else {
+                // Size for value. +1 for comma, -2 for "{}".
+                $size += $value_msg->jsonByteSize() -1;
+            }
+        } elseif (get_class($this) === 'Google\Protobuf\FieldMask') {
+            $field_mask = GPBUtil::formatFieldMask($this);
+            $size += strlen($field_mask) + 2;  // 2 for ""
+        } elseif (get_class($this) === 'Google\Protobuf\Duration') {
+            $duration = GPBUtil::formatDuration($this) . "s";
+            $size += strlen($duration) + 2;  // 2 for ""
+        } elseif (get_class($this) === 'Google\Protobuf\Timestamp') {
             $timestamp = GPBUtil::formatTimestamp($this);
             $timestamp = GPBUtil::formatTimestamp($this);
             $timestamp = json_encode($timestamp);
             $timestamp = json_encode($timestamp);
             $size += strlen($timestamp);
             $size += strlen($timestamp);
         } else {
         } else {
-            // Size for "{}".
-            $size += 2;
-            
+            if (!GPBUtil::hasSpecialJsonMapping($this)) {
+                // Size for "{}".
+                $size += 2;
+            }
+
             $fields = $this->desc->getField();
             $fields = $this->desc->getField();
             $count = 0;
             $count = 0;
             foreach ($fields as $field) {
             foreach ($fields as $field) {

+ 2 - 1
tests.sh

@@ -363,7 +363,8 @@ generate_php_test_proto() {
     proto/test_service_namespace.proto         \
     proto/test_service_namespace.proto         \
     proto/test_descriptors.proto
     proto/test_descriptors.proto
   pushd ../../src
   pushd ../../src
-  ./protoc --php_out=../php/tests/generated -I../php/tests -I. ../php/tests/proto/test_import_descriptor_proto.proto
+  ./protoc --php_out=../php/tests/generated -I../php/tests -I. \
+    ../php/tests/proto/test_import_descriptor_proto.proto
   popd
   popd
   popd
   popd
 }
 }