Lazily Create Singular Wrapper Message (#6833)

* Register additional handlers from wrappers

* Return zval instead of parse frame

* Use parse frame

* Update upb

* Lazily create wrapper messages

* Fix a segment fault

Need check type of field before getting submsg def

* Avoid expanding during serialization and direct access

* Fix a bug that getXXXUnwrapped returns null for string

* Implement writeWrapperUnwrapped

* Add more tests

* Fix oneof wrapper parsing

* Fix get oneof field

* Avoid expansion for oneof wrappers

* Fix bug

* Fix a bug that in php7 variable is defined out of scope

* Fix broken tests
 * Update upb to fix Timestamp conformance tests
 * Fix segmentation fault for oneof wrapper fields

* Fix encoding/decoding top level wrapper values

* Add type checking for write wrapper value in php7

* Fix zts build

* Fix the bug that readWrapperValue uses parent message's layout to access wrapper value

* Fix wrapper in map
diff --git a/php/ext/google/protobuf/encode_decode.c b/php/ext/google/protobuf/encode_decode.c
index 39304a5..2612d22 100644
--- a/php/ext/google/protobuf/encode_decode.c
+++ b/php/ext/google/protobuf/encode_decode.c
@@ -122,12 +122,29 @@
 // Parsing.
 // -----------------------------------------------------------------------------
 
-// TODO(teboring): This shoud be a bit in upb_msgdef
-static bool is_wrapper_msg(const upb_msgdef *msg) {
-  return !strcmp(upb_filedef_name(upb_msgdef_file(msg)),
-                 "google/protobuf/wrappers.proto");
+bool is_wrapper_msg(const upb_msgdef* m) {
+  switch (upb_msgdef_wellknowntype(m)) {
+    case UPB_WELLKNOWN_DOUBLEVALUE:
+    case UPB_WELLKNOWN_FLOATVALUE:
+    case UPB_WELLKNOWN_INT64VALUE:
+    case UPB_WELLKNOWN_UINT64VALUE:
+    case UPB_WELLKNOWN_INT32VALUE:
+    case UPB_WELLKNOWN_UINT32VALUE:
+    case UPB_WELLKNOWN_STRINGVALUE:
+    case UPB_WELLKNOWN_BYTESVALUE:
+    case UPB_WELLKNOWN_BOOLVALUE:
+      return true;
+    default:
+      return false;
+  }
 }
 
+typedef struct {
+  void* closure;
+  void* submsg;
+  bool is_msg;
+} wrapperfields_parseframe_t;
+
 #define DEREF(msg, ofs, type) *(type*)(((uint8_t *)msg) + ofs)
 
 // Creates a handlerdata that simply contains the offset for this field.
@@ -432,6 +449,40 @@
   return submsg;
 }
 
+// Appends a wrapper submessage to a repeated field.
+static void *appendwrappersubmsg_handler(void *closure, const void *hd) {
+  zval* array = (zval*)closure;
+  TSRMLS_FETCH();
+  RepeatedField* intern = UNBOX(RepeatedField, array);
+
+  const submsg_handlerdata_t *submsgdata = hd;
+  Descriptor* subdesc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)submsgdata->md));
+  zend_class_entry* subklass = subdesc->klass;
+  MessageHeader* submsg;
+  wrapperfields_parseframe_t* frame =
+      (wrapperfields_parseframe_t*)malloc(sizeof(wrapperfields_parseframe_t));
+
+#if PHP_MAJOR_VERSION < 7
+  zval* val = NULL;
+  MAKE_STD_ZVAL(val);
+  ZVAL_OBJ(val, subklass->create_object(subklass TSRMLS_CC));
+  repeated_field_push_native(intern, &val);
+  submsg = UNBOX(MessageHeader, val);
+#else
+  zend_object* obj = subklass->create_object(subklass TSRMLS_CC);
+  repeated_field_push_native(intern, &obj);
+  submsg = (MessageHeader*)((char*)obj - XtOffsetOf(MessageHeader, std));
+#endif
+  custom_data_init(subklass, submsg PHP_PROTO_TSRMLS_CC);
+
+  frame->closure = closure;
+  frame->submsg = submsg;
+  frame->is_msg = true;
+
+  return frame;
+}
+
 // Sets a non-repeated submessage field in a message.
 static void *submsg_handler(void *closure, const void *hd) {
   MessageHeader* msg = closure;
@@ -502,6 +553,46 @@
   return submsg;
 }
 
+static void *map_wrapper_submsg_handler(void *closure, const void *hd) {
+  MessageHeader* msg = closure;
+  const submsg_handlerdata_t* submsgdata = hd;
+  TSRMLS_FETCH();
+  Descriptor* subdesc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)submsgdata->md));
+  zend_class_entry* subklass = subdesc->klass;
+  zval* submsg_php;
+  MessageHeader* submsg;
+  wrapperfields_parseframe_t* frame =
+      (wrapperfields_parseframe_t*)malloc(sizeof(wrapperfields_parseframe_t));
+
+  CACHED_VALUE* cached =
+      DEREF(message_data(msg), submsgdata->ofs, CACHED_VALUE*);
+
+  if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(cached)) == IS_NULL) {
+#if PHP_MAJOR_VERSION < 7
+    zval val;
+    ZVAL_OBJ(&val, subklass->create_object(subklass TSRMLS_CC));
+    MessageHeader* intern = UNBOX(MessageHeader, &val);
+    custom_data_init(subklass, intern PHP_PROTO_TSRMLS_CC);
+    REPLACE_ZVAL_VALUE(cached, &val, 1);
+    zval_dtor(&val);
+#else
+    zend_object* obj = subklass->create_object(subklass TSRMLS_CC);
+    ZVAL_OBJ(cached, obj);
+    MessageHeader* intern = UNBOX_HASHTABLE_VALUE(MessageHeader, obj);
+    custom_data_init(subklass, intern PHP_PROTO_TSRMLS_CC);
+#endif
+  }
+
+  submsg_php = CACHED_PTR_TO_ZVAL_PTR(cached);
+
+  submsg = UNBOX(MessageHeader, submsg_php);
+  frame->closure = closure;
+  frame->submsg = submsg;
+  frame->is_msg = true;
+  return frame;
+}
+
 // Handler data for startmap/endmap handlers.
 typedef struct {
   const upb_fielddef* fd;
@@ -887,6 +978,78 @@
   return submsg;
 }
 
+// Sets a non-repeated wrapper submessage field in a message.
+static void* wrapper_submsg_handler(void* closure, const void* hd) {
+  MessageHeader* msg = closure;
+  const submsg_handlerdata_t* submsgdata = hd;
+  TSRMLS_FETCH();
+  Descriptor* subdesc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)submsgdata->md));
+  zend_class_entry* subklass = subdesc->klass;
+  zval* submsg_php;
+  MessageHeader* submsg;
+  wrapperfields_parseframe_t* frame =
+      (wrapperfields_parseframe_t*)malloc(sizeof(wrapperfields_parseframe_t));
+
+  CACHED_VALUE* cached = find_zval_property(msg, submsgdata->fd);
+  submsg_php = CACHED_PTR_TO_ZVAL_PTR(cached);
+  frame->closure = closure;
+
+  if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(cached)) == IS_OBJECT) {
+    submsg = UNBOX(MessageHeader, submsg_php);
+    frame->submsg = submsg;
+    frame->is_msg = true;
+  } else {
+    // In this case, wrapper message hasn't been created and value will be
+    // stored in cache directly.
+    frame->submsg = cached;
+    frame->is_msg = false;
+  }
+
+  return frame;
+}
+
+// Handler for a wrapper submessage field in a oneof.
+static void* wrapper_oneofsubmsg_handler(void* closure, const void* hd) {
+  MessageHeader* msg = closure;
+  const oneof_handlerdata_t *oneofdata = hd;
+  uint32_t oldcase = DEREF(message_data(msg), oneofdata->case_ofs, uint32_t);
+  TSRMLS_FETCH();
+  Descriptor* subdesc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)oneofdata->md));
+  zend_class_entry* subklass = subdesc->klass;
+  wrapperfields_parseframe_t* frame =
+      (wrapperfields_parseframe_t*)malloc(sizeof(wrapperfields_parseframe_t));
+  CACHED_VALUE* cached = OBJ_PROP(&msg->std, oneofdata->property_ofs);
+  MessageHeader* submsg;
+
+  if (oldcase != oneofdata->oneof_case_num) {
+    oneof_cleanup(msg, oneofdata);
+    frame->submsg = cached;
+    frame->is_msg = false;
+  } else if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(cached)) == IS_OBJECT) {
+    submsg = UNBOX(MessageHeader, CACHED_PTR_TO_ZVAL_PTR(cached));
+    frame->submsg = submsg;
+    frame->is_msg = true;
+  } else {
+    // In this case, wrapper message hasn't been created and value will be
+    // stored in cache directly.
+    frame->submsg = cached;
+    frame->is_msg = false;
+  }
+
+  DEREF(message_data(msg), oneofdata->case_ofs, uint32_t) =
+      oneofdata->oneof_case_num;
+
+  return frame;
+}
+
+static bool wrapper_submsg_end_handler(void *closure, const void *hd) {
+  wrapperfields_parseframe_t* frame = closure;
+  free(frame);
+  return true;
+}
+
 // Set up handlers for a repeated field.
 static void add_handlers_for_repeated_field(upb_handlers *h,
                                             const upb_fielddef *f,
@@ -923,7 +1086,12 @@
     case UPB_TYPE_MESSAGE: {
       upb_handlerattr attr = UPB_HANDLERATTR_INIT;
       attr.handler_data = newsubmsghandlerdata(h, 0, f);
-      upb_handlers_setstartsubmsg(h, f, appendsubmsg_handler, &attr);
+      if (is_wrapper_msg(upb_fielddef_msgsubdef(f))) {
+        upb_handlers_setstartsubmsg(h, f, appendwrappersubmsg_handler, &attr);
+        upb_handlers_setendsubmsg(h, f, wrapper_submsg_end_handler, &attr);
+      } else {
+        upb_handlers_setstartsubmsg(h, f, appendsubmsg_handler, &attr);
+      }
       break;
     }
   }
@@ -974,7 +1142,16 @@
       upb_handlerattr attr = UPB_HANDLERATTR_INIT;
       if (is_map) {
         attr.handler_data = newsubmsghandlerdata(h, offset, f);
-        upb_handlers_setstartsubmsg(h, f, map_submsg_handler, &attr);
+        if (is_wrapper_msg(upb_fielddef_msgsubdef(f))) {
+          upb_handlers_setstartsubmsg(h, f, map_wrapper_submsg_handler, &attr);
+          upb_handlers_setendsubmsg(h, f, wrapper_submsg_end_handler, &attr);
+        } else {
+          upb_handlers_setstartsubmsg(h, f, map_submsg_handler, &attr);
+        }
+      } else if (is_wrapper_msg(upb_fielddef_msgsubdef(f))) {
+        attr.handler_data = newsubmsghandlerdata(h, 0, f);
+        upb_handlers_setstartsubmsg(h, f, wrapper_submsg_handler, &attr);
+        upb_handlers_setendsubmsg(h, f, wrapper_submsg_end_handler, &attr);
       } else {
         attr.handler_data = newsubmsghandlerdata(h, 0, f);
         upb_handlers_setstartsubmsg(h, f, submsg_handler, &attr);
@@ -1056,12 +1233,109 @@
       break;
     }
     case UPB_TYPE_MESSAGE: {
-      upb_handlers_setstartsubmsg(h, f, oneofsubmsg_handler, &attr);
+      if (is_wrapper_msg(upb_fielddef_msgsubdef(f))) {
+        upb_handlers_setstartsubmsg(h, f, wrapper_oneofsubmsg_handler, &attr);
+        upb_handlers_setendsubmsg(h, f, wrapper_submsg_end_handler, &attr);
+      } else {
+        upb_handlers_setstartsubmsg(h, f, oneofsubmsg_handler, &attr);
+      }
       break;
     }
   }
 }
 
+#define DEFINE_WRAPPER_HANDLER(utype, type, ctype)           \
+  static bool type##wrapper_handler(                         \
+      void* closure, const void* hd, ctype val) {            \
+    wrapperfields_parseframe_t* frame = closure;             \
+    if (frame->is_msg) {                                     \
+      MessageHeader* msg = frame->submsg;                    \
+      const size_t *ofs = hd;                                \
+      DEREF(message_data(msg), *ofs, ctype) = val;           \
+    } else {                                                 \
+      TSRMLS_FETCH();                                        \
+      native_slot_get(utype, &val, frame->submsg TSRMLS_CC); \
+    }                                                        \
+    return true;                                             \
+  }
+
+DEFINE_WRAPPER_HANDLER(UPB_TYPE_BOOL,   bool,   bool)
+DEFINE_WRAPPER_HANDLER(UPB_TYPE_INT32,  int32,  int32_t)
+DEFINE_WRAPPER_HANDLER(UPB_TYPE_UINT32, uint32, uint32_t)
+DEFINE_WRAPPER_HANDLER(UPB_TYPE_FLOAT,  float,  float)
+DEFINE_WRAPPER_HANDLER(UPB_TYPE_INT64,  int64,  int64_t)
+DEFINE_WRAPPER_HANDLER(UPB_TYPE_UINT64, uint64, uint64_t)
+DEFINE_WRAPPER_HANDLER(UPB_TYPE_DOUBLE, double, double)
+
+#undef DEFINE_WRAPPER_HANDLER
+
+static bool strwrapper_end_handler(void *closure, const void *hd) {
+  stringfields_parseframe_t* frame = closure;
+  const upb_fielddef **field = (const upb_fielddef **) hd;
+  wrapperfields_parseframe_t* wrapper_frame = frame->closure;
+  MessageHeader* msg;
+  CACHED_VALUE* cached;
+
+  if (wrapper_frame->is_msg) {
+    msg = wrapper_frame->submsg;
+    cached = find_zval_property(msg, *field);
+  } else {
+    cached = wrapper_frame->submsg;
+  }
+
+  new_php_string(cached, frame->sink.ptr, frame->sink.len);
+
+  stringsink_uninit(&frame->sink);
+  free(frame);
+
+  return true;
+}
+
+static void add_handlers_for_wrapper(const upb_msgdef* msgdef,
+                                     upb_handlers* h) {
+  const upb_fielddef* f = upb_msgdef_itof(msgdef, 1);
+  Descriptor* desc;
+  size_t offset;
+
+  TSRMLS_FETCH();
+  desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)msgdef));
+  offset = desc->layout->fields[upb_fielddef_index(f)].offset;
+
+  switch (upb_msgdef_wellknowntype(msgdef)) {
+#define SET_HANDLER(utype, ltype)                                 \
+  case utype: {                                                   \
+    upb_handlerattr attr = UPB_HANDLERATTR_INIT;                  \
+    attr.handler_data = newhandlerdata(h, offset);                \
+    upb_handlers_set##ltype(h, f, ltype##wrapper_handler, &attr); \
+    break;                                                        \
+  }
+
+    SET_HANDLER(UPB_WELLKNOWN_BOOLVALUE,   bool);
+    SET_HANDLER(UPB_WELLKNOWN_INT32VALUE,  int32);
+    SET_HANDLER(UPB_WELLKNOWN_UINT32VALUE, uint32);
+    SET_HANDLER(UPB_WELLKNOWN_FLOATVALUE,  float);
+    SET_HANDLER(UPB_WELLKNOWN_INT64VALUE,  int64);
+    SET_HANDLER(UPB_WELLKNOWN_UINT64VALUE, uint64);
+    SET_HANDLER(UPB_WELLKNOWN_DOUBLEVALUE, double);
+
+#undef SET_HANDLER
+
+    case UPB_WELLKNOWN_STRINGVALUE:
+    case UPB_WELLKNOWN_BYTESVALUE: {
+      upb_handlerattr attr = UPB_HANDLERATTR_INIT;
+      attr.handler_data = newhandlerfielddata(h, f);
+
+      upb_handlers_setstartstr(h, f, str_handler, &attr);
+      upb_handlers_setstring(h, f, stringdata_handler, &attr);
+      upb_handlers_setendstr(h, f, strwrapper_end_handler, &attr);
+      break;
+    }
+    default:
+      // Cannot reach here.
+      break;
+  }
+}
+
 static bool add_unknown_handler(void* closure, const void* hd, const char* buf,
                          size_t size) {
   encodeunknown_handlerfunc handler =
@@ -1102,6 +1376,14 @@
     desc->layout = create_layout(desc->msgdef);
   }
 
+
+  // If this is a wrapper message type, set up a special set of handlers and
+  // bail out of the normal (user-defined) message type handling.
+  if (is_wrapper_msg(msgdef)) {
+    add_handlers_for_wrapper(msgdef, h);
+    return;
+  }
+
   upb_handlerattr attr = UPB_HANDLERATTR_INIT;
   attr.handler_data = newunknownfieldshandlerdata(h);
   upb_handlers_setunknown(h, add_unknown_handler, &attr);
@@ -1152,6 +1434,9 @@
 static void putrawmsg(MessageHeader* msg, const Descriptor* desc,
                       upb_sink sink, int depth, bool is_json,
                       bool open_msg TSRMLS_DC);
+static void putwrappervalue(
+    zval* value, const upb_fielddef* f,
+    upb_sink sink, int depth, bool is_json TSRMLS_DC);
 static void putjsonany(MessageHeader* msg, const Descriptor* desc,
                        upb_sink sink, int depth TSRMLS_DC);
 static void putjsonlistvalue(
@@ -1299,7 +1584,7 @@
                        value_field, depth + 1, entry_sink, is_json TSRMLS_CC);
 
     upb_sink_endmsg(entry_sink, &status);
-    upb_sink_endsubmsg(subsink, getsel(f, UPB_HANDLER_ENDSUBMSG));
+    upb_sink_endsubmsg(subsink, entry_sink, getsel(f, UPB_HANDLER_ENDSUBMSG));
   }
 
   upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
@@ -1535,7 +1820,12 @@
       }
     } else if (upb_fielddef_issubmsg(f)) {
       zval* submsg = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
-      putsubmsg(submsg, f, sink, depth, is_json TSRMLS_CC);
+      if (is_wrapper_msg(upb_fielddef_msgsubdef(f)) &&
+          Z_TYPE_P(submsg) != IS_NULL && Z_TYPE_P(submsg) != IS_OBJECT) {
+        putwrappervalue(submsg, f, sink, depth, is_json TSRMLS_CC);
+      } else {
+        putsubmsg(submsg, f, sink, depth, is_json TSRMLS_CC);
+      }
     } else {
       upb_selector_t sel = getsel(f, upb_handlers_getprimitivehandlertype(f));
 
@@ -1634,6 +1924,54 @@
   putrawsubmsg(submsg, f, sink, depth, is_json TSRMLS_CC);
 }
 
+static void putwrappervalue(
+    zval* value, const upb_fielddef* f,
+    upb_sink sink, int depth, bool is_json TSRMLS_DC) {
+  upb_sink subsink;
+  const upb_msgdef* msgdef = upb_fielddef_msgsubdef(f);
+  const upb_fielddef* value_field = upb_msgdef_itof(msgdef, 1);
+  upb_selector_t sel =
+      getsel(value_field, upb_handlers_getprimitivehandlertype(value_field));
+
+  upb_sink_startsubmsg(sink, getsel(f, UPB_HANDLER_STARTSUBMSG), &subsink);
+
+#define T(upbtypeconst, upbtype, ctype, default_value)      \
+  case upbtypeconst: {                                      \
+    ctype value_raw;                                        \
+    native_slot_set(upb_fielddef_type(value_field), NULL,   \
+                    &value_raw, value PHP_PROTO_TSRMLS_CC); \
+    if ((is_json && is_wrapper_msg(msgdef)) ||              \
+        value_raw != default_value) {                       \
+      upb_sink_put##upbtype(subsink, sel, value_raw);       \
+    }                                                       \
+  } break;
+
+  switch (upb_fielddef_type(value_field)) {
+    T(UPB_TYPE_FLOAT, float, float, 0.0)
+    T(UPB_TYPE_DOUBLE, double, double, 0.0)
+    T(UPB_TYPE_BOOL, bool, uint8_t, 0)
+    T(UPB_TYPE_INT32, int32, int32_t, 0)
+    T(UPB_TYPE_UINT32, uint32, uint32_t, 0)
+    T(UPB_TYPE_INT64, int64, int64_t, 0)
+    T(UPB_TYPE_UINT64, uint64, uint64_t, 0)
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      if ((is_json && is_wrapper_msg(msgdef)) ||
+          Z_STRLEN_P(value) > 0) {
+        putstr(value, value_field, subsink, is_json && is_wrapper_msg(msgdef));
+      }
+      break;
+    }
+    case UPB_TYPE_ENUM:
+    case UPB_TYPE_MESSAGE:
+      zend_error(E_ERROR, "Internal error.");
+  }
+
+#undef T
+
+  upb_sink_endsubmsg(sink, subsink, getsel(f, UPB_HANDLER_ENDSUBMSG));
+}
+
 static void putrawsubmsg(MessageHeader* submsg, const upb_fielddef* f,
                          upb_sink sink, int depth, bool is_json TSRMLS_DC) {
   upb_sink subsink;
@@ -1643,7 +1981,7 @@
 
   upb_sink_startsubmsg(sink, getsel(f, UPB_HANDLER_STARTSUBMSG), &subsink);
   putrawmsg(submsg, subdesc, subsink, depth + 1, is_json, true TSRMLS_CC);
-  upb_sink_endsubmsg(sink, getsel(f, UPB_HANDLER_ENDSUBMSG));
+  upb_sink_endsubmsg(sink, subsink, getsel(f, UPB_HANDLER_ENDSUBMSG));
 }
 
 static void putarray(zval* array, const upb_fielddef* f, upb_sink sink,
@@ -1769,12 +2107,28 @@
   stackenv se;
   upb_sink sink;
   upb_pbdecoder* decoder;
+  void* closure;
   stackenv_init(&se, "Error occurred during parsing: %s");
 
-  upb_sink_reset(&sink, h, msg);
+  if (is_wrapper_msg(desc->msgdef)) {
+    wrapperfields_parseframe_t* frame =
+        (wrapperfields_parseframe_t*)malloc(
+            sizeof(wrapperfields_parseframe_t));
+    frame->submsg = msg;
+    frame->is_msg = true;
+    closure = frame;
+  } else {
+    closure = msg;
+  }
+
+  upb_sink_reset(&sink, h, closure);
   decoder = upb_pbdecoder_create(se.arena, method, sink, &se.status);
   upb_bufsrc_putbuf(data, data_len, upb_pbdecoder_input(decoder));
 
+  if (is_wrapper_msg(desc->msgdef)) {
+    free((wrapperfields_parseframe_t*)closure);
+  }
+
   stackenv_uninit(&se);
 }
 
@@ -1852,13 +2206,28 @@
     stackenv se;
     upb_sink sink;
     upb_json_parser* parser;
+    void* closure;
     stackenv_init(&se, "Error occurred during parsing: %s");
 
-    upb_sink_reset(&sink, get_fill_handlers(desc), msg);
+    if (is_wrapper_msg(desc->msgdef)) {
+      wrapperfields_parseframe_t* frame =
+          (wrapperfields_parseframe_t*)malloc(
+              sizeof(wrapperfields_parseframe_t));
+      frame->submsg = msg;
+      frame->is_msg = true;
+      closure = frame;
+    } else {
+      closure = msg;
+    }
+
+    upb_sink_reset(&sink, get_fill_handlers(desc), closure);
     parser = upb_json_parser_create(se.arena, method, generated_pool->symtab,
                                     sink, &se.status, ignore_json_unknown);
     upb_bufsrc_putbuf(data, data_len, upb_json_parser_input(parser));
 
+    if (is_wrapper_msg(desc->msgdef)) {
+      free((wrapperfields_parseframe_t*)closure);
+    }
     stackenv_uninit(&se);
   }
 }
diff --git a/php/ext/google/protobuf/message.c b/php/ext/google/protobuf/message.c
index 03dec75..291c2e5 100644
--- a/php/ext/google/protobuf/message.c
+++ b/php/ext/google/protobuf/message.c
@@ -55,6 +55,8 @@
   PHP_ME(Message, serializeToJsonString, NULL, ZEND_ACC_PUBLIC)
   PHP_ME(Message, mergeFromJsonString, NULL, ZEND_ACC_PUBLIC)
   PHP_ME(Message, mergeFrom, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Message, readWrapperValue, NULL, ZEND_ACC_PROTECTED)
+  PHP_ME(Message, writeWrapperValue, NULL, ZEND_ACC_PROTECTED)
   PHP_ME(Message, readOneof, NULL, ZEND_ACC_PROTECTED)
   PHP_ME(Message, writeOneof, NULL, ZEND_ACC_PROTECTED)
   PHP_ME(Message, whichOneof, NULL, ZEND_ACC_PROTECTED)
@@ -295,12 +297,6 @@
 // PHP Methods
 // -----------------------------------------------------------------------------
 
-static bool is_wrapper_msg(const upb_msgdef* m) {
-  upb_wellknowntype_t type = upb_msgdef_wellknowntype(m);
-  return type >= UPB_WELLKNOWN_DOUBLEVALUE &&
-         type <= UPB_WELLKNOWN_BOOLVALUE;
-}
-
 static void append_wrapper_message(
     zend_class_entry* subklass, RepeatedField* intern, zval* value TSRMLS_DC) {
   MessageHeader* submsg;
@@ -561,6 +557,133 @@
   layout_merge(from->descriptor->layout, from, to TSRMLS_CC);
 }
 
+PHP_METHOD(Message, readWrapperValue) {
+  char* member;
+  PHP_PROTO_SIZE length;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &member,
+                            &length) == FAILURE) {
+    return;
+  }
+
+  MessageHeader* msg = UNBOX(MessageHeader, getThis());
+  const upb_fielddef* field =
+      upb_msgdef_ntofz(msg->descriptor->msgdef, member);
+
+  if (upb_fielddef_containingoneof(field)) {
+    uint32_t* oneof_case =
+        slot_oneof_case(msg->descriptor->layout, message_data(msg), field);
+    if (*oneof_case != upb_fielddef_number(field)) {
+      RETURN_NULL();
+    }
+  }
+
+  zval* cached_zval =
+      CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, field));
+
+  if (Z_TYPE_P(cached_zval) == IS_NULL) {
+    RETURN_NULL();
+  }
+
+  if (Z_TYPE_P(cached_zval) == IS_OBJECT) {
+    const upb_msgdef* submsgdef = upb_fielddef_msgsubdef(field);
+    const upb_fielddef* value_field = upb_msgdef_itof(submsgdef, 1);
+    MessageHeader* submsg = UNBOX(MessageHeader, cached_zval);
+    CACHED_VALUE* cached_value = find_zval_property(submsg, value_field);
+    layout_get(submsg->descriptor->layout, submsg, value_field,
+               cached_value TSRMLS_CC);
+    RETURN_ZVAL(CACHED_PTR_TO_ZVAL_PTR(cached_value), 1, 0);
+  } else {
+    RETURN_ZVAL(cached_zval, 1, 0);
+  }
+}
+
+PHP_METHOD(Message, writeWrapperValue) {
+  char* member;
+  PHP_PROTO_SIZE length;
+  zval* value;
+  if (zend_parse_parameters(
+      ZEND_NUM_ARGS() TSRMLS_CC, "sz", &member, &length, &value) ==
+      FAILURE) {
+    return;
+  }
+
+  MessageHeader* msg = UNBOX(MessageHeader, getThis());
+  const upb_fielddef* field = upb_msgdef_ntofz(msg->descriptor->msgdef, member);
+
+  zval* cached_zval =
+      CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, field));
+
+  if (Z_TYPE_P(value) == IS_NULL) {
+    MessageHeader* msg = UNBOX(MessageHeader, getThis());
+    layout_set(msg->descriptor->layout, msg,
+               field, value TSRMLS_CC);
+    return;
+  }
+
+  {
+    // Type Checking
+    const upb_msgdef* submsgdef = upb_fielddef_msgsubdef(field);
+    const upb_fielddef* value_field = upb_msgdef_itof(submsgdef, 1);
+    upb_fieldtype_t type = upb_fielddef_type(value_field);
+    switch(type) {
+      case UPB_TYPE_STRING:
+      case UPB_TYPE_BYTES: {
+        if (!protobuf_convert_to_string(value)) {
+          return;
+        }
+        if (type == UPB_TYPE_STRING &&
+            !is_structurally_valid_utf8(Z_STRVAL_P(value), Z_STRLEN_P(value))) {
+          zend_error(E_USER_ERROR, "Given string is not UTF8 encoded.");
+          return;
+        }
+      }
+      break;
+#define CASE_TYPE(upb_type, type, c_type)                    \
+  case UPB_TYPE_##upb_type: {                                \
+    c_type type##_value;                                     \
+    if (!protobuf_convert_to_##type(value, &type##_value)) { \
+      return;                                                \
+    }                                                        \
+    break;                                                   \
+  }
+      CASE_TYPE(INT32,  int32,  int32_t)
+      CASE_TYPE(UINT32, uint32, uint32_t)
+      CASE_TYPE(ENUM,   int32,  int32_t)
+      CASE_TYPE(INT64,  int64,  int64_t)
+      CASE_TYPE(UINT64, uint64, uint64_t)
+      CASE_TYPE(FLOAT,  float,  float)
+      CASE_TYPE(DOUBLE, double, double)
+      CASE_TYPE(BOOL,   bool,   int8_t)
+
+#undef CASE_TYPE
+    }
+  }
+
+  if (upb_fielddef_containingoneof(field)) {
+    uint32_t* oneof_case =
+        slot_oneof_case(msg->descriptor->layout, message_data(msg), field);
+    if (*oneof_case != upb_fielddef_number(field)) {
+      zval null_value;
+      ZVAL_NULL(&null_value);
+      layout_set(msg->descriptor->layout, msg, field, &null_value TSRMLS_CC);
+      cached_zval = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, field));
+      ZVAL_ZVAL(cached_zval, value, 1, 0);
+      return;
+    }
+  }
+
+  if (Z_TYPE_P(cached_zval) == IS_OBJECT) {
+    const upb_msgdef* submsgdef = upb_fielddef_msgsubdef(field);
+    const upb_fielddef* value_field = upb_msgdef_itof(submsgdef, 1);
+    MessageHeader* submsg = UNBOX(MessageHeader, cached_zval);
+    CACHED_VALUE* cached_value = find_zval_property(submsg, value_field);
+    layout_set(submsg->descriptor->layout, submsg,
+               value_field, value TSRMLS_CC);
+  } else {
+    ZVAL_ZVAL(cached_zval, value, 1, 0);
+  }
+}
+
 PHP_METHOD(Message, readOneof) {
   PHP_PROTO_LONG index;
 
diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h
index 86bc5b3..6151ff4 100644
--- a/php/ext/google/protobuf/protobuf.h
+++ b/php/ext/google/protobuf/protobuf.h
@@ -965,6 +965,8 @@
 
 PHP_METHOD(Message, clear);
 PHP_METHOD(Message, mergeFrom);
+PHP_METHOD(Message, readWrapperValue);
+PHP_METHOD(Message, writeWrapperValue);
 PHP_METHOD(Message, readOneof);
 PHP_METHOD(Message, writeOneof);
 PHP_METHOD(Message, whichOneof);
@@ -1525,4 +1527,7 @@
 bool is_reserved_name(const char* name);
 bool is_valid_constant_name(const char* name);
 
+// For lazy wrapper
+bool is_wrapper_msg(const upb_msgdef* m);
+
 #endif  // __GOOGLE_PROTOBUF_PHP_PROTOBUF_H__
diff --git a/php/ext/google/protobuf/storage.c b/php/ext/google/protobuf/storage.c
index e6050d0..4a8543f 100644
--- a/php/ext/google/protobuf/storage.c
+++ b/php/ext/google/protobuf/storage.c
@@ -116,17 +116,17 @@
         return false;
       }
 
+#if PHP_MAJOR_VERSION < 7
+      REPLACE_ZVAL_VALUE((CACHED_VALUE*)memory, value, 1);
+#else
       zval* property_ptr = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
       if (EXPECTED(property_ptr != value)) {
         php_proto_zval_ptr_dtor(property_ptr);
       }
 
-#if PHP_MAJOR_VERSION < 7
-      DEREF(memory, zval*) = value;
-      Z_ADDREF_P(value);
-#else
       ZVAL_ZVAL(property_ptr, value, 1, 0);
 #endif
+
       break;
     }
 
@@ -797,25 +797,62 @@
   if (upb_fielddef_containingoneof(field)) {
     if (*oneof_case != upb_fielddef_number(field)) {
       native_slot_get_default(upb_fielddef_type(field), cache TSRMLS_CC);
-    } else {
-      upb_fieldtype_t type = upb_fielddef_type(field);
-      CACHED_VALUE* stored_cache = find_zval_property(header, field);
-      native_slot_get(
-          type, value_memory(type, memory, stored_cache), cache TSRMLS_CC);
+      return CACHED_PTR_TO_ZVAL_PTR(cache);
     }
-    return CACHED_PTR_TO_ZVAL_PTR(cache);
+    // Intentional fall through to be handled as a signuarl field.
   } else if (is_map_field(field)) {
     map_field_ensure_created(field, cache PHP_PROTO_TSRMLS_CC);
     return CACHED_PTR_TO_ZVAL_PTR(cache);
   } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
     repeated_field_ensure_created(field, cache PHP_PROTO_TSRMLS_CC);
     return CACHED_PTR_TO_ZVAL_PTR(cache);
+  }
+
+  CACHED_VALUE* stored_cache = find_zval_property(header, field);
+
+  if (upb_fielddef_type(field) == UPB_TYPE_MESSAGE &&
+      is_wrapper_msg(upb_fielddef_msgsubdef(field))) {
+    zval * cached_zval = CACHED_PTR_TO_ZVAL_PTR(stored_cache);
+#if PHP_MAJOR_VERSION >= 7
+    zend_object* obj;
+#endif
+    if (Z_TYPE_P(cached_zval) != IS_OBJECT &&
+        Z_TYPE_P(cached_zval) != IS_NULL) {
+      // Needs to expand value to wrapper.
+      const upb_msgdef* submsgdef = upb_fielddef_msgsubdef(field);
+      const upb_fielddef* value_field = upb_msgdef_itof(submsgdef, 1);
+      MessageHeader* submsg;
+      Descriptor* subdesc =
+          UNBOX_HASHTABLE_VALUE(
+              Descriptor, get_def_obj((void*)submsgdef));
+      zend_class_entry* subklass = subdesc->klass;
+#if PHP_MAJOR_VERSION < 7
+      zval* val = NULL;
+      MAKE_STD_ZVAL(val);
+      ZVAL_OBJ(val, subklass->create_object(subklass TSRMLS_CC));
+      submsg = UNBOX(MessageHeader, val);
+#else
+      obj = subklass->create_object(subklass TSRMLS_CC);
+      submsg = (MessageHeader*)((char*)obj - XtOffsetOf(MessageHeader, std));
+#endif
+      custom_data_init(subklass, submsg PHP_PROTO_TSRMLS_CC);
+
+      layout_set(subdesc->layout, submsg, value_field, cached_zval TSRMLS_CC);
+#if PHP_MAJOR_VERSION < 7
+      ZVAL_ZVAL(cached_zval, val, 1, 1);
+#else
+      ZVAL_OBJ(cached_zval, obj);
+#endif
+    }
+    if (stored_cache != cache) {
+      ZVAL_ZVAL(CACHED_PTR_TO_ZVAL_PTR(cache), cached_zval, 1, 0);
+    }
   } else {
     upb_fieldtype_t type = upb_fielddef_type(field);
-    native_slot_get(type, value_memory(type, memory, cache),
+    native_slot_get(type, value_memory(type, memory, stored_cache),
                     cache TSRMLS_CC);
-    return CACHED_PTR_TO_ZVAL_PTR(cache);
   }
+  return CACHED_PTR_TO_ZVAL_PTR(cache);
 }
 
 void layout_set(MessageLayout* layout, MessageHeader* header,
@@ -825,33 +862,6 @@
   uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
 
   if (upb_fielddef_containingoneof(field)) {
-    upb_fieldtype_t type = upb_fielddef_type(field);
-    zend_class_entry *ce = NULL;
-
-    // For non-singular fields, the related memory needs to point to the actual
-    // zval in properties table first.
-    switch (type) {
-      case UPB_TYPE_MESSAGE: {
-        const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
-        Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
-        ce = desc->klass;
-        // Intentionally fall through.
-      }
-      case UPB_TYPE_STRING:
-      case UPB_TYPE_BYTES: {
-        int property_cache_index =
-            header->descriptor->layout->fields[upb_fielddef_index(field)]
-                .cache_index;
-        DEREF(memory, CACHED_VALUE*) =
-            OBJ_PROP(&header->std, property_cache_index);
-        memory = DEREF(memory, CACHED_VALUE*);
-        break;
-      }
-      default:
-        break;
-    }
-
-    native_slot_set(type, ce, memory, val TSRMLS_CC);
     *oneof_case = upb_fielddef_number(field);
   } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
     // Works for both repeated and map fields
@@ -895,19 +905,20 @@
 #endif
       zval_dtor(&converted_value);
     }
-  } else {
-    upb_fieldtype_t type = upb_fielddef_type(field);
-    zend_class_entry *ce = NULL;
-    if (type == UPB_TYPE_MESSAGE) {
-      const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
-      Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
-      ce = desc->klass;
-    }
-    CACHED_VALUE* cache = find_zval_property(header, field);
-    native_slot_set(
-        type, ce, value_memory(upb_fielddef_type(field), memory, cache),
-        val TSRMLS_CC);
+    return;
   }
+
+  upb_fieldtype_t type = upb_fielddef_type(field);
+  zend_class_entry *ce = NULL;
+  if (type == UPB_TYPE_MESSAGE) {
+    const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
+    Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
+    ce = desc->klass;
+  }
+  CACHED_VALUE* cache = find_zval_property(header, field);
+  native_slot_set(
+      type, ce, value_memory(upb_fielddef_type(field), memory, cache),
+      val TSRMLS_CC);
 }
 
 static void native_slot_merge(
diff --git a/php/ext/google/protobuf/upb.c b/php/ext/google/protobuf/upb.c
index be9d730..9ec5bdf 100644
--- a/php/ext/google/protobuf/upb.c
+++ b/php/ext/google/protobuf/upb.c
@@ -116,7 +116,7 @@
 #ifdef __cplusplus
 #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) || \
     (defined(_MSC_VER) && _MSC_VER >= 1900)
-// C++11 is present
+/* C++11 is present */
 #else
 #error upb requires C++11 for C++ support
 #endif
@@ -724,6 +724,7 @@
     CHK(upb_append_unknown(d, frame));
     return true;
   }
+  UPB_UNREACHABLE();
 }
 
 static bool upb_decode_message(upb_decstate *d, char *msg, const upb_msglayout *l) {
@@ -7153,7 +7154,8 @@
         CHECK_SUSPEND(upb_sink_startsubmsg(outer->sink, arg, &d->top->sink));
       )
       VMCASE(OP_ENDSUBMSG,
-        CHECK_SUSPEND(upb_sink_endsubmsg(d->top->sink, arg));
+        upb_sink subsink = (d->top + 1)->sink;
+        CHECK_SUSPEND(upb_sink_endsubmsg(d->top->sink, subsink, arg));
       )
       VMCASE(OP_STARTSTR,
         uint32_t len = delim_remaining(d);
@@ -8430,7 +8432,7 @@
   return r;
 }
 
-#line 1 "upb/json/parser.rl"
+// #line 1 "upb/json/parser.rl"
 /*
 ** upb::json::Parser (upb_json_parser)
 **
@@ -10117,28 +10119,18 @@
   capture_begin(p, ptr);
 }
 
-#define EPOCH_YEAR 1970
-#define TM_YEAR_BASE 1900
-
-static bool isleap(int year) {
-  return (year % 4) == 0 && (year % 100 != 0 || (year % 400) == 0);
+static int div_round_up2(int n, int d) {
+  return (n + d - 1) / d;
 }
 
-const unsigned short int __mon_yday[2][13] = {
-    /* Normal years.  */
-    { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
-    /* Leap years.  */
-    { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
-};
-
 /* epoch_days(1970, 1, 1) == 1970-01-01 == 0. */
 static int epoch_days(int year, int month, int day) {
   static const uint16_t month_yday[12] = {0,   31,  59,  90,  120, 151,
                                           181, 212, 243, 273, 304, 334};
   int febs_since_0 = month > 2 ? year + 1 : year;
-  int leap_days_since_0 = div_round_up(febs_since_0, 4) -
-                          div_round_up(febs_since_0, 100) +
-                          div_round_up(febs_since_0, 400);
+  int leap_days_since_0 = div_round_up2(febs_since_0, 4) -
+                          div_round_up2(febs_since_0, 100) +
+                          div_round_up2(febs_since_0, 400);
   int days_since_0 =
       365 * year + month_yday[month - 1] + (day - 1) + leap_days_since_0;
 
@@ -10460,7 +10452,7 @@
     p->top--;
     ok = upb_handlers_getselector(mapfield, UPB_HANDLER_ENDSUBMSG, &sel);
     UPB_ASSERT(ok);
-    upb_sink_endsubmsg(p->top->sink, sel);
+    upb_sink_endsubmsg(p->top->sink, (p->top + 1)->sink, sel);
   }
 
   p->top->f = NULL;
@@ -10574,7 +10566,7 @@
     p->top--;
     if (!is_unknown) {
       sel = getsel_for_handlertype(p, UPB_HANDLER_ENDSUBMSG);
-      upb_sink_endsubmsg(p->top->sink, sel);
+      upb_sink_endsubmsg(p->top->sink, (p->top + 1)->sink, sel);
     }
   }
 }
@@ -11013,11 +11005,11 @@
  * final state once, when the closing '"' is seen. */
 
 
-#line 2794 "upb/json/parser.rl"
+// #line 2780 "upb/json/parser.rl"
 
 
 
-#line 2597 "upb/json/parser.c"
+// #line 2583 "upb/json/parser.c"
 static const char _json_actions[] = {
 	0, 1, 0, 1, 1, 1, 3, 1, 
 	4, 1, 6, 1, 7, 1, 8, 1, 
@@ -11272,7 +11264,7 @@
 static const int json_en_main = 1;
 
 
-#line 2797 "upb/json/parser.rl"
+// #line 2783 "upb/json/parser.rl"
 
 size_t parse(void *closure, const void *hd, const char *buf, size_t size,
              const upb_bufhandle *handle) {
@@ -11295,7 +11287,7 @@
   capture_resume(parser, buf);
 
   
-#line 2875 "upb/json/parser.c"
+// #line 2861 "upb/json/parser.c"
 	{
 	int _klen;
 	unsigned int _trans;
@@ -11370,147 +11362,147 @@
 		switch ( *_acts++ )
 		{
 	case 1:
-#line 2602 "upb/json/parser.rl"
+// #line 2588 "upb/json/parser.rl"
 	{ p--; {cs = stack[--top]; goto _again;} }
 	break;
 	case 2:
-#line 2604 "upb/json/parser.rl"
+// #line 2590 "upb/json/parser.rl"
 	{ p--; {stack[top++] = cs; cs = 23;goto _again;} }
 	break;
 	case 3:
-#line 2608 "upb/json/parser.rl"
+// #line 2594 "upb/json/parser.rl"
 	{ start_text(parser, p); }
 	break;
 	case 4:
-#line 2609 "upb/json/parser.rl"
+// #line 2595 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_text(parser, p)); }
 	break;
 	case 5:
-#line 2615 "upb/json/parser.rl"
+// #line 2601 "upb/json/parser.rl"
 	{ start_hex(parser); }
 	break;
 	case 6:
-#line 2616 "upb/json/parser.rl"
+// #line 2602 "upb/json/parser.rl"
 	{ hexdigit(parser, p); }
 	break;
 	case 7:
-#line 2617 "upb/json/parser.rl"
+// #line 2603 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_hex(parser)); }
 	break;
 	case 8:
-#line 2623 "upb/json/parser.rl"
+// #line 2609 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(escape(parser, p)); }
 	break;
 	case 9:
-#line 2629 "upb/json/parser.rl"
+// #line 2615 "upb/json/parser.rl"
 	{ p--; {cs = stack[--top]; goto _again;} }
 	break;
 	case 10:
-#line 2634 "upb/json/parser.rl"
+// #line 2620 "upb/json/parser.rl"
 	{ start_year(parser, p); }
 	break;
 	case 11:
-#line 2635 "upb/json/parser.rl"
+// #line 2621 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_year(parser, p)); }
 	break;
 	case 12:
-#line 2639 "upb/json/parser.rl"
+// #line 2625 "upb/json/parser.rl"
 	{ start_month(parser, p); }
 	break;
 	case 13:
-#line 2640 "upb/json/parser.rl"
+// #line 2626 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_month(parser, p)); }
 	break;
 	case 14:
-#line 2644 "upb/json/parser.rl"
+// #line 2630 "upb/json/parser.rl"
 	{ start_day(parser, p); }
 	break;
 	case 15:
-#line 2645 "upb/json/parser.rl"
+// #line 2631 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_day(parser, p)); }
 	break;
 	case 16:
-#line 2649 "upb/json/parser.rl"
+// #line 2635 "upb/json/parser.rl"
 	{ start_hour(parser, p); }
 	break;
 	case 17:
-#line 2650 "upb/json/parser.rl"
+// #line 2636 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_hour(parser, p)); }
 	break;
 	case 18:
-#line 2654 "upb/json/parser.rl"
+// #line 2640 "upb/json/parser.rl"
 	{ start_minute(parser, p); }
 	break;
 	case 19:
-#line 2655 "upb/json/parser.rl"
+// #line 2641 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_minute(parser, p)); }
 	break;
 	case 20:
-#line 2659 "upb/json/parser.rl"
+// #line 2645 "upb/json/parser.rl"
 	{ start_second(parser, p); }
 	break;
 	case 21:
-#line 2660 "upb/json/parser.rl"
+// #line 2646 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_second(parser, p)); }
 	break;
 	case 22:
-#line 2665 "upb/json/parser.rl"
+// #line 2651 "upb/json/parser.rl"
 	{ start_duration_base(parser, p); }
 	break;
 	case 23:
-#line 2666 "upb/json/parser.rl"
+// #line 2652 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_duration_base(parser, p)); }
 	break;
 	case 24:
-#line 2668 "upb/json/parser.rl"
+// #line 2654 "upb/json/parser.rl"
 	{ p--; {cs = stack[--top]; goto _again;} }
 	break;
 	case 25:
-#line 2673 "upb/json/parser.rl"
+// #line 2659 "upb/json/parser.rl"
 	{ start_timestamp_base(parser); }
 	break;
 	case 26:
-#line 2675 "upb/json/parser.rl"
+// #line 2661 "upb/json/parser.rl"
 	{ start_timestamp_fraction(parser, p); }
 	break;
 	case 27:
-#line 2676 "upb/json/parser.rl"
+// #line 2662 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_timestamp_fraction(parser, p)); }
 	break;
 	case 28:
-#line 2678 "upb/json/parser.rl"
+// #line 2664 "upb/json/parser.rl"
 	{ start_timestamp_zone(parser, p); }
 	break;
 	case 29:
-#line 2679 "upb/json/parser.rl"
+// #line 2665 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_timestamp_zone(parser, p)); }
 	break;
 	case 30:
-#line 2681 "upb/json/parser.rl"
+// #line 2667 "upb/json/parser.rl"
 	{ p--; {cs = stack[--top]; goto _again;} }
 	break;
 	case 31:
-#line 2686 "upb/json/parser.rl"
+// #line 2672 "upb/json/parser.rl"
 	{ start_fieldmask_path_text(parser, p); }
 	break;
 	case 32:
-#line 2687 "upb/json/parser.rl"
+// #line 2673 "upb/json/parser.rl"
 	{ end_fieldmask_path_text(parser, p); }
 	break;
 	case 33:
-#line 2692 "upb/json/parser.rl"
+// #line 2678 "upb/json/parser.rl"
 	{ start_fieldmask_path(parser); }
 	break;
 	case 34:
-#line 2693 "upb/json/parser.rl"
+// #line 2679 "upb/json/parser.rl"
 	{ end_fieldmask_path(parser); }
 	break;
 	case 35:
-#line 2699 "upb/json/parser.rl"
+// #line 2685 "upb/json/parser.rl"
 	{ p--; {cs = stack[--top]; goto _again;} }
 	break;
 	case 36:
-#line 2704 "upb/json/parser.rl"
+// #line 2690 "upb/json/parser.rl"
 	{
         if (is_wellknown_msg(parser, UPB_WELLKNOWN_TIMESTAMP)) {
           {stack[top++] = cs; cs = 47;goto _again;}
@@ -11524,11 +11516,11 @@
       }
 	break;
 	case 37:
-#line 2717 "upb/json/parser.rl"
+// #line 2703 "upb/json/parser.rl"
 	{ p--; {stack[top++] = cs; cs = 78;goto _again;} }
 	break;
 	case 38:
-#line 2722 "upb/json/parser.rl"
+// #line 2708 "upb/json/parser.rl"
 	{
         if (is_wellknown_msg(parser, UPB_WELLKNOWN_ANY)) {
           start_any_member(parser, p);
@@ -11538,11 +11530,11 @@
       }
 	break;
 	case 39:
-#line 2729 "upb/json/parser.rl"
+// #line 2715 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_membername(parser)); }
 	break;
 	case 40:
-#line 2732 "upb/json/parser.rl"
+// #line 2718 "upb/json/parser.rl"
 	{
         if (is_wellknown_msg(parser, UPB_WELLKNOWN_ANY)) {
           end_any_member(parser, p);
@@ -11552,7 +11544,7 @@
       }
 	break;
 	case 41:
-#line 2743 "upb/json/parser.rl"
+// #line 2729 "upb/json/parser.rl"
 	{
         if (is_wellknown_msg(parser, UPB_WELLKNOWN_ANY)) {
           start_any_object(parser, p);
@@ -11562,7 +11554,7 @@
       }
 	break;
 	case 42:
-#line 2752 "upb/json/parser.rl"
+// #line 2738 "upb/json/parser.rl"
 	{
         if (is_wellknown_msg(parser, UPB_WELLKNOWN_ANY)) {
           CHECK_RETURN_TOP(end_any_object(parser, p));
@@ -11572,54 +11564,54 @@
       }
 	break;
 	case 43:
-#line 2764 "upb/json/parser.rl"
+// #line 2750 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(start_array(parser)); }
 	break;
 	case 44:
-#line 2768 "upb/json/parser.rl"
+// #line 2754 "upb/json/parser.rl"
 	{ end_array(parser); }
 	break;
 	case 45:
-#line 2773 "upb/json/parser.rl"
+// #line 2759 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(start_number(parser, p)); }
 	break;
 	case 46:
-#line 2774 "upb/json/parser.rl"
+// #line 2760 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_number(parser, p)); }
 	break;
 	case 47:
-#line 2776 "upb/json/parser.rl"
+// #line 2762 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(start_stringval(parser)); }
 	break;
 	case 48:
-#line 2777 "upb/json/parser.rl"
+// #line 2763 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_stringval(parser)); }
 	break;
 	case 49:
-#line 2779 "upb/json/parser.rl"
+// #line 2765 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_bool(parser, true)); }
 	break;
 	case 50:
-#line 2781 "upb/json/parser.rl"
+// #line 2767 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_bool(parser, false)); }
 	break;
 	case 51:
-#line 2783 "upb/json/parser.rl"
+// #line 2769 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_null(parser)); }
 	break;
 	case 52:
-#line 2785 "upb/json/parser.rl"
+// #line 2771 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(start_subobject_full(parser)); }
 	break;
 	case 53:
-#line 2786 "upb/json/parser.rl"
+// #line 2772 "upb/json/parser.rl"
 	{ end_subobject_full(parser); }
 	break;
 	case 54:
-#line 2791 "upb/json/parser.rl"
+// #line 2777 "upb/json/parser.rl"
 	{ p--; {cs = stack[--top]; goto _again;} }
 	break;
-#line 3199 "upb/json/parser.c"
+// #line 3185 "upb/json/parser.c"
 		}
 	}
 
@@ -11636,32 +11628,32 @@
 	while ( __nacts-- > 0 ) {
 		switch ( *__acts++ ) {
 	case 0:
-#line 2600 "upb/json/parser.rl"
+// #line 2586 "upb/json/parser.rl"
 	{ p--; {cs = stack[--top]; 	if ( p == pe )
 		goto _test_eof;
 goto _again;} }
 	break;
 	case 46:
-#line 2774 "upb/json/parser.rl"
+// #line 2760 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_number(parser, p)); }
 	break;
 	case 49:
-#line 2779 "upb/json/parser.rl"
+// #line 2765 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_bool(parser, true)); }
 	break;
 	case 50:
-#line 2781 "upb/json/parser.rl"
+// #line 2767 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_bool(parser, false)); }
 	break;
 	case 51:
-#line 2783 "upb/json/parser.rl"
+// #line 2769 "upb/json/parser.rl"
 	{ CHECK_RETURN_TOP(end_null(parser)); }
 	break;
 	case 53:
-#line 2786 "upb/json/parser.rl"
+// #line 2772 "upb/json/parser.rl"
 	{ end_subobject_full(parser); }
 	break;
-#line 3241 "upb/json/parser.c"
+// #line 3227 "upb/json/parser.c"
 		}
 	}
 	}
@@ -11669,7 +11661,7 @@
 	_out: {}
 	}
 
-#line 2819 "upb/json/parser.rl"
+// #line 2805 "upb/json/parser.rl"
 
   if (p != pe) {
     upb_status_seterrf(parser->status, "Parse error at '%.*s'\n", pe - p, p);
@@ -11712,13 +11704,13 @@
 
   /* Emit Ragel initialization of the parser. */
   
-#line 3292 "upb/json/parser.c"
+// #line 3278 "upb/json/parser.c"
 	{
 	cs = json_start;
 	top = 0;
 	}
 
-#line 2861 "upb/json/parser.rl"
+// #line 2847 "upb/json/parser.rl"
   p->current_state = cs;
   p->parser_top = top;
   accumulate_clear(p);
diff --git a/php/ext/google/protobuf/upb.h b/php/ext/google/protobuf/upb.h
index 0a55baf..0587727 100644
--- a/php/ext/google/protobuf/upb.h
+++ b/php/ext/google/protobuf/upb.h
@@ -122,7 +122,7 @@
 #ifdef __cplusplus
 #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) || \
     (defined(_MSC_VER) && _MSC_VER >= 1900)
-// C++11 is present
+/* C++11 is present */
 #else
 #error upb requires C++11 for C++ support
 #endif
@@ -5700,15 +5700,16 @@
   return sub->closure ? true : false;
 }
 
-UPB_INLINE bool upb_sink_endsubmsg(upb_sink s, upb_selector_t sel) {
+UPB_INLINE bool upb_sink_endsubmsg(upb_sink s, upb_sink sub,
+                                   upb_selector_t sel) {
   typedef upb_endfield_handlerfunc func;
   func *endsubmsg;
   const void *hd;
   if (!s.handlers) return true;
   endsubmsg = (func*)upb_handlers_gethandler(s.handlers, sel, &hd);
 
-  if (!endsubmsg) return s.closure;
-  return endsubmsg(s.closure, hd);
+  if (!endsubmsg) return true;
+  return endsubmsg(sub.closure, hd);
 }
 
 #ifdef __cplusplus
@@ -5874,8 +5875,8 @@
     return ret;
   }
 
-  bool EndSubMessage(HandlersPtr::Selector s) {
-    return upb_sink_endsubmsg(sink_, s);
+  bool EndSubMessage(HandlersPtr::Selector s, Sink sub) {
+    return upb_sink_endsubmsg(sink_, sub.sink_, s);
   }
 
   /* For repeated fields of any type, the sequence of values must be wrapped in
diff --git a/php/src/Google/Protobuf/Internal/Message.php b/php/src/Google/Protobuf/Internal/Message.php
index 1ff2dc9..1ffb245 100644
--- a/php/src/Google/Protobuf/Internal/Message.php
+++ b/php/src/Google/Protobuf/Internal/Message.php
@@ -175,6 +175,42 @@
         }
     }
 
+    protected function readWrapperValue($member)
+    {
+        $field = $this->desc->getFieldByName($member);
+        $oneof_index = $field->getOneofIndex();
+        if ($oneof_index === -1) {
+            $wrapper = $this->$member;
+        } else {
+            $wrapper = $this->readOneof($field->getNumber());
+        }
+
+        if (is_null($wrapper)) {
+            return NULL;
+        } else {
+            return $wrapper->getValue();
+        }
+    }
+
+    protected function writeWrapperValue($member, $value)
+    {
+        $field = $this->desc->getFieldByName($member);
+        $wrapped_value = $value;
+        if (!is_null($value)) {
+            $desc = $field->getMessageType();
+            $klass = $desc->getClass();
+            $wrapped_value = new $klass;
+            $wrapped_value->setValue($value);
+        }
+
+        $oneof_index = $field->getOneofIndex();
+        if ($oneof_index === -1) {
+            $this->$member = $wrapped_value;
+        } else {
+            $this->writeOneof($field->getNumber(), $wrapped_value);
+        }
+    }
+
     protected function readOneof($number)
     {
         $field = $this->desc->getFieldByNumber($number);
diff --git a/php/tests/encode_decode_test.php b/php/tests/encode_decode_test.php
index 3dad5f3..53cd526 100644
--- a/php/tests/encode_decode_test.php
+++ b/php/tests/encode_decode_test.php
@@ -5,7 +5,13 @@
 
 use Google\Protobuf\RepeatedField;
 use Google\Protobuf\GPBType;
+use Foo\TestInt32Value;
+use Foo\TestInt64Value;
+use Foo\TestUInt32Value;
+use Foo\TestUInt64Value;
+use Foo\TestBoolValue;
 use Foo\TestStringValue;
+use Foo\TestBytesValue;
 use Foo\TestAny;
 use Foo\TestEnum;
 use Foo\TestMessage;
@@ -98,6 +104,13 @@
         $this->assertSame("1", $m->serializeToJsonString());
     }
 
+    public function testDecodeRepeatedInt32Value()
+    {
+        $m = new TestInt32Value();
+        $m->mergeFromJsonString("{\"repeated_field\":[12345]}");
+        $this->assertSame(12345, $m->getRepeatedField()[0]->getValue());
+    }
+
     public function testDecodeTopLevelUInt32Value()
     {
         $m = new UInt32Value();
@@ -168,12 +181,18 @@
         $this->assertSame("\"a\"", $m->serializeToJsonString());
     }
 
-    public function testEncodeStringValue()
+    public function testDecodeRepeatedStringValue()
     {
-        $m = new TestStringValue(['field' => new StringValue(['value' => ''])]);
-        var_dump($m->getField());
-        var_dump($m->serializeToJsonString());
-        $this->assertSame("{\"field\":\"\"}", $m->serializeToJsonString());
+        $m = new TestStringValue();
+        $m->mergeFromJsonString("{\"repeated_field\":[\"a\"]}");
+        $this->assertSame("a", $m->getRepeatedField()[0]->getValue());
+    }
+
+    public function testDecodeMapStringValue()
+    {
+        $m = new TestStringValue();
+        $m->mergeFromJsonString("{\"map_field\":{\"1\": \"a\"}}");
+        $this->assertSame("a", $m->getMapField()[1]->getValue());
     }
 
     public function testDecodeTopLevelBytesValue()
@@ -1230,4 +1249,195 @@
         $this->assertTrue(true);
     }
 
+    /**
+     * @dataProvider wrappersDataProvider
+     */
+    public function testWrapperJsonDecodeAndGet(
+        $class,
+        $nonDefaultValue,
+        $nonDefaultValueData,
+        $defaultValue,
+        $defaultValueData
+    )
+    {
+        // Singular with non-default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"field\":" . $nonDefaultValueData . "}");
+        $wrapper = $m->getField();
+        $this->assertEquals($nonDefaultValue, $wrapper->getValue());
+
+        // Singular with default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"field\":" . $defaultValueData . "}");
+        $wrapper = $m->getField();
+        $this->assertEquals($defaultValue, $wrapper->getValue());
+
+        // Repeated with empty
+        $m = new $class();
+        $m->mergeFromJsonString("{\"repeated_field\":[]}");
+        $repeatedWrapper = $m->getRepeatedField();
+        $this->assertSame(0, count($repeatedWrapper));
+
+        // Repeated with non-default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"repeated_field\":[" . $defaultValueData . "]}");
+        $repeatedWrapper = $m->getRepeatedField();
+        $this->assertSame(1, count($repeatedWrapper));
+        $this->assertEquals($defaultValue, $repeatedWrapper[0]->getValue());
+
+        // Repeated with default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"repeated_field\":[" . $defaultValueData . "]}");
+        $repeatedWrapper = $m->getRepeatedField();
+        $this->assertSame(1, count($repeatedWrapper));
+        $this->assertEquals($defaultValue, $repeatedWrapper[0]->getValue());
+
+        // Oneof with non-default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"oneof_field\":" . $nonDefaultValueData . "}");
+        $wrapper = $m->getOneofField();
+        $this->assertEquals($nonDefaultValue, $wrapper->getValue());
+        $this->assertEquals("oneof_field", $m->getOneofFields());
+        $this->assertEquals(0, $m->getInt32Field());
+
+        // Oneof with default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"oneof_field\":" . $defaultValueData . "}");
+        $wrapper = $m->getOneofField();
+        $this->assertEquals($defaultValue, $wrapper->getValue());
+        $this->assertEquals("oneof_field", $m->getOneofFields());
+        $this->assertEquals(0, $m->getInt32Field());
+    }
+
+    /**
+     * @dataProvider wrappersDataProvider
+     */
+    public function testWrapperJsonDecodeAndGetUnwrapped(
+        $class,
+        $nonDefaultValue,
+        $nonDefaultValueData,
+        $defaultValue,
+        $defaultValueData
+    )
+    {
+        // Singular with non-default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"field\":" . $nonDefaultValueData . "}");
+        $this->assertEquals($nonDefaultValue, $m->getFieldUnwrapped());
+
+        // Singular with default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"field\":" . $defaultValueData . "}");
+        $this->assertEquals($defaultValue, $m->getFieldUnwrapped());
+
+        // Oneof with non-default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"oneof_field\":" . $nonDefaultValueData . "}");
+        $this->assertEquals($nonDefaultValue, $m->getOneofFieldUnwrapped());
+        $this->assertEquals("oneof_field", $m->getOneofFields());
+        $this->assertEquals(0, $m->getInt32Field());
+
+        // Oneof with default
+        $m = new $class();
+        $m->mergeFromJsonString("{\"oneof_field\":" . $defaultValueData . "}");
+        $this->assertEquals($defaultValue, $m->getOneofFieldUnwrapped());
+        $this->assertEquals("oneof_field", $m->getOneofFields());
+        $this->assertEquals(0, $m->getInt32Field());
+    }
+
+    /**
+     * @dataProvider wrappersDataProvider
+     */
+    public function testWrapperJsonDecodeEncode(
+        $class,
+        $nonDefaultValue,
+        $nonDefaultValueData,
+        $defaultValue,
+        $defaultValueData
+    )
+    {
+        // Singular with non-default
+        $from = new $class();
+        $to = new $class();
+        $from->mergeFromJsonString("{\"field\":" . $nonDefaultValueData . "}");
+        $data = $from->serializeToJsonString();
+        $to->mergeFromJsonString($data);
+        $this->assertEquals($nonDefaultValue, $to->getFieldUnwrapped());
+
+        // Singular with default
+        $from = new $class();
+        $to = new $class();
+        $from->mergeFromJsonString("{\"field\":" . $defaultValueData . "}");
+        $data = $from->serializeToJsonString();
+        $to->mergeFromJsonString($data);
+        $this->assertEquals($defaultValue, $to->getFieldUnwrapped());
+
+        // Oneof with non-default
+        $from = new $class();
+        $to = new $class();
+        $from->mergeFromJsonString("{\"oneof_field\":" . $nonDefaultValueData . "}");
+        $data = $from->serializeToJsonString();
+        $to->mergeFromJsonString($data);
+        $this->assertEquals($nonDefaultValue, $to->getOneofFieldUnwrapped());
+
+        // Oneof with default
+        $from = new $class();
+        $to = new $class();
+        $from->mergeFromJsonString("{\"oneof_field\":" . $defaultValueData . "}");
+        $data = $from->serializeToJsonString();
+        $to->mergeFromJsonString($data);
+        $this->assertEquals($defaultValue, $to->getOneofFieldUnwrapped());
+    }
+
+    /**
+     * @dataProvider wrappersDataProvider
+     */
+    public function testWrapperSetUnwrappedJsonEncode(
+        $class,
+        $nonDefaultValue,
+        $nonDefaultValueData,
+        $defaultValue,
+        $defaultValueData
+    )
+    {
+        // Singular with non-default
+        $from = new $class();
+        $to = new $class();
+        $from->setFieldUnwrapped($nonDefaultValue);
+        $data = $from->serializeToJsonString();
+        $to->mergeFromJsonString($data);
+        $this->assertEquals($nonDefaultValue, $to->getFieldUnwrapped());
+
+        // Singular with default
+        $from = new $class();
+        $to = new $class();
+        $from->setFieldUnwrapped($defaultValue);
+        $data = $from->serializeToJsonString();
+        $to->mergeFromJsonString($data);
+        $this->assertEquals($defaultValue, $to->getFieldUnwrapped());
+
+        // Oneof with non-default
+        $from = new $class();
+        $to = new $class();
+        $from->setOneofFieldUnwrapped($nonDefaultValue);
+        $data = $from->serializeToJsonString();
+        $to->mergeFromJsonString($data);
+        $this->assertEquals($nonDefaultValue, $to->getOneofFieldUnwrapped());
+
+        // Oneof with default
+        $from = new $class();
+        $to = new $class();
+        $from->setOneofFieldUnwrapped($defaultValue);
+        $data = $from->serializeToJsonString();
+        $to->mergeFromJsonString($data);
+        $this->assertEquals($defaultValue, $to->getOneofFieldUnwrapped());
+    }
+
+    public function wrappersDataProvider()
+    {
+        return [
+            [TestInt32Value::class, 1, "1", 0, "0"],
+            [TestStringValue::class, "a", "\"a\"", "", "\"\""],
+        ];
+    }
 }
diff --git a/php/tests/memory_leak_test.php b/php/tests/memory_leak_test.php
index 2873f34..a9c292d 100644
--- a/php/tests/memory_leak_test.php
+++ b/php/tests/memory_leak_test.php
@@ -20,9 +20,13 @@
 require_once('generated/Foo/PBARRAY.php');
 require_once('generated/Foo/PBEmpty.php');
 require_once('generated/Foo/TestAny.php');
+require_once('generated/Foo/TestBoolValue.php');
+require_once('generated/Foo/TestBytesValue.php');
 require_once('generated/Foo/TestEnum.php');
 require_once('generated/Foo/TestIncludeNamespaceMessage.php');
 require_once('generated/Foo/TestIncludePrefixMessage.php');
+require_once('generated/Foo/TestInt32Value.php');
+require_once('generated/Foo/TestInt64Value.php');
 require_once('generated/Foo/TestMessage.php');
 require_once('generated/Foo/TestMessage/PBEmpty.php');
 require_once('generated/Foo/TestMessage/NestedEnum.php');
@@ -32,6 +36,8 @@
 require_once('generated/Foo/TestRandomFieldOrder.php');
 require_once('generated/Foo/TestReverseFieldOrder.php');
 require_once('generated/Foo/TestStringValue.php');
+require_once('generated/Foo/TestUInt32Value.php');
+require_once('generated/Foo/TestUInt64Value.php');
 require_once('generated/Foo/TestUnpackedMessage.php');
 require_once('generated/Foo/testLowerCaseMessage.php');
 require_once('generated/Foo/testLowerCaseEnum.php');
diff --git a/php/tests/proto/test.proto b/php/tests/proto/test.proto
index 6821858..9505709 100644
--- a/php/tests/proto/test.proto
+++ b/php/tests/proto/test.proto
@@ -215,6 +215,66 @@
   google.protobuf.Any any = 1;
 }
 
+message TestInt32Value {
+  google.protobuf.Int32Value field = 1;
+  repeated google.protobuf.Int32Value repeated_field = 2;
+  oneof oneof_fields {
+    google.protobuf.Int32Value oneof_field = 3;
+    int32 int32_field = 4;
+  }
+}
+
+message TestInt64Value {
+  google.protobuf.Int64Value field = 1;
+  repeated google.protobuf.Int64Value repeated_field = 2;
+  oneof oneof_fields {
+    google.protobuf.Int64Value oneof_field = 3;
+    int32 int32_field = 4;
+  }
+}
+
+message TestUInt32Value {
+  google.protobuf.UInt32Value field = 1;
+  repeated google.protobuf.UInt32Value repeated_field = 2;
+  oneof oneof_fields {
+    google.protobuf.UInt32Value oneof_field = 3;
+    int32 int32_field = 4;
+  }
+}
+
+message TestUInt64Value {
+  google.protobuf.UInt64Value field = 1;
+  repeated google.protobuf.UInt64Value repeated_field = 2;
+  oneof oneof_fields {
+    google.protobuf.UInt64Value oneof_field = 3;
+    int32 int32_field = 4;
+  }
+}
+
+message TestBoolValue {
+  google.protobuf.BoolValue field = 1;
+  repeated google.protobuf.BoolValue repeated_field = 2;
+  oneof oneof_fields {
+    google.protobuf.BoolValue oneof_field = 3;
+    int32 int32_field = 4;
+  }
+}
+
 message TestStringValue {
   google.protobuf.StringValue field = 1;
+  repeated google.protobuf.StringValue repeated_field = 2;
+  oneof oneof_fields {
+    google.protobuf.StringValue oneof_field = 3;
+    int32 int32_field = 4;
+  }
+  map<int32, google.protobuf.StringValue> map_field = 5;
+}
+
+message TestBytesValue {
+  google.protobuf.BytesValue field = 1;
+  repeated google.protobuf.BytesValue repeated_field = 2;
+  oneof oneof_fields {
+    google.protobuf.BytesValue oneof_field = 3;
+    int32 int32_field = 4;
+  }
 }
diff --git a/php/tests/wrapper_type_setters_test.php b/php/tests/wrapper_type_setters_test.php
index ad9f718..e4bdfcf 100644
--- a/php/tests/wrapper_type_setters_test.php
+++ b/php/tests/wrapper_type_setters_test.php
@@ -132,7 +132,8 @@
                 [2.2, new DoubleValue(["value" => 2.2])],
                 [null, null],
                 [0, new DoubleValue()],
-            ]],[TestWrapperSetters::class, StringValue::class, "setStringValueOneof", "setStringValueOneofUnwrapped", "getStringValueOneof", "getStringValueOneofUnwrapped", [
+            ]],
+            [TestWrapperSetters::class, StringValue::class, "setStringValueOneof", "setStringValueOneofUnwrapped", "getStringValueOneof", "getStringValueOneofUnwrapped", [
                 ["asdf", new StringValue(["value" => "asdf"])],
                 ["", new StringValue(["value" => ""])],
                 [null, null],
diff --git a/src/google/protobuf/compiler/php/php_generator.cc b/src/google/protobuf/compiler/php/php_generator.cc
index 29a5ac2..55b3de2 100644
--- a/src/google/protobuf/compiler/php/php_generator.cc
+++ b/src/google/protobuf/compiler/php/php_generator.cc
@@ -628,7 +628,7 @@
   } else {
     GenerateFieldDocComment(printer, field, is_descriptor, kFieldProperty);
     printer->Print(
-        "private $^name^ = ^default^;\n",
+        "protected $^name^ = ^default^;\n",
         "name", field->name(),
         "default", DefaultForField(field));
   }
@@ -682,10 +682,10 @@
     printer->Print(
         "public function get^camel_name^Unwrapped()\n"
         "{\n"
-        "    $wrapper = $this->get^camel_name^();\n"
-        "    return is_null($wrapper) ? null : $wrapper->getValue();\n"
+        "    return $this->readWrapperValue(\"^field_name^\");\n"
         "}\n\n",
-        "camel_name", UnderscoresToCamelCase(field->name(), true));
+        "camel_name", UnderscoresToCamelCase(field->name(), true),
+        "field_name", field->name());
   }
 
   // Generate setter.
@@ -795,11 +795,11 @@
     printer->Print(
         "public function set^camel_name^Unwrapped($var)\n"
         "{\n"
-        "    $wrappedVar = is_null($var) ? null : new \\^wrapper_type^(['value' => $var]);\n"
-        "    return $this->set^camel_name^($wrappedVar);\n"
+        "    $this->writeWrapperValue(\"^field_name^\", $var);\n"
+        "    return $this;"
         "}\n\n",
         "camel_name", UnderscoresToCamelCase(field->name(), true),
-        "wrapper_type", LegacyFullClassName(field->message_type(), is_descriptor));
+        "field_name", field->name());
   }
 
   // Generate has method for proto2 only.