Browse Source

PHP: Add support for primitive types in setters (#5126)

* Add support for primitive types in setters

* Update to address PR feedback

* Add tests and fixes for repeated fields

* Remove repeated field code, add getters

* Cleanup, test getters and oneofs

* Move boxing logic into separate class

* Add tests for wrapper type constructor args

* Update to add new setXXXValue methods

* Fix tests for invalid values

* Fix c extension for wrapper accessors

* Fix the bug that well known types didn't call Message_construct

* Address PR comments

* Refactoring init message with array logic

* Add include path to protoc

* Add missing TSRM_LS defintion

* Fix TSRM_LS

* Fix dist check
michaelbausor 7 years ago
parent
commit
6a51c03823

+ 4 - 0
.gitignore

@@ -194,3 +194,7 @@ cmake/cmake-build-debug/
 
 
 # Visual Studio 2017
 # Visual Studio 2017
 .vs
 .vs
+
+# IntelliJ
+.idea
+*.iml

+ 3 - 1
Makefile.am

@@ -749,11 +749,13 @@ php_EXTRA_DIST=                                                       \
   php/tests/proto/test_reserved_message_upper.proto                   \
   php/tests/proto/test_reserved_message_upper.proto                   \
   php/tests/proto/test_service.proto                                  \
   php/tests/proto/test_service.proto                                  \
   php/tests/proto/test_service_namespace.proto                        \
   php/tests/proto/test_service_namespace.proto                        \
+  php/tests/proto/test_wrapper_type_setters.proto                     \
   php/tests/test.sh                                                   \
   php/tests/test.sh                                                   \
   php/tests/test_base.php                                             \
   php/tests/test_base.php                                             \
   php/tests/test_util.php                                             \
   php/tests/test_util.php                                             \
   php/tests/undefined_test.php                                        \
   php/tests/undefined_test.php                                        \
-  php/tests/well_known_test.php
+  php/tests/well_known_test.php                                       \
+  php/tests/wrapper_type_setters_test.php
 
 
 python_EXTRA_DIST=                                                           \
 python_EXTRA_DIST=                                                           \
   python/MANIFEST.in                                                         \
   python/MANIFEST.in                                                         \

+ 1 - 1
php/composer.json

@@ -23,6 +23,6 @@
     }
     }
   },
   },
   "scripts": {
   "scripts": {
-    "test": "(cd tests && rm -rf generated && mkdir -p generated && ../../src/protoc --php_out=generated proto/empty/echo.proto proto/test.proto proto/test_include.proto proto/test_no_namespace.proto proto/test_prefix.proto proto/test_php_namespace.proto proto/test_empty_php_namespace.proto proto/test_reserved_enum_lower.proto proto/test_reserved_enum_upper.proto proto/test_reserved_enum_value_lower.proto proto/test_reserved_enum_value_upper.proto proto/test_reserved_message_lower.proto proto/test_reserved_message_upper.proto proto/test_service.proto proto/test_service_namespace.proto proto/test_descriptors.proto) && (cd ../src && ./protoc --php_out=../php/tests/generated -I../php/tests -I. ../php/tests/proto/test_import_descriptor_proto.proto) && vendor/bin/phpunit"
+    "test": "(cd tests && rm -rf generated && mkdir -p generated && ../../src/protoc --php_out=generated -I../../src -I. proto/empty/echo.proto proto/test.proto proto/test_include.proto proto/test_no_namespace.proto proto/test_prefix.proto proto/test_php_namespace.proto proto/test_empty_php_namespace.proto proto/test_reserved_enum_lower.proto proto/test_reserved_enum_upper.proto proto/test_reserved_enum_value_lower.proto proto/test_reserved_enum_value_upper.proto proto/test_reserved_message_lower.proto proto/test_reserved_message_upper.proto proto/test_service.proto proto/test_service_namespace.proto proto/test_wrapper_type_setters.proto proto/test_descriptors.proto) && (cd ../src && ./protoc --php_out=../php/tests/generated -I../php/tests -I. ../php/tests/proto/test_import_descriptor_proto.proto) && vendor/bin/phpunit"
   }
   }
 }
 }

+ 55 - 41
php/ext/google/protobuf/message.c

@@ -254,6 +254,16 @@ void custom_data_init(const zend_class_entry* ce,
               &intern->std PHP_PROTO_TSRMLS_CC);
               &intern->std PHP_PROTO_TSRMLS_CC);
 }
 }
 
 
+#define INIT_MESSAGE_WITH_ARRAY                                    \
+  {                                                                \
+    zval* array_wrapper = NULL;                                    \
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,           \
+                              "|a!", &array_wrapper) == FAILURE) { \
+      return;                                                      \
+    }                                                              \
+    Message_construct(getThis(), array_wrapper);                   \
+  }
+
 void build_class_from_descriptor(
 void build_class_from_descriptor(
     PHP_PROTO_HASHTABLE_VALUE php_descriptor TSRMLS_DC) {
     PHP_PROTO_HASHTABLE_VALUE php_descriptor TSRMLS_DC) {
   Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, php_descriptor);
   Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, php_descriptor);
@@ -361,15 +371,26 @@ void Message_construct(zval* msg, zval* array_wrapper) {
       zval* submsg = CACHED_PTR_TO_ZVAL_PTR(cached);
       zval* submsg = CACHED_PTR_TO_ZVAL_PTR(cached);
       ZVAL_OBJ(submsg, desc->klass->create_object(desc->klass TSRMLS_CC));
       ZVAL_OBJ(submsg, desc->klass->create_object(desc->klass TSRMLS_CC));
       Message_construct(submsg, NULL);
       Message_construct(submsg, NULL);
-      MessageHeader* from = UNBOX(MessageHeader,
-                                  CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)value));
       MessageHeader* to = UNBOX(MessageHeader, submsg);
       MessageHeader* to = UNBOX(MessageHeader, submsg);
-      if(from->descriptor != to->descriptor) {
-        zend_error(E_USER_ERROR, "Cannot merge messages with different class.");
-        return;
+      const upb_filedef *file = upb_def_file(upb_msgdef_upcast(submsgdef));
+      if (!strcmp(upb_filedef_name(file), "google/protobuf/wrappers.proto") &&
+          Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)value)) != IS_OBJECT) {
+        const upb_fielddef *value_field = upb_msgdef_itof(submsgdef, 1);
+        layout_set(to->descriptor->layout, to,
+                   value_field, CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)value)
+                   TSRMLS_CC);
+      } else {
+        MessageHeader* from =
+            UNBOX(MessageHeader,
+                  CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)value));
+        if(from->descriptor != to->descriptor) {
+          zend_error(E_USER_ERROR,
+                     "Cannot merge messages with different class.");
+          return;
+        }
+
+        layout_merge(from->descriptor->layout, from, to TSRMLS_CC);
       }
       }
-
-      layout_merge(from->descriptor->layout, from, to TSRMLS_CC);
     } else {
     } else {
       message_set_property_internal(msg, &key,
       message_set_property_internal(msg, &key,
           CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)value) TSRMLS_CC);
           CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)value) TSRMLS_CC);
@@ -382,14 +403,7 @@ void Message_construct(zval* msg, zval* array_wrapper) {
 // modified. As a result, the first created instance will be a normal zend
 // modified. As a result, the first created instance will be a normal zend
 // object. Here, we manually modify it to our message in such a case.
 // object. Here, we manually modify it to our message in such a case.
 PHP_METHOD(Message, __construct) {
 PHP_METHOD(Message, __construct) {
-  // Init message with array
-  zval* array_wrapper = NULL;
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
-                            "|a!", &array_wrapper) == FAILURE) {
-    return;
-  }
-
-  Message_construct(getThis(), array_wrapper);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_METHOD(Message, clear) {
 PHP_METHOD(Message, clear) {
@@ -1024,7 +1038,7 @@ static void hex_to_binary(const char* hex, char** binary, int* binary_len) {
 PHP_METHOD(Any, __construct) {
 PHP_METHOD(Any, __construct) {
   init_file_any(TSRMLS_C);
   init_file_any(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(any_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Any, any, TypeUrl, "type_url")
 PHP_PROTO_FIELD_ACCESSORS(Any, any, TypeUrl, "type_url")
@@ -1193,7 +1207,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Duration, __construct) {
 PHP_METHOD(Duration, __construct) {
   init_file_duration(TSRMLS_C);
   init_file_duration(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(duration_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Duration, duration, Seconds, "seconds")
 PHP_PROTO_FIELD_ACCESSORS(Duration, duration, Seconds, "seconds")
@@ -1229,7 +1243,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Timestamp, __construct) {
 PHP_METHOD(Timestamp, __construct) {
   init_file_timestamp(TSRMLS_C);
   init_file_timestamp(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(timestamp_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Timestamp, timestamp, Seconds, "seconds")
 PHP_PROTO_FIELD_ACCESSORS(Timestamp, timestamp, Seconds, "seconds")
@@ -1432,7 +1446,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Api, __construct) {
 PHP_METHOD(Api, __construct) {
   init_file_api(TSRMLS_C);
   init_file_api(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(api_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Api, api, Name, "name")
 PHP_PROTO_FIELD_ACCESSORS(Api, api, Name, "name")
@@ -1467,7 +1481,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(BoolValue, __construct) {
 PHP_METHOD(BoolValue, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(bool_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(BoolValue, bool_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(BoolValue, bool_value, Value, "value")
@@ -1496,7 +1510,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(BytesValue, __construct) {
 PHP_METHOD(BytesValue, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(bytes_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(BytesValue, bytes_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(BytesValue, bytes_value, Value, "value")
@@ -1525,7 +1539,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(DoubleValue, __construct) {
 PHP_METHOD(DoubleValue, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(double_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(DoubleValue, double_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(DoubleValue, double_value, Value, "value")
@@ -1570,7 +1584,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Enum, __construct) {
 PHP_METHOD(Enum, __construct) {
   init_file_type(TSRMLS_C);
   init_file_type(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(enum_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Enum, enum, Name, "name")
 PHP_PROTO_FIELD_ACCESSORS(Enum, enum, Name, "name")
@@ -1611,7 +1625,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(EnumValue, __construct) {
 PHP_METHOD(EnumValue, __construct) {
   init_file_type(TSRMLS_C);
   init_file_type(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(enum_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(EnumValue, enum_value, Name, "name")
 PHP_PROTO_FIELD_ACCESSORS(EnumValue, enum_value, Name, "name")
@@ -1642,7 +1656,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(FieldMask, __construct) {
 PHP_METHOD(FieldMask, __construct) {
   init_file_field_mask(TSRMLS_C);
   init_file_field_mask(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(field_mask_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(FieldMask, field_mask, Paths, "paths")
 PHP_PROTO_FIELD_ACCESSORS(FieldMask, field_mask, Paths, "paths")
@@ -1707,7 +1721,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Field, __construct) {
 PHP_METHOD(Field, __construct) {
   init_file_type(TSRMLS_C);
   init_file_type(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(field_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Field, field, Kind, "kind")
 PHP_PROTO_FIELD_ACCESSORS(Field, field, Kind, "kind")
@@ -1745,7 +1759,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(FloatValue, __construct) {
 PHP_METHOD(FloatValue, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(float_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(FloatValue, float_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(FloatValue, float_value, Value, "value")
@@ -1770,7 +1784,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(GPBEmpty, __construct) {
 PHP_METHOD(GPBEmpty, __construct) {
   init_file_empty(TSRMLS_C);
   init_file_empty(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(empty_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 
 
@@ -1798,7 +1812,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Int32Value, __construct) {
 PHP_METHOD(Int32Value, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(int32_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Int32Value, int32_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(Int32Value, int32_value, Value, "value")
@@ -1827,7 +1841,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Int64Value, __construct) {
 PHP_METHOD(Int64Value, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(int64_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Int64Value, int64_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(Int64Value, int64_value, Value, "value")
@@ -1856,7 +1870,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(ListValue, __construct) {
 PHP_METHOD(ListValue, __construct) {
   init_file_struct(TSRMLS_C);
   init_file_struct(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(list_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(ListValue, list_value, Values, "values")
 PHP_PROTO_FIELD_ACCESSORS(ListValue, list_value, Values, "values")
@@ -1909,7 +1923,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Method, __construct) {
 PHP_METHOD(Method, __construct) {
   init_file_api(TSRMLS_C);
   init_file_api(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(method_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Method, method, Name, "name")
 PHP_PROTO_FIELD_ACCESSORS(Method, method, Name, "name")
@@ -1948,7 +1962,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Mixin, __construct) {
 PHP_METHOD(Mixin, __construct) {
   init_file_api(TSRMLS_C);
   init_file_api(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(mixin_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Mixin, mixin, Name, "name")
 PHP_PROTO_FIELD_ACCESSORS(Mixin, mixin, Name, "name")
@@ -1982,7 +1996,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Option, __construct) {
 PHP_METHOD(Option, __construct) {
   init_file_type(TSRMLS_C);
   init_file_type(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(option_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Option, option, Name, "name")
 PHP_PROTO_FIELD_ACCESSORS(Option, option, Name, "name")
@@ -2012,7 +2026,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(SourceContext, __construct) {
 PHP_METHOD(SourceContext, __construct) {
   init_file_source_context(TSRMLS_C);
   init_file_source_context(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(source_context_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(SourceContext, source_context, FileName, "file_name")
 PHP_PROTO_FIELD_ACCESSORS(SourceContext, source_context, FileName, "file_name")
@@ -2041,7 +2055,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(StringValue, __construct) {
 PHP_METHOD(StringValue, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(string_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(StringValue, string_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(StringValue, string_value, Value, "value")
@@ -2070,7 +2084,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Struct, __construct) {
 PHP_METHOD(Struct, __construct) {
   init_file_struct(TSRMLS_C);
   init_file_struct(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(struct_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Struct, struct, Fields, "fields")
 PHP_PROTO_FIELD_ACCESSORS(Struct, struct, Fields, "fields")
@@ -2119,7 +2133,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Type, __construct) {
 PHP_METHOD(Type, __construct) {
   init_file_type(TSRMLS_C);
   init_file_type(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(type_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(Type, type, Name, "name")
 PHP_PROTO_FIELD_ACCESSORS(Type, type, Name, "name")
@@ -2153,7 +2167,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(UInt32Value, __construct) {
 PHP_METHOD(UInt32Value, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(u_int32_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(UInt32Value, u_int32_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(UInt32Value, u_int32_value, Value, "value")
@@ -2182,7 +2196,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(UInt64Value, __construct) {
 PHP_METHOD(UInt64Value, __construct) {
   init_file_wrappers(TSRMLS_C);
   init_file_wrappers(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(u_int64_value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_FIELD_ACCESSORS(UInt64Value, u_int64_value, Value, "value")
 PHP_PROTO_FIELD_ACCESSORS(UInt64Value, u_int64_value, Value, "value")
@@ -2222,7 +2236,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
 PHP_METHOD(Value, __construct) {
 PHP_METHOD(Value, __construct) {
   init_file_struct(TSRMLS_C);
   init_file_struct(TSRMLS_C);
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
   MessageHeader* intern = UNBOX(MessageHeader, getThis());
-  custom_data_init(value_type, intern PHP_PROTO_TSRMLS_CC);
+  INIT_MESSAGE_WITH_ARRAY;
 }
 }
 
 
 PHP_PROTO_ONEOF_FIELD_ACCESSORS(Value, value, NullValue, "null_value")
 PHP_PROTO_ONEOF_FIELD_ACCESSORS(Value, value, NullValue, "null_value")

+ 59 - 27
php/ext/google/protobuf/type_check.c

@@ -222,7 +222,7 @@ process_double:
                                        type##_t* type##_value) {             \
                                        type##_t* type##_value) {             \
     int64_t lval;                                                            \
     int64_t lval;                                                            \
     double dval;                                                             \
     double dval;                                                             \
-                                                                             \
+    TSRMLS_FETCH();                                                          \
     switch (convert_numeric_string(val, len, &lval, &dval)) {                \
     switch (convert_numeric_string(val, len, &lval, &dval)) {                \
       case IS_DOUBLE: {                                                      \
       case IS_DOUBLE: {                                                      \
         return convert_double_to_##type(dval, type##_value);                 \
         return convert_double_to_##type(dval, type##_value);                 \
@@ -231,13 +231,15 @@ process_double:
         return convert_int64_to_##type(lval, type##_value);                  \
         return convert_int64_to_##type(lval, type##_value);                  \
       }                                                                      \
       }                                                                      \
       default:                                                               \
       default:                                                               \
-        zend_error(E_USER_ERROR,                                             \
-                   "Given string value cannot be converted to integer.");    \
+        zend_throw_exception(NULL,                                           \
+                   "Given string value cannot be converted to integer.",     \
+                   0 TSRMLS_CC);                                             \
         return false;                                                        \
         return false;                                                        \
     }                                                                        \
     }                                                                        \
   }                                                                          \
   }                                                                          \
                                                                              \
                                                                              \
   bool protobuf_convert_to_##type(zval* from, type##_t* to) {                \
   bool protobuf_convert_to_##type(zval* from, type##_t* to) {                \
+    TSRMLS_FETCH();                                                          \
     switch (Z_TYPE_P(from)) {                                                \
     switch (Z_TYPE_P(from)) {                                                \
       case IS_LONG: {                                                        \
       case IS_LONG: {                                                        \
         return convert_int64_to_##type(Z_LVAL_P(from), to);                  \
         return convert_int64_to_##type(Z_LVAL_P(from), to);                  \
@@ -250,8 +252,9 @@ process_double:
                                         to);                                 \
                                         to);                                 \
       }                                                                      \
       }                                                                      \
       default: {                                                             \
       default: {                                                             \
-        zend_error(E_USER_ERROR,                                             \
-                   "Given value cannot be converted to integer.");           \
+        zend_throw_exception(NULL,                                           \
+                   "Given value cannot be converted to integer.",            \
+                   0 TSRMLS_CC);                                             \
         return false;                                                        \
         return false;                                                        \
       }                                                                      \
       }                                                                      \
     }                                                                        \
     }                                                                        \
@@ -281,6 +284,7 @@ CONVERT_TO_INTEGER(uint64);
     int64_t lval;                                                           \
     int64_t lval;                                                           \
     double dval;                                                            \
     double dval;                                                            \
                                                                             \
                                                                             \
+    TSRMLS_FETCH();                                                         \
     switch (convert_numeric_string(val, len, &lval, &dval)) {               \
     switch (convert_numeric_string(val, len, &lval, &dval)) {               \
       case IS_DOUBLE: {                                                     \
       case IS_DOUBLE: {                                                     \
         *type##_value = (type)dval;                                         \
         *type##_value = (type)dval;                                         \
@@ -291,13 +295,15 @@ CONVERT_TO_INTEGER(uint64);
         return true;                                                        \
         return true;                                                        \
       }                                                                     \
       }                                                                     \
       default:                                                              \
       default:                                                              \
-        zend_error(E_USER_ERROR,                                            \
-                   "Given string value cannot be converted to integer.");   \
+        zend_throw_exception(NULL,                                          \
+                   "Given string value cannot be converted to integer.",    \
+                   0 TSRMLS_CC);                                            \
         return false;                                                       \
         return false;                                                       \
     }                                                                       \
     }                                                                       \
   }                                                                         \
   }                                                                         \
                                                                             \
                                                                             \
   bool protobuf_convert_to_##type(zval* from, type* to) {                   \
   bool protobuf_convert_to_##type(zval* from, type* to) {                   \
+    TSRMLS_FETCH();                                                         \
     switch (Z_TYPE_P(from)) {                                               \
     switch (Z_TYPE_P(from)) {                                               \
       case IS_LONG: {                                                       \
       case IS_LONG: {                                                       \
         return convert_int64_to_##type(Z_LVAL_P(from), to);                 \
         return convert_int64_to_##type(Z_LVAL_P(from), to);                 \
@@ -310,8 +316,9 @@ CONVERT_TO_INTEGER(uint64);
                                         to);                                \
                                         to);                                \
       }                                                                     \
       }                                                                     \
       default: {                                                            \
       default: {                                                            \
-        zend_error(E_USER_ERROR,                                            \
-                   "Given value cannot be converted to integer.");          \
+        zend_throw_exception(NULL,                                          \
+                   "Given value cannot be converted to integer.",           \
+                   0 TSRMLS_CC);                                            \
         return false;                                                       \
         return false;                                                       \
       }                                                                     \
       }                                                                     \
     }                                                                       \
     }                                                                       \
@@ -324,6 +331,7 @@ CONVERT_TO_FLOAT(double);
 #undef CONVERT_TO_FLOAT
 #undef CONVERT_TO_FLOAT
 
 
 bool protobuf_convert_to_bool(zval* from, int8_t* to) {
 bool protobuf_convert_to_bool(zval* from, int8_t* to) {
+  TSRMLS_FETCH();
   switch (Z_TYPE_P(from)) {
   switch (Z_TYPE_P(from)) {
 #if PHP_MAJOR_VERSION < 7
 #if PHP_MAJOR_VERSION < 7
     case IS_BOOL:
     case IS_BOOL:
@@ -354,7 +362,9 @@ bool protobuf_convert_to_bool(zval* from, int8_t* to) {
       }
       }
     } break;
     } break;
     default: {
     default: {
-      zend_error(E_USER_ERROR, "Given value cannot be converted to bool.");
+      zend_throw_exception(
+          NULL, "Given value cannot be converted to bool.",
+          0 TSRMLS_CC);
       return false;
       return false;
     }
     }
   }
   }
@@ -362,6 +372,7 @@ bool protobuf_convert_to_bool(zval* from, int8_t* to) {
 }
 }
 
 
 bool protobuf_convert_to_string(zval* from) {
 bool protobuf_convert_to_string(zval* from) {
+  TSRMLS_FETCH();
   switch (Z_TYPE_P(from)) {
   switch (Z_TYPE_P(from)) {
     case IS_STRING: {
     case IS_STRING: {
       return true;
       return true;
@@ -380,7 +391,9 @@ bool protobuf_convert_to_string(zval* from) {
       return true;
       return true;
     }
     }
     default:
     default:
-      zend_error(E_USER_ERROR, "Given value cannot be converted to string.");
+      zend_throw_exception(
+          NULL, "Given value cannot be converted to string.",
+          0 TSRMLS_CC);
       return false;
       return false;
   }
   }
 }
 }
@@ -421,8 +434,9 @@ PHP_METHOD(Util, checkMessage) {
     RETURN_NULL();
     RETURN_NULL();
   }
   }
   if (!instanceof_function(Z_OBJCE_P(val), klass TSRMLS_CC)) {
   if (!instanceof_function(Z_OBJCE_P(val), klass TSRMLS_CC)) {
-    zend_error(E_USER_ERROR, "Given value is not an instance of %s.",
-               klass->name);
+    zend_throw_exception(
+        NULL, "Given value is not an instance of %s.", klass->name,
+        0 TSRMLS_CC);
     return;
     return;
   }
   }
   RETURN_ZVAL(val, 1, 0);
   RETURN_ZVAL(val, 1, 0);
@@ -465,24 +479,32 @@ void check_repeated_field(const zend_class_entry* klass, PHP_PROTO_LONG type,
 
 
   } else if (Z_TYPE_P(val) == IS_OBJECT) {
   } else if (Z_TYPE_P(val) == IS_OBJECT) {
     if (!instanceof_function(Z_OBJCE_P(val), repeated_field_type TSRMLS_CC)) {
     if (!instanceof_function(Z_OBJCE_P(val), repeated_field_type TSRMLS_CC)) {
-      zend_error(E_USER_ERROR, "Given value is not an instance of %s.",
-                 repeated_field_type->name);
+      zend_throw_exception(
+          NULL, "Given value is not an instance of %s.",
+          repeated_field_type->name,
+          0 TSRMLS_CC);
       return;
       return;
     }
     }
     RepeatedField* intern = UNBOX(RepeatedField, val);
     RepeatedField* intern = UNBOX(RepeatedField, val);
     if (to_fieldtype(type) != intern->type) {
     if (to_fieldtype(type) != intern->type) {
-      zend_error(E_USER_ERROR, "Incorrect repeated field type.");
+      zend_throw_exception(
+          NULL, "Incorrect repeated field type.",
+          0 TSRMLS_CC);
       return;
       return;
     }
     }
     if (klass != NULL && intern->msg_ce != klass) {
     if (klass != NULL && intern->msg_ce != klass) {
-      zend_error(E_USER_ERROR,
-                 "Expect a repeated field of %s, but %s is given.", klass->name,
-                 intern->msg_ce->name);
+      zend_throw_exception(
+          NULL, "Expect a repeated field of %s, but %s is given.",
+          klass->name,
+          intern->msg_ce->name,
+          0 TSRMLS_CC);
       return;
       return;
     }
     }
     RETURN_ZVAL(val, 1, 0);
     RETURN_ZVAL(val, 1, 0);
   } else {
   } else {
-    zend_error(E_USER_ERROR, "Incorrect repeated field type.");
+    zend_throw_exception(
+        NULL, "Incorrect repeated field type.",
+        0 TSRMLS_CC);
     return;
     return;
   }
   }
 }
 }
@@ -538,27 +560,37 @@ void check_map_field(const zend_class_entry* klass, PHP_PROTO_LONG key_type,
     RETURN_ZVAL(CACHED_TO_ZVAL_PTR(map_field), 1, 1);
     RETURN_ZVAL(CACHED_TO_ZVAL_PTR(map_field), 1, 1);
   } else if (Z_TYPE_P(val) == IS_OBJECT) {
   } else if (Z_TYPE_P(val) == IS_OBJECT) {
     if (!instanceof_function(Z_OBJCE_P(val), map_field_type TSRMLS_CC)) {
     if (!instanceof_function(Z_OBJCE_P(val), map_field_type TSRMLS_CC)) {
-      zend_error(E_USER_ERROR, "Given value is not an instance of %s.",
-                 map_field_type->name);
+      zend_throw_exception(
+          NULL, "Given value is not an instance of %s.",
+          map_field_type->name,
+          0 TSRMLS_CC);
       return;
       return;
     }
     }
     Map* intern = UNBOX(Map, val);
     Map* intern = UNBOX(Map, val);
     if (to_fieldtype(key_type) != intern->key_type) {
     if (to_fieldtype(key_type) != intern->key_type) {
-      zend_error(E_USER_ERROR, "Incorrect map field key type.");
+      zend_throw_exception(
+          NULL, "Incorrect map field key type.",
+          0 TSRMLS_CC);
       return;
       return;
     }
     }
     if (to_fieldtype(value_type) != intern->value_type) {
     if (to_fieldtype(value_type) != intern->value_type) {
-      zend_error(E_USER_ERROR, "Incorrect map field value type.");
+      zend_throw_exception(
+          NULL, "Incorrect map field value type.",
+          0 TSRMLS_CC);
       return;
       return;
     }
     }
     if (klass != NULL && intern->msg_ce != klass) {
     if (klass != NULL && intern->msg_ce != klass) {
-      zend_error(E_USER_ERROR, "Expect a map field of %s, but %s is given.",
-                 klass->name, intern->msg_ce->name);
+      zend_throw_exception(
+          NULL, "Expect a map field of %s, but %s is given.",
+          klass->name, intern->msg_ce->name,
+          0 TSRMLS_CC);
       return;
       return;
     }
     }
     RETURN_ZVAL(val, 1, 0);
     RETURN_ZVAL(val, 1, 0);
   } else {
   } else {
-    zend_error(E_USER_ERROR, "Incorrect map field type.");
+      zend_throw_exception(
+          NULL, "Incorrect map field type.",
+          0 TSRMLS_CC);
     return;
     return;
   }
   }
 }
 }

+ 1 - 0
php/phpunit.xml

@@ -12,6 +12,7 @@
       <file>tests/well_known_test.php</file>
       <file>tests/well_known_test.php</file>
       <file>tests/descriptors_test.php</file>
       <file>tests/descriptors_test.php</file>
       <file>tests/generated_service_test.php</file>
       <file>tests/generated_service_test.php</file>
+      <file>tests/wrapper_type_setters_test.php</file>
     </testsuite>
     </testsuite>
   </testsuites>
   </testsuites>
 </phpunit>
 </phpunit>

+ 19 - 0
php/src/Google/Protobuf/Internal/FieldDescriptor.php

@@ -187,6 +187,25 @@ class FieldDescriptor
             $this->getMessageType()->getClass() === "Google\Protobuf\Timestamp";
             $this->getMessageType()->getClass() === "Google\Protobuf\Timestamp";
     }
     }
 
 
+    public function isWrapperType()
+    {
+        if ($this->getType() == GPBType::MESSAGE) {
+            $class = $this->getMessageType()->getClass();
+            return in_array($class, [
+                "Google\Protobuf\DoubleValue",
+                "Google\Protobuf\FloatValue",
+                "Google\Protobuf\Int64Value",
+                "Google\Protobuf\UInt64Value",
+                "Google\Protobuf\Int32Value",
+                "Google\Protobuf\UInt32Value",
+                "Google\Protobuf\BoolValue",
+                "Google\Protobuf\StringValue",
+                "Google\Protobuf\BytesValue",
+            ]);
+        }
+        return false;
+    }
+
     private static function isTypePackable($field_type)
     private static function isTypePackable($field_type)
     {
     {
         return ($field_type !== GPBType::STRING  &&
         return ($field_type !== GPBType::STRING  &&

+ 38 - 1
php/src/Google/Protobuf/Internal/Message.php

@@ -975,7 +975,7 @@ class Message
      *
      *
      * @param array $array An array containing message properties and values.
      * @param array $array An array containing message properties and values.
      * @return null.
      * @return null.
-     * @throws Exception Invalid data.
+     * @throws \Exception Invalid data.
      */
      */
     protected function mergeFromArray(array $array)
     protected function mergeFromArray(array $array)
     {
     {
@@ -987,10 +987,47 @@ class Message
                     'Invalid message property: ' . $key);
                     'Invalid message property: ' . $key);
             }
             }
             $setter = $field->getSetter();
             $setter = $field->getSetter();
+            if ($field->isWrapperType()) {
+                self::normalizeToMessageType($value, $field->getMessageType()->getClass());
+            }
             $this->$setter($value);
             $this->$setter($value);
         }
         }
     }
     }
 
 
+    /**
+     * Tries to normalize $value into a provided protobuf wrapper type $class.
+     * If $value is any type other than an object, we attempt to construct an
+     * instance of $class and assign $value to it using the setValue method
+     * shared by all wrapper types.
+     *
+     * @param mixed $value The value to normalize.
+     * @param string $class The expected wrapper class name
+     * @throws \Exception If $value cannot be converted to a wrapper type
+     */
+    private static function normalizeToMessageType(&$value, $class)
+    {
+        if (is_null($value) || is_object($value)) {
+            // This handles the case that $value is an instance of $class. We
+            // choose not to do any more strict checking here, relying on the
+            // existing type checking done by GPBUtil.
+            return;
+        } else {
+            // Try to instantiate $class and set the value
+            try {
+                $msg = new $class;
+                $msg->setValue($value);
+                $value = $msg;
+                return;
+            } catch (\Exception $exception) {
+                throw new \Exception(
+                    "Error normalizing value to type '$class': " . $exception->getMessage(),
+                    $exception->getCode(),
+                    $exception
+                );
+            }
+        }
+    }
+
     protected function mergeFromJsonArray($array)
     protected function mergeFromJsonArray($array)
     {
     {
         if (is_a($this, "Google\Protobuf\Any")) {
         if (is_a($this, "Google\Protobuf\Any")) {

+ 22 - 0
php/tests/proto/test_wrapper_type_setters.proto

@@ -0,0 +1,22 @@
+syntax = "proto3";
+
+import "google/protobuf/wrappers.proto";
+
+package foo;
+
+message TestWrapperSetters {
+  google.protobuf.DoubleValue double_value = 1;
+  google.protobuf.FloatValue float_value = 2;
+  google.protobuf.Int64Value int64_value = 3;
+  google.protobuf.UInt64Value uint64_value = 4;
+  google.protobuf.Int32Value int32_value = 5;
+  google.protobuf.UInt32Value uint32_value = 6;
+  google.protobuf.BoolValue bool_value = 7;
+  google.protobuf.StringValue string_value = 8;
+  google.protobuf.BytesValue bytes_value = 9;
+
+  oneof wrapped_oneofs {
+    google.protobuf.DoubleValue double_value_oneof = 10;
+    google.protobuf.StringValue string_value_oneof = 11;
+  }
+}

+ 1 - 1
php/tests/test.sh

@@ -14,7 +14,7 @@ set -e
 phpize && ./configure CFLAGS='-g -O0' && make
 phpize && ./configure CFLAGS='-g -O0' && make
 popd
 popd
 
 
-tests=( array_test.php encode_decode_test.php generated_class_test.php map_field_test.php well_known_test.php descriptors_test.php )
+tests=( array_test.php encode_decode_test.php generated_class_test.php map_field_test.php well_known_test.php descriptors_test.php wrapper_type_setters_test.php)
 
 
 for t in "${tests[@]}"
 for t in "${tests[@]}"
 do
 do

+ 228 - 0
php/tests/wrapper_type_setters_test.php

@@ -0,0 +1,228 @@
+<?php
+
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Foo\TestWrapperSetters;
+use Google\Protobuf\BoolValue;
+use Google\Protobuf\BytesValue;
+use Google\Protobuf\DoubleValue;
+use Google\Protobuf\FloatValue;
+use Google\Protobuf\Int32Value;
+use Google\Protobuf\Int64Value;
+use Google\Protobuf\StringValue;
+use Google\Protobuf\UInt32Value;
+use Google\Protobuf\UInt64Value;
+
+class WrapperTypeSettersTest extends TestBase
+{
+    /**
+     * @dataProvider gettersAndSettersDataProvider
+     */
+    public function testGettersAndSetters(
+        $class,
+        $wrapperClass,
+        $setter,
+        $valueSetter,
+        $getter,
+        $valueGetter,
+        $sequence
+    ) {
+        $oldSetterMsg = new $class();
+        $newSetterMsg = new $class();
+        foreach ($sequence as list($value, $expectedValue)) {
+            // Manually wrap the value to pass to the old setter
+            $wrappedValue = is_null($value) ? $value : new $wrapperClass(['value' => $value]);
+
+            // Set values using new and old setters
+            $oldSetterMsg->$setter($wrappedValue);
+            $newSetterMsg->$valueSetter($value);
+
+            // Get expected values old getter
+            $expectedValue = $oldSetterMsg->$getter();
+
+            // Check that old getter returns the same value after using the
+            // new setter
+            $actualValue = $newSetterMsg->$getter();
+            $this->assertEquals($expectedValue, $actualValue);
+
+            // Check that new getter returns the unwrapped value from
+            // $expectedValue
+            $actualValueNewGetter = $newSetterMsg->$valueGetter();
+            if (is_null($expectedValue)) {
+                $this->assertNull($actualValueNewGetter);
+            } else {
+                $this->assertEquals($expectedValue->getValue(), $actualValueNewGetter);
+            }
+        }
+    }
+
+    public function gettersAndSettersDataProvider()
+    {
+        return [
+            [TestWrapperSetters::class, DoubleValue::class, "setDoubleValue", "setDoubleValueValue", "getDoubleValue", "getDoubleValueValue", [
+                [1.1, new DoubleValue(["value" => 1.1])],
+                [2.2, new DoubleValue(["value" => 2.2])],
+                [null, null],
+                [0, new DoubleValue()],
+            ]],
+            [TestWrapperSetters::class, FloatValue::class, "setFloatValue", "setFloatValueValue", "getFloatValue", "getFloatValueValue", [
+                [1.1, new FloatValue(["value" => 1.1])],
+                [2.2, new FloatValue(["value" => 2.2])],
+                [null, null],
+                [0, new FloatValue()],
+            ]],
+            [TestWrapperSetters::class, Int64Value::class, "setInt64Value", "setInt64ValueValue", "getInt64Value", "getInt64ValueValue", [
+                [123, new Int64Value(["value" => 123])],
+                [-789, new Int64Value(["value" => -789])],
+                [null, null],
+                [0, new Int64Value()],
+                [5.5, new Int64Value(["value" => 5])], // Test conversion from float to int
+            ]],
+            [TestWrapperSetters::class, UInt64Value::class, "setUInt64Value", "setUInt64ValueValue", "getUInt64Value", "getUInt64ValueValue", [
+                [123, new UInt64Value(["value" => 123])],
+                [789, new UInt64Value(["value" => 789])],
+                [null, null],
+                [0, new UInt64Value()],
+                [5.5, new UInt64Value(["value" => 5])], // Test conversion from float to int
+                [-7, new UInt64Value(["value" => -7])], // Test conversion from -ve to +ve
+            ]],
+            [TestWrapperSetters::class, Int32Value::class, "setInt32Value", "setInt32ValueValue", "getInt32Value", "getInt32ValueValue", [
+                [123, new Int32Value(["value" => 123])],
+                [-789, new Int32Value(["value" => -789])],
+                [null, null],
+                [0, new Int32Value()],
+                [5.5, new Int32Value(["value" => 5])], // Test conversion from float to int
+            ]],
+            [TestWrapperSetters::class, UInt32Value::class, "setUInt32Value", "setUInt32ValueValue", "getUInt32Value", "getUInt32ValueValue", [
+                [123, new UInt32Value(["value" => 123])],
+                [789, new UInt32Value(["value" => 789])],
+                [null, null],
+                [0, new UInt32Value()],
+                [5.5, new UInt32Value(["value" => 5])], // Test conversion from float to int
+                [-7, new UInt32Value(["value" => -7])], // Test conversion from -ve to +ve
+            ]],
+            [TestWrapperSetters::class, BoolValue::class, "setBoolValue", "setBoolValueValue", "getBoolValue", "getBoolValueValue", [
+                [true, new BoolValue(["value" => true])],
+                [false, new BoolValue(["value" => false])],
+                [null, null],
+            ]],
+            [TestWrapperSetters::class, StringValue::class, "setStringValue", "setStringValueValue", "getStringValue", "getStringValueValue", [
+                ["asdf", new StringValue(["value" => "asdf"])],
+                ["", new StringValue(["value" => ""])],
+                [null, null],
+                ["", new StringValue()],
+                [5, new StringValue(["value" => "5"])], // Test conversion from number to string
+                [5.5, new StringValue(["value" => "5.5"])], // Test conversion from number to string
+                [-7, new StringValue(["value" => "-7"])], // Test conversion from number to string
+                [-7.5, new StringValue(["value" => "-7.5"])], // Test conversion from number to string
+            ]],
+            [TestWrapperSetters::class, BytesValue::class, "setBytesValue", "setBytesValueValue", "getBytesValue", "getBytesValueValue", [
+                ["asdf", new BytesValue(["value" => "asdf"])],
+                ["", new BytesValue(["value" => ""])],
+                [null, null],
+                ["", new BytesValue()],
+                [5, new BytesValue(["value" => "5"])], // Test conversion from number to bytes
+                [5.5, new BytesValue(["value" => "5.5"])], // Test conversion from number to bytes
+                [-7, new BytesValue(["value" => "-7"])], // Test conversion from number to bytes
+                [-7.5, new BytesValue(["value" => "-7.5"])], // Test conversion from number to bytes
+            ]],
+            [TestWrapperSetters::class, DoubleValue::class, "setDoubleValueOneof", "setDoubleValueOneofValue", "getDoubleValueOneof", "getDoubleValueOneofValue", [
+                [1.1, new DoubleValue(["value" => 1.1])],
+                [2.2, new DoubleValue(["value" => 2.2])],
+                [null, null],
+                [0, new DoubleValue()],
+            ]],[TestWrapperSetters::class, StringValue::class, "setStringValueOneof", "setStringValueOneofValue", "getStringValueOneof", "getStringValueOneofValue", [
+                ["asdf", new StringValue(["value" => "asdf"])],
+                ["", new StringValue(["value" => ""])],
+                [null, null],
+                ["", new StringValue()],
+                [5, new StringValue(["value" => "5"])], // Test conversion from number to string
+                [5.5, new StringValue(["value" => "5.5"])], // Test conversion from number to string
+                [-7, new StringValue(["value" => "-7"])], // Test conversion from number to string
+                [-7.5, new StringValue(["value" => "-7.5"])], // Test conversion from number to string
+            ]],
+        ];
+    }
+
+    /**
+     * @dataProvider invalidSettersDataProvider
+     * @expectedException \Exception
+     */
+    public function testInvalidSetters($class, $setter, $value)
+    {
+        (new $class())->$setter($value);
+    }
+
+    public function invalidSettersDataProvider()
+    {
+        return [
+            [TestWrapperSetters::class, "setDoubleValueValue", "abc"],
+            [TestWrapperSetters::class, "setDoubleValueValue", []],
+            [TestWrapperSetters::class, "setDoubleValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setDoubleValueValue", new DoubleValue()],
+
+            [TestWrapperSetters::class, "setFloatValueValue", "abc"],
+            [TestWrapperSetters::class, "setFloatValueValue", []],
+            [TestWrapperSetters::class, "setFloatValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setFloatValueValue", new FloatValue()],
+
+            [TestWrapperSetters::class, "setInt64ValueValue", "abc"],
+            [TestWrapperSetters::class, "setInt64ValueValue", []],
+            [TestWrapperSetters::class, "setInt64ValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setInt64ValueValue", new Int64Value()],
+
+            [TestWrapperSetters::class, "setUInt64ValueValue", "abc"],
+            [TestWrapperSetters::class, "setUInt64ValueValue", []],
+            [TestWrapperSetters::class, "setUInt64ValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setUInt64ValueValue", new UInt64Value()],
+
+            [TestWrapperSetters::class, "setInt32ValueValue", "abc"],
+            [TestWrapperSetters::class, "setInt32ValueValue", []],
+            [TestWrapperSetters::class, "setInt32ValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setInt32ValueValue", new Int32Value()],
+
+            [TestWrapperSetters::class, "setUInt32ValueValue", "abc"],
+            [TestWrapperSetters::class, "setUInt32ValueValue", []],
+            [TestWrapperSetters::class, "setUInt32ValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setUInt32ValueValue", new UInt32Value()],
+
+            [TestWrapperSetters::class, "setBoolValueValue", []],
+            [TestWrapperSetters::class, "setBoolValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setBoolValueValue", new BoolValue()],
+
+            [TestWrapperSetters::class, "setStringValueValue", []],
+            [TestWrapperSetters::class, "setStringValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setStringValueValue", new StringValue()],
+
+            [TestWrapperSetters::class, "setBytesValueValue", []],
+            [TestWrapperSetters::class, "setBytesValueValue", new stdClass()],
+            [TestWrapperSetters::class, "setBytesValueValue", new BytesValue()],
+        ];
+    }
+
+    /**
+     * @dataProvider constructorWithWrapperTypeDataProvider
+     */
+    public function testConstructorWithWrapperType($class, $wrapperClass, $wrapperField, $getter, $value)
+    {
+        $actualInstance = new $class([$wrapperField => $value]);
+        $expectedInstance = new $class([$wrapperField => new $wrapperClass(['value' => $value])]);
+        $this->assertEquals($expectedInstance->$getter()->getValue(), $actualInstance->$getter()->getValue());
+    }
+
+    public function constructorWithWrapperTypeDataProvider()
+    {
+        return [
+            [TestWrapperSetters::class, DoubleValue::class, 'double_value', 'getDoubleValue', 1.1],
+            [TestWrapperSetters::class, FloatValue::class, 'float_value', 'getFloatValue', 2.2],
+            [TestWrapperSetters::class, Int64Value::class, 'int64_value', 'getInt64Value', 3],
+            [TestWrapperSetters::class, UInt64Value::class, 'uint64_value', 'getUInt64Value', 4],
+            [TestWrapperSetters::class, Int32Value::class, 'int32_value', 'getInt32Value', 5],
+            [TestWrapperSetters::class, UInt32Value::class, 'uint32_value', 'getUInt32Value', 6],
+            [TestWrapperSetters::class, BoolValue::class, 'bool_value', 'getBoolValue', true],
+            [TestWrapperSetters::class, StringValue::class, 'string_value', 'getStringValue', "eight"],
+            [TestWrapperSetters::class, BytesValue::class, 'bytes_value', 'getBytesValue', "nine"],
+        ];
+    }
+}

+ 74 - 2
src/google/protobuf/compiler/php/php_generator.cc

@@ -99,6 +99,10 @@ void GenerateMessageConstructorDocComment(io::Printer* printer,
                                           int is_descriptor);
                                           int is_descriptor);
 void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field,
 void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field,
                              int is_descriptor, int function_type);
                              int is_descriptor, int function_type);
+void GenerateWrapperFieldGetterDocComment(io::Printer* printer,
+                                          const FieldDescriptor* field);
+void GenerateWrapperFieldSetterDocComment(io::Printer* printer,
+                                          const FieldDescriptor* field);
 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_,
 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_,
                             int is_descriptor);
                             int is_descriptor);
 void GenerateEnumValueDocComment(io::Printer* printer,
 void GenerateEnumValueDocComment(io::Printer* printer,
@@ -674,6 +678,21 @@ void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor,
         field->name());
         field->name());
   }
   }
 
 
+  // For wrapper types, generate an additional getXXXValue getter
+  if (!field->is_map() &&
+      !field->is_repeated() &&
+      field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
+      IsWrapperType(field)) {
+    GenerateWrapperFieldGetterDocComment(printer, field);
+    printer->Print(
+        "public function get^camel_name^Value()\n"
+        "{\n"
+        "    $wrapper = $this->get^camel_name^();\n"
+        "    return is_null($wrapper) ? null : $wrapper->getValue();\n"
+        "}\n\n",
+        "camel_name", UnderscoresToCamelCase(field->name(), true));
+  }
+
   // Generate setter.
   // Generate setter.
   GenerateFieldDocComment(printer, field, is_descriptor, kFieldSetter);
   GenerateFieldDocComment(printer, field, is_descriptor, kFieldSetter);
   printer->Print(
   printer->Print(
@@ -772,6 +791,22 @@ void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor,
   printer->Print(
   printer->Print(
       "}\n\n");
       "}\n\n");
 
 
+  // For wrapper types, generate an additional setXXXValue getter
+  if (!field->is_map() &&
+      !field->is_repeated() &&
+      field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
+      IsWrapperType(field)) {
+    GenerateWrapperFieldSetterDocComment(printer, field);
+    printer->Print(
+        "public function set^camel_name^Value($var)\n"
+        "{\n"
+        "    $wrappedVar = is_null($var) ? null : new \\^wrapper_type^(['value' => $var]);\n"
+        "    return $this->set^camel_name^($wrappedVar);\n"
+        "}\n\n",
+        "camel_name", UnderscoresToCamelCase(field->name(), true),
+        "wrapper_type", LegacyFullClassName(field->message_type(), is_descriptor));
+  }
+
   // Generate has method for proto2 only.
   // Generate has method for proto2 only.
   if (is_descriptor) {
   if (is_descriptor) {
     printer->Print(
     printer->Print(
@@ -988,14 +1023,16 @@ void GenerateUseDeclaration(bool is_descriptor, io::Printer* printer) {
     printer->Print(
     printer->Print(
         "use Google\\Protobuf\\Internal\\GPBType;\n"
         "use Google\\Protobuf\\Internal\\GPBType;\n"
         "use Google\\Protobuf\\Internal\\RepeatedField;\n"
         "use Google\\Protobuf\\Internal\\RepeatedField;\n"
-        "use Google\\Protobuf\\Internal\\GPBUtil;\n\n");
+        "use Google\\Protobuf\\Internal\\GPBUtil;\n"
+        "use Google\\Protobuf\\Internal\\GPBWrapperUtils;\n\n");
   } else {
   } else {
     printer->Print(
     printer->Print(
         "use Google\\Protobuf\\Internal\\GPBType;\n"
         "use Google\\Protobuf\\Internal\\GPBType;\n"
         "use Google\\Protobuf\\Internal\\GPBWire;\n"
         "use Google\\Protobuf\\Internal\\GPBWire;\n"
         "use Google\\Protobuf\\Internal\\RepeatedField;\n"
         "use Google\\Protobuf\\Internal\\RepeatedField;\n"
         "use Google\\Protobuf\\Internal\\InputStream;\n"
         "use Google\\Protobuf\\Internal\\InputStream;\n"
-        "use Google\\Protobuf\\Internal\\GPBUtil;\n\n");
+        "use Google\\Protobuf\\Internal\\GPBUtil;\n"
+        "use Google\\Protobuf\\Internal\\GPBWrapperUtils;\n\n");
   }
   }
 }
 }
 
 
@@ -1497,6 +1534,41 @@ void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field,
   printer->Print(" */\n");
   printer->Print(" */\n");
 }
 }
 
 
+void GenerateWrapperFieldGetterDocComment(io::Printer* printer, const FieldDescriptor* field) {
+  // Generate a doc comment for the special getXXXValue methods that are
+  // generated for wrapper types.
+  const FieldDescriptor* primitiveField = field->message_type()->FindFieldByName("value");
+  printer->Print("/**\n");
+  printer->Print(
+      " * Returns the unboxed value from <code>get^camel_name^()</code>\n\n",
+      "camel_name", UnderscoresToCamelCase(field->name(), true));
+  GenerateDocCommentBody(printer, field);
+  printer->Print(
+    " * Generated from protobuf field <code>^def^</code>\n",
+    "def", EscapePhpdoc(FirstLineOf(field->DebugString())));
+  printer->Print(" * @return ^php_type^|null\n",
+        "php_type", PhpGetterTypeName(primitiveField, false));
+  printer->Print(" */\n");
+}
+
+void GenerateWrapperFieldSetterDocComment(io::Printer* printer, const FieldDescriptor* field) {
+  // Generate a doc comment for the special setXXXValue methods that are
+  // generated for wrapper types.
+  const FieldDescriptor* primitiveField = field->message_type()->FindFieldByName("value");
+  printer->Print("/**\n");
+  printer->Print(
+      " * Sets the field by wrapping a primitive type in a ^message_name^ object.\n\n",
+      "message_name", LegacyFullClassName(field->message_type(), false));
+  GenerateDocCommentBody(printer, field);
+  printer->Print(
+    " * Generated from protobuf field <code>^def^</code>\n",
+    "def", EscapePhpdoc(FirstLineOf(field->DebugString())));
+  printer->Print(" * @param ^php_type^|null $var\n",
+        "php_type", PhpSetterTypeName(primitiveField, false));
+  printer->Print(" * @return $this\n");
+  printer->Print(" */\n");
+}
+
 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_,
 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_,
                             int is_descriptor) {
                             int is_descriptor) {
   printer->Print("/**\n");
   printer->Print("/**\n");

+ 5 - 0
src/google/protobuf/compiler/php/php_generator.h

@@ -62,6 +62,11 @@ PROTOC_EXPORT std::string GeneratedClassName(
 PROTOC_EXPORT std::string GeneratedClassName(
 PROTOC_EXPORT std::string GeneratedClassName(
     const google::protobuf::ServiceDescriptor* desc);
     const google::protobuf::ServiceDescriptor* desc);
 
 
+inline bool IsWrapperType(const FieldDescriptor* descriptor) {
+  return descriptor->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
+      descriptor->message_type()->file()->name() == "google/protobuf/wrappers.proto";
+}
+
 }  // namespace php
 }  // namespace php
 }  // namespace compiler
 }  // namespace compiler
 }  // namespace protobuf
 }  // namespace protobuf

+ 2 - 0
tests.sh

@@ -285,6 +285,7 @@ generate_php_test_proto() {
   rm -rf generated
   rm -rf generated
   mkdir generated
   mkdir generated
   ../../src/protoc --php_out=generated         \
   ../../src/protoc --php_out=generated         \
+    -I../../src -I.                            \
     proto/empty/echo.proto                     \
     proto/empty/echo.proto                     \
     proto/test.proto                           \
     proto/test.proto                           \
     proto/test_include.proto                   \
     proto/test_include.proto                   \
@@ -300,6 +301,7 @@ generate_php_test_proto() {
     proto/test_reserved_message_upper.proto    \
     proto/test_reserved_message_upper.proto    \
     proto/test_service.proto                   \
     proto/test_service.proto                   \
     proto/test_service_namespace.proto         \
     proto/test_service_namespace.proto         \
+    proto/test_wrapper_type_setters.proto      \
     proto/test_descriptors.proto
     proto/test_descriptors.proto
   pushd ../../src
   pushd ../../src
   ./protoc --php_out=../php/tests/generated -I../php/tests -I. \
   ./protoc --php_out=../php/tests/generated -I../php/tests -I. \