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
pull/5236/head
michaelbausor 6 years ago committed by Paul Yang
parent 9e69594adf
commit 6a51c03823
  1. 4
      .gitignore
  2. 4
      Makefile.am
  3. 2
      php/composer.json
  4. 88
      php/ext/google/protobuf/message.c
  5. 86
      php/ext/google/protobuf/type_check.c
  6. 1
      php/phpunit.xml
  7. 19
      php/src/Google/Protobuf/Internal/FieldDescriptor.php
  8. 39
      php/src/Google/Protobuf/Internal/Message.php
  9. 22
      php/tests/proto/test_wrapper_type_setters.proto
  10. 2
      php/tests/test.sh
  11. 228
      php/tests/wrapper_type_setters_test.php
  12. 76
      src/google/protobuf/compiler/php/php_generator.cc
  13. 5
      src/google/protobuf/compiler/php/php_generator.h
  14. 2
      tests.sh

4
.gitignore vendored

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

@ -749,11 +749,13 @@ php_EXTRA_DIST= \
php/tests/proto/test_reserved_message_upper.proto \
php/tests/proto/test_service.proto \
php/tests/proto/test_service_namespace.proto \
php/tests/proto/test_wrapper_type_setters.proto \
php/tests/test.sh \
php/tests/test_base.php \
php/tests/test_util.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/MANIFEST.in \

@ -23,6 +23,6 @@
}
},
"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"
}
}

@ -254,6 +254,16 @@ void custom_data_init(const zend_class_entry* ce,
&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(
PHP_PROTO_HASHTABLE_VALUE php_descriptor TSRMLS_DC) {
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_OBJ(submsg, desc->klass->create_object(desc->klass TSRMLS_CC));
Message_construct(submsg, NULL);
MessageHeader* from = UNBOX(MessageHeader,
CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)value));
MessageHeader* to = UNBOX(MessageHeader, submsg);
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.");
zend_error(E_USER_ERROR,
"Cannot merge messages with different class.");
return;
}
layout_merge(from->descriptor->layout, from, to TSRMLS_CC);
}
} else {
message_set_property_internal(msg, &key,
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
// object. Here, we manually modify it to our message in such a case.
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) {
@ -1024,7 +1038,7 @@ static void hex_to_binary(const char* hex, char** binary, int* binary_len) {
PHP_METHOD(Any, __construct) {
init_file_any(TSRMLS_C);
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")
@ -1193,7 +1207,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Duration, __construct) {
init_file_duration(TSRMLS_C);
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")
@ -1229,7 +1243,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Timestamp, __construct) {
init_file_timestamp(TSRMLS_C);
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")
@ -1432,7 +1446,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Api, __construct) {
init_file_api(TSRMLS_C);
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")
@ -1467,7 +1481,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(BoolValue, __construct) {
init_file_wrappers(TSRMLS_C);
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")
@ -1496,7 +1510,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(BytesValue, __construct) {
init_file_wrappers(TSRMLS_C);
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")
@ -1525,7 +1539,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(DoubleValue, __construct) {
init_file_wrappers(TSRMLS_C);
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")
@ -1570,7 +1584,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Enum, __construct) {
init_file_type(TSRMLS_C);
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")
@ -1611,7 +1625,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(EnumValue, __construct) {
init_file_type(TSRMLS_C);
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")
@ -1642,7 +1656,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(FieldMask, __construct) {
init_file_field_mask(TSRMLS_C);
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")
@ -1707,7 +1721,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Field, __construct) {
init_file_type(TSRMLS_C);
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")
@ -1745,7 +1759,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(FloatValue, __construct) {
init_file_wrappers(TSRMLS_C);
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")
@ -1770,7 +1784,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(GPBEmpty, __construct) {
init_file_empty(TSRMLS_C);
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) {
init_file_wrappers(TSRMLS_C);
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")
@ -1827,7 +1841,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Int64Value, __construct) {
init_file_wrappers(TSRMLS_C);
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")
@ -1856,7 +1870,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(ListValue, __construct) {
init_file_struct(TSRMLS_C);
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")
@ -1909,7 +1923,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Method, __construct) {
init_file_api(TSRMLS_C);
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")
@ -1948,7 +1962,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Mixin, __construct) {
init_file_api(TSRMLS_C);
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")
@ -1982,7 +1996,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Option, __construct) {
init_file_type(TSRMLS_C);
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")
@ -2012,7 +2026,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(SourceContext, __construct) {
init_file_source_context(TSRMLS_C);
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")
@ -2041,7 +2055,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(StringValue, __construct) {
init_file_wrappers(TSRMLS_C);
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")
@ -2070,7 +2084,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Struct, __construct) {
init_file_struct(TSRMLS_C);
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")
@ -2119,7 +2133,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Type, __construct) {
init_file_type(TSRMLS_C);
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")
@ -2153,7 +2167,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(UInt32Value, __construct) {
init_file_wrappers(TSRMLS_C);
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")
@ -2182,7 +2196,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(UInt64Value, __construct) {
init_file_wrappers(TSRMLS_C);
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")
@ -2222,7 +2236,7 @@ PHP_PROTO_INIT_SUBMSGCLASS_END
PHP_METHOD(Value, __construct) {
init_file_struct(TSRMLS_C);
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")

@ -222,7 +222,7 @@ process_double:
type##_t* type##_value) { \
int64_t lval; \
double dval; \
\
TSRMLS_FETCH(); \
switch (convert_numeric_string(val, len, &lval, &dval)) { \
case IS_DOUBLE: { \
return convert_double_to_##type(dval, type##_value); \
@ -231,13 +231,15 @@ process_double:
return convert_int64_to_##type(lval, type##_value); \
} \
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; \
} \
} \
\
bool protobuf_convert_to_##type(zval* from, type##_t* to) { \
TSRMLS_FETCH(); \
switch (Z_TYPE_P(from)) { \
case IS_LONG: { \
return convert_int64_to_##type(Z_LVAL_P(from), to); \
@ -250,8 +252,9 @@ process_double:
to); \
} \
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; \
} \
} \
@ -281,6 +284,7 @@ CONVERT_TO_INTEGER(uint64);
int64_t lval; \
double dval; \
\
TSRMLS_FETCH(); \
switch (convert_numeric_string(val, len, &lval, &dval)) { \
case IS_DOUBLE: { \
*type##_value = (type)dval; \
@ -291,13 +295,15 @@ CONVERT_TO_INTEGER(uint64);
return true; \
} \
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; \
} \
} \
\
bool protobuf_convert_to_##type(zval* from, type* to) { \
TSRMLS_FETCH(); \
switch (Z_TYPE_P(from)) { \
case IS_LONG: { \
return convert_int64_to_##type(Z_LVAL_P(from), to); \
@ -310,8 +316,9 @@ CONVERT_TO_INTEGER(uint64);
to); \
} \
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; \
} \
} \
@ -324,6 +331,7 @@ CONVERT_TO_FLOAT(double);
#undef CONVERT_TO_FLOAT
bool protobuf_convert_to_bool(zval* from, int8_t* to) {
TSRMLS_FETCH();
switch (Z_TYPE_P(from)) {
#if PHP_MAJOR_VERSION < 7
case IS_BOOL:
@ -354,7 +362,9 @@ bool protobuf_convert_to_bool(zval* from, int8_t* to) {
}
} break;
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;
}
}
@ -362,6 +372,7 @@ bool protobuf_convert_to_bool(zval* from, int8_t* to) {
}
bool protobuf_convert_to_string(zval* from) {
TSRMLS_FETCH();
switch (Z_TYPE_P(from)) {
case IS_STRING: {
return true;
@ -380,7 +391,9 @@ bool protobuf_convert_to_string(zval* from) {
return true;
}
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;
}
}
@ -421,8 +434,9 @@ PHP_METHOD(Util, checkMessage) {
RETURN_NULL();
}
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_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) {
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;
}
RepeatedField* intern = UNBOX(RepeatedField, val);
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;
}
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_ZVAL(val, 1, 0);
} else {
zend_error(E_USER_ERROR, "Incorrect repeated field type.");
zend_throw_exception(
NULL, "Incorrect repeated field type.",
0 TSRMLS_CC);
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);
} else if (Z_TYPE_P(val) == IS_OBJECT) {
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;
}
Map* intern = UNBOX(Map, val);
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;
}
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;
}
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_ZVAL(val, 1, 0);
} else {
zend_error(E_USER_ERROR, "Incorrect map field type.");
zend_throw_exception(
NULL, "Incorrect map field type.",
0 TSRMLS_CC);
return;
}
}

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

@ -187,6 +187,25 @@ class FieldDescriptor
$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)
{
return ($field_type !== GPBType::STRING &&

@ -975,7 +975,7 @@ class Message
*
* @param array $array An array containing message properties and values.
* @return null.
* @throws Exception Invalid data.
* @throws \Exception Invalid data.
*/
protected function mergeFromArray(array $array)
{
@ -987,10 +987,47 @@ class Message
'Invalid message property: ' . $key);
}
$setter = $field->getSetter();
if ($field->isWrapperType()) {
self::normalizeToMessageType($value, $field->getMessageType()->getClass());
}
$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)
{
if (is_a($this, "Google\Protobuf\Any")) {

@ -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;
}
}

@ -14,7 +14,7 @@ set -e
phpize && ./configure CFLAGS='-g -O0' && make
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[@]}"
do

@ -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"],
];
}
}

@ -99,6 +99,10 @@ void GenerateMessageConstructorDocComment(io::Printer* printer,
int is_descriptor);
void GenerateFieldDocComment(io::Printer* printer, const FieldDescriptor* field,
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_,
int is_descriptor);
void GenerateEnumValueDocComment(io::Printer* printer,
@ -674,6 +678,21 @@ void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor,
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.
GenerateFieldDocComment(printer, field, is_descriptor, kFieldSetter);
printer->Print(
@ -772,6 +791,22 @@ void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor,
printer->Print(
"}\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.
if (is_descriptor) {
printer->Print(
@ -988,14 +1023,16 @@ void GenerateUseDeclaration(bool is_descriptor, io::Printer* printer) {
printer->Print(
"use Google\\Protobuf\\Internal\\GPBType;\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 {
printer->Print(
"use Google\\Protobuf\\Internal\\GPBType;\n"
"use Google\\Protobuf\\Internal\\GPBWire;\n"
"use Google\\Protobuf\\Internal\\RepeatedField;\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");
}
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_,
int is_descriptor) {
printer->Print("/**\n");

@ -62,6 +62,11 @@ PROTOC_EXPORT std::string GeneratedClassName(
PROTOC_EXPORT std::string GeneratedClassName(
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 compiler
} // namespace protobuf

@ -285,6 +285,7 @@ generate_php_test_proto() {
rm -rf generated
mkdir generated
../../src/protoc --php_out=generated \
-I../../src -I. \
proto/empty/echo.proto \
proto/test.proto \
proto/test_include.proto \
@ -300,6 +301,7 @@ generate_php_test_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
pushd ../../src
./protoc --php_out=../php/tests/generated -I../php/tests -I. \

Loading…
Cancel
Save