Selaa lähdekoodia

Check in php implementation. (#2052)

This pull request includes two implementation: C extension and PHP
package. Both implementations support encode/decode of singular,
repeated and map fields.
Paul Yang 9 vuotta sitten
vanhempi
commit
e0e54661f7
58 muutettua tiedostoa jossa 17000 lisäystä ja 1316 poistoa
  1. 15 0
      .gitignore
  2. 1 1
      README.md
  3. 1 0
      cmake/extract_includes.bat.in
  4. 1 0
      cmake/libprotoc.cmake
  5. 24 0
      composer.json
  6. 99 0
      php/README.md
  7. 388 0
      php/ext/google/protobuf/array.c
  8. 4 4
      php/ext/google/protobuf/config.m4
  9. 343 266
      php/ext/google/protobuf/def.c
  10. 1245 0
      php/ext/google/protobuf/encode_decode.c
  11. 470 0
      php/ext/google/protobuf/map.c
  12. 150 176
      php/ext/google/protobuf/message.c
  13. 74 0
      php/ext/google/protobuf/package.xml
  14. 114 36
      php/ext/google/protobuf/protobuf.c
  15. 339 166
      php/ext/google/protobuf/protobuf.h
  16. 332 312
      php/ext/google/protobuf/storage.c
  17. 0 15
      php/ext/google/protobuf/test.php
  18. 310 0
      php/ext/google/protobuf/type_check.c
  19. 365 123
      php/ext/google/protobuf/upb.c
  20. 612 207
      php/ext/google/protobuf/upb.h
  21. 68 0
      php/ext/google/protobuf/utf8.c
  22. 36 0
      php/ext/google/protobuf/utf8.h
  23. 162 0
      php/src/Google/Protobuf/Internal/DescriptorPool.php
  24. 63 0
      php/src/Google/Protobuf/Internal/EnumBuilderContext.php
  25. 40 0
      php/src/Google/Protobuf/Internal/GPBLabel.php
  26. 55 0
      php/src/Google/Protobuf/Internal/GPBType.php
  27. 161 0
      php/src/Google/Protobuf/Internal/GPBUtil.php
  28. 583 0
      php/src/Google/Protobuf/Internal/GPBWire.php
  29. 323 0
      php/src/Google/Protobuf/Internal/InputStream.php
  30. 57 0
      php/src/Google/Protobuf/Internal/MapEntry.php
  31. 321 0
      php/src/Google/Protobuf/Internal/MapField.php
  32. 671 0
      php/src/Google/Protobuf/Internal/Message.php
  33. 120 0
      php/src/Google/Protobuf/Internal/MessageBuilderContext.php
  34. 77 0
      php/src/Google/Protobuf/Internal/OneofField.php
  35. 143 0
      php/src/Google/Protobuf/Internal/OutputStream.php
  36. 303 0
      php/src/Google/Protobuf/Internal/RepeatedField.php
  37. 175 0
      php/src/Google/Protobuf/Internal/Type.php
  38. 541 0
      php/src/Google/Protobuf/descriptor.php
  39. 2532 0
      php/src/Google/Protobuf/descriptor_internal.pb.php
  40. 15 0
      php/src/phpdoc.dist.xml
  41. 888 0
      php/tests/array_test.php
  42. 0 4
      php/tests/autoload.php
  43. 136 0
      php/tests/encode_decode_test.php
  44. 557 0
      php/tests/generated_class_test.php
  45. 648 0
      php/tests/map_field_test.php
  46. 73 0
      php/tests/memory_leak_test.php
  47. 443 0
      php/tests/php_implementation_test.php
  48. 1385 0
      php/tests/test.pb.php
  49. 136 0
      php/tests/test.proto
  50. 23 0
      php/tests/test.sh
  51. 92 0
      php/tests/test_base.php
  52. 36 0
      php/tests/test_include.pb.php
  53. 393 0
      php/tests/test_util.php
  54. 13 0
      phpunit.xml
  55. 2 0
      src/Makefile.am
  56. 4 6
      src/google/protobuf/compiler/main.cc
  57. 781 0
      src/google/protobuf/compiler/php/php_generator.cc
  58. 57 0
      src/google/protobuf/compiler/php/php_generator.h

+ 15 - 0
.gitignore

@@ -118,3 +118,18 @@ conformance/lite/
 conformance/nonexistent_tests.txt
 conformance/protoc_middleman
 conformance/succeeding_tests.txt
+
+# php test output
+composer.lock
+php/ext/google/protobuf/.libs/
+php/ext/google/protobuf/Makefile.fragments
+php/ext/google/protobuf/Makefile.global
+php/ext/google/protobuf/Makefile.objects
+php/ext/google/protobuf/acinclude.m4
+php/ext/google/protobuf/build/
+php/ext/google/protobuf/config.h
+php/ext/google/protobuf/config.nice
+php/ext/google/protobuf/configure.in
+php/ext/google/protobuf/mkinstalldirs
+php/ext/google/protobuf/run-tests.php
+vendor/

+ 1 - 1
README.md

@@ -65,7 +65,7 @@ how to install protobuf runtime for that specific language:
 | JavaScript                           | [js](js)                                              |
 | Ruby                                 | [ruby](ruby)                                          |
 | Go                                   | [golang/protobuf](https://github.com/golang/protobuf) |
-| PHP                                  | TBD                                                   |
+| PHP                                  | [php](php)                                            |
 
 
 Usage

+ 1 - 0
cmake/extract_includes.bat.in

@@ -32,6 +32,7 @@ copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\js\js_generat
 copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\objectivec\objectivec_generator.h include\google\protobuf\compiler\objectivec\objectivec_generator.h
 copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\objectivec\objectivec_helpers.h include\google\protobuf\compiler\objectivec\objectivec_helpers.h
 copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\parser.h include\google\protobuf\compiler\parser.h
+copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\php\php_generator.h include\google\protobuf\compiler\php\php_generator.h
 copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\plugin.h include\google\protobuf\compiler\plugin.h
 copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\plugin.pb.h include\google\protobuf\compiler\plugin.pb.h
 copy ${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\python\python_generator.h include\google\protobuf\compiler\python\python_generator.h

+ 1 - 0
cmake/libprotoc.cmake

@@ -84,6 +84,7 @@ set(libprotoc_files
   ${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_message_field.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_oneof.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc
+  ${protobuf_source_dir}/src/google/protobuf/compiler/php/php_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/plugin.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/plugin.pb.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/python/python_generator.cc

+ 24 - 0
composer.json

@@ -0,0 +1,24 @@
+{
+  "name": "google/protobuf",
+  "type": "library",
+  "description": "proto library for PHP",
+  "keywords": ["proto"],
+  "homepage": "https://developers.google.com/protocol-buffers/",
+  "license": "BSD-3-Clause",
+  "require": {
+    "php": ">=5.5.0"
+  },
+  "require-dev": {
+    "phpunit/phpunit": ">=4.8.0"
+  },
+  "autoload": {
+    "psr-4": {
+      "Google\\": "php/src/Google"
+    },
+    "files": [
+      "php/src/Google/Protobuf/descriptor.php",
+      "php/src/Google/Protobuf/descriptor_internal.pb.php",
+      "php/src/Google/Protobuf/Internal/Type.php"
+    ]
+  }
+}

+ 99 - 0
php/README.md

@@ -0,0 +1,99 @@
+This directory contains the Protocol Buffers runtime implementation via both a
+pure PHP package and a native c extension. The pure PHP package is intended to
+provide usability to wider range of PHP platforms, while the c extension is
+intended to provide higher performance. Both implementations provide the same
+runtime APIs and share the same generated code. Users don’t need to re-generate
+code for the same proto definition when they want to switch the implementation
+later.
+
+Both implementations make use of generated PHP code that defines message and
+enum types in PHP. We strongly recommend using protoc's PHP generation support
+with .proto files. The build process in this directory only installs the
+extension/package; you need to install protoc as well to have PHP code
+generation functionality.
+
+## Requirements
+
+To use PHP runtime library requires:
+
+- PHP 5.5 or above.
+
+## Installation
+
+### C Extension
+
+#### Prerequirements
+
+To install the c extension, the following tools are needed:
+* autoconf
+* automake
+* libtool
+* make
+* gcc
+* pear
+* pecl
+
+On Ubuntu, you can install them with:
+```
+sudo apt-get install php-pear php5-dev autoconf automake libtool make gcc
+```
+On other platforms, please use the corresponding package managing tool to
+install them before proceeding.
+
+#### Installation from Source (Building extension)
+
+To build the c extension, run the following command:
+```
+cd ext/google/protobuf
+pear package
+sudo pecl install protobuf-{VERSION}.tgz
+```
+
+#### Installation from PECL
+
+When we release a version of Protocol Buffers, we will upload the extension to
+[PECL](https://pecl.php.net/). To use this pre-packaged extension, simply
+install it as you would any other extension:
+
+```
+sudo pecl install protobuf-{VERSION}
+```
+
+### PHP Package
+
+#### Installation from composer
+
+Simply add "google/protobuf" to the 'require' section of composer.json in your
+project.
+
+### Protoc
+
+Once the extension or package is installed, if you wish to generate PHP code
+from a `.proto` file, you will also want to install the Protocol Buffers
+compiler (protoc), as described in this repository's main `README` file.  The
+version of `protoc` included in the latest release supports the `--php_out`
+option to generate PHP code:
+```
+protoc --php_out=out_dir test.proto
+```
+
+## Usage
+
+For general guide:
+  https://developers.google.com/protocol-buffers/phptutorial/
+For generated code:
+  https://developers.google.com/protocol-buffers/docs/reference/php-generated
+
+Known Issues
+------------
+
+* Missing native support for well known types.
+* Missing support for proto2.
+* No API provided for clear/copy messages.
+* No API provided for encoding/decoding with stream.
+* Map fields may not be garbage-collected if there is cycle reference.
+* No debug information for messages in c extension.
+* HHVM not tested.
+* PHP 7.0 not tested.
+* C extension not tested on windows.
+* Message name cannot be Empty.

+ 388 - 0
php/ext/google/protobuf/array.c

@@ -0,0 +1,388 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <ext/spl/spl_iterators.h>
+#include <Zend/zend_API.h>
+#include <Zend/zend_interfaces.h>
+
+#include "protobuf.h"
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetGet, 0, 0, 1)
+  ZEND_ARG_INFO(0, index)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetSet, 0, 0, 2)
+  ZEND_ARG_INFO(0, index)
+  ZEND_ARG_INFO(0, newval)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_void, 0)
+ZEND_END_ARG_INFO()
+
+static zend_function_entry repeated_field_methods[] = {
+  PHP_ME(RepeatedField, __construct,  NULL,              ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, append,       NULL,              ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, offsetExists, arginfo_offsetGet, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, offsetGet,    arginfo_offsetGet, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, offsetSet,    arginfo_offsetSet, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, offsetUnset,  arginfo_offsetGet, ZEND_ACC_PUBLIC)
+  PHP_ME(RepeatedField, count,        arginfo_void,      ZEND_ACC_PUBLIC)
+  ZEND_FE_END
+};
+
+// Forward declare static functions.
+
+static zend_object_value repeated_field_create(zend_class_entry *ce TSRMLS_DC);
+static void repeated_field_free(void *object TSRMLS_DC);
+static int repeated_field_array_init(zval *array, upb_fieldtype_t type,
+                                     uint size ZEND_FILE_LINE_DC);
+static void repeated_field_free_element(void *object);
+static void repeated_field_write_dimension(zval *object, zval *offset,
+                                           zval *value TSRMLS_DC);
+static int repeated_field_has_dimension(zval *object, zval *offset TSRMLS_DC);
+static HashTable *repeated_field_get_gc(zval *object, zval ***table,
+                                        int *n TSRMLS_DC);
+
+// -----------------------------------------------------------------------------
+// RepeatedField creation/desctruction
+// -----------------------------------------------------------------------------
+
+zend_class_entry* repeated_field_type;
+zend_object_handlers* repeated_field_handlers;
+
+void repeated_field_init(TSRMLS_D) {
+  zend_class_entry class_type;
+  const char* class_name = "Google\\Protobuf\\Internal\\RepeatedField";
+  INIT_CLASS_ENTRY_EX(class_type, class_name, strlen(class_name),
+                      repeated_field_methods);
+
+  repeated_field_type = zend_register_internal_class(&class_type TSRMLS_CC);
+  repeated_field_type->create_object = repeated_field_create;
+
+  zend_class_implements(repeated_field_type TSRMLS_CC, 2, spl_ce_ArrayAccess,
+                        spl_ce_Countable);
+
+  repeated_field_handlers = PEMALLOC(zend_object_handlers);
+  memcpy(repeated_field_handlers, zend_get_std_object_handlers(),
+         sizeof(zend_object_handlers));
+  repeated_field_handlers->get_gc = repeated_field_get_gc;
+}
+
+static zend_object_value repeated_field_create(zend_class_entry *ce TSRMLS_DC) {
+  zend_object_value retval = {0};
+  RepeatedField *intern;
+
+  intern = emalloc(sizeof(RepeatedField));
+  memset(intern, 0, sizeof(RepeatedField));
+
+  zend_object_std_init(&intern->std, ce TSRMLS_CC);
+  object_properties_init(&intern->std, ce);
+
+  intern->array = NULL;
+  intern->type = 0;
+  intern->msg_ce = NULL;
+
+  retval.handle = zend_objects_store_put(
+      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,
+      (zend_objects_free_object_storage_t)repeated_field_free, NULL TSRMLS_CC);
+  retval.handlers = repeated_field_handlers;
+
+  return retval;
+}
+
+static void repeated_field_free(void *object TSRMLS_DC) {
+  RepeatedField *intern = object;
+  zend_object_std_dtor(&intern->std TSRMLS_CC);
+  zval_ptr_dtor(&intern->array);
+  efree(object);
+}
+
+static int repeated_field_array_init(zval *array, upb_fieldtype_t type,
+                                     uint size ZEND_FILE_LINE_DC) {
+  ALLOC_HASHTABLE(Z_ARRVAL_P(array));
+
+  switch (type) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+    case UPB_TYPE_MESSAGE:
+      zend_hash_init(Z_ARRVAL_P(array), size, NULL, ZVAL_PTR_DTOR, 0);
+      break;
+    default:
+      zend_hash_init(Z_ARRVAL_P(array), size, NULL, repeated_field_free_element,
+                     0);
+  }
+  Z_TYPE_P(array) = IS_ARRAY;
+  return SUCCESS;
+}
+
+static void repeated_field_free_element(void *object) {
+}
+
+// -----------------------------------------------------------------------------
+// RepeatedField Handlers
+// -----------------------------------------------------------------------------
+
+static void repeated_field_write_dimension(zval *object, zval *offset,
+                                           zval *value TSRMLS_DC) {
+  uint64_t index;
+
+  RepeatedField *intern = zend_object_store_get_object(object TSRMLS_CC);
+  HashTable *ht = HASH_OF(intern->array);
+  int size = native_slot_size(intern->type);
+
+  unsigned char memory[NATIVE_SLOT_MAX_SIZE];
+  memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
+
+  if (!native_slot_set(intern->type, intern->msg_ce, memory, value)) {
+    return;
+  }
+
+  if (!offset || Z_TYPE_P(offset) == IS_NULL) {
+    index = zend_hash_num_elements(HASH_OF(intern->array));
+  } else {
+    if (protobuf_convert_to_uint64(offset, &index)) {
+      if (!zend_hash_index_exists(ht, index)) {
+        zend_error(E_USER_ERROR, "Element at %d doesn't exist.\n", index);
+        return;
+      }
+    } else {
+      return;
+    }
+  }
+
+  zend_hash_index_update(ht, index, memory, size, NULL);
+}
+
+static HashTable *repeated_field_get_gc(zval *object, zval ***table,
+                                        int *n TSRMLS_DC) {
+  *table = NULL;
+  *n = 0;
+  RepeatedField *intern = zend_object_store_get_object(object TSRMLS_CC);
+  return HASH_OF(intern->array);
+}
+
+// -----------------------------------------------------------------------------
+// C RepeatedField Utilities
+// -----------------------------------------------------------------------------
+
+void *repeated_field_index_native(RepeatedField *intern, int index) {
+  HashTable *ht = HASH_OF(intern->array);
+  void *value;
+
+  if (zend_hash_index_find(ht, index, (void **)&value) == FAILURE) {
+    zend_error(E_USER_ERROR, "Element at %d doesn't exist.\n", index);
+    return NULL;
+  }
+
+  return value;
+}
+
+void repeated_field_push_native(RepeatedField *intern, void *value TSRMLS_DC) {
+  HashTable *ht = HASH_OF(intern->array);
+  int size = native_slot_size(intern->type);
+  zend_hash_next_index_insert(ht, (void **)value, size, NULL);
+}
+
+void repeated_field_create_with_type(zend_class_entry *ce,
+                                     const upb_fielddef *field,
+                                     zval **repeated_field TSRMLS_DC) {
+  MAKE_STD_ZVAL(*repeated_field);
+  Z_TYPE_PP(repeated_field) = IS_OBJECT;
+  Z_OBJVAL_PP(repeated_field) =
+      repeated_field_type->create_object(repeated_field_type TSRMLS_CC);
+
+  RepeatedField *intern =
+      zend_object_store_get_object(*repeated_field TSRMLS_CC);
+  intern->type = upb_fielddef_type(field);
+  if (intern->type == UPB_TYPE_MESSAGE) {
+    upb_msgdef *msg = upb_fielddef_msgsubdef(field);
+    zval *desc_php = get_def_obj(msg);
+    Descriptor *desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+    intern->msg_ce = desc->klass;
+  }
+  MAKE_STD_ZVAL(intern->array);
+  repeated_field_array_init(intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
+
+  // TODO(teboring): Link class entry for message and enum
+}
+
+
+// -----------------------------------------------------------------------------
+// PHP RepeatedField Methods
+// -----------------------------------------------------------------------------
+
+/**
+ * Constructs an instance of RepeatedField.
+ * @param long Type of the stored element.
+ * @param string Message/Enum class name (message/enum fields only).
+ */
+PHP_METHOD(RepeatedField, __construct) {
+  long type;
+  zend_class_entry* klass = NULL;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|C", &type, &klass) ==
+      FAILURE) {
+    return;
+  }
+
+  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  intern->type = to_fieldtype(type);
+  intern->msg_ce = klass;
+
+  MAKE_STD_ZVAL(intern->array);
+  repeated_field_array_init(intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
+
+  if (intern->type == UPB_TYPE_MESSAGE && klass == NULL) {
+    zend_error(E_USER_ERROR, "Message type must have concrete class.");
+    return;
+  }
+
+  // TODO(teboring): Consider enum.
+}
+
+/**
+ * Append element to the end of the repeated field.
+ * @param object The element to be added.
+ */
+PHP_METHOD(RepeatedField, append) {
+  zval *value;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &value) ==
+      FAILURE) {
+    return;
+  }
+  repeated_field_write_dimension(getThis(), NULL, value TSRMLS_CC);
+}
+
+/**
+ * Check whether the element at given index exists.
+ * @param long The index to be checked.
+ * @return bool True if the element at the given index exists.
+ */
+PHP_METHOD(RepeatedField, offsetExists) {
+  long index;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
+      FAILURE) {
+    return;
+  }
+
+  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+  RETURN_BOOL(index >= 0 &&
+              index < zend_hash_num_elements(HASH_OF(intern->array)));
+}
+
+/**
+ * Return the element at the given index.
+ * This will also be called for: $ele = $arr[0]
+ * @param long The index of the element to be fetched.
+ * @return object The stored element at given index.
+ * @exception Invalid type for index.
+ * @exception Non-existing index.
+ */
+PHP_METHOD(RepeatedField, offsetGet) {
+  long index;
+  void *memory;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
+      FAILURE) {
+    return;
+  }
+
+  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  HashTable *table = HASH_OF(intern->array);
+
+  if (zend_hash_index_find(table, index, (void **)&memory) == FAILURE) {
+    zend_error(E_USER_ERROR, "Element at %d doesn't exist.\n", index);
+    return;
+  }
+
+  native_slot_get(intern->type, memory, return_value_ptr TSRMLS_CC);
+}
+
+/**
+ * Assign the element at the given index.
+ * This will also be called for: $arr []= $ele and $arr[0] = ele
+ * @param long The index of the element to be assigned.
+ * @param object The element to be assigned.
+ * @exception Invalid type for index.
+ * @exception Non-existing index.
+ * @exception Incorrect type of the element.
+ */
+PHP_METHOD(RepeatedField, offsetSet) {
+  zval *index, *value;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &index, &value) ==
+      FAILURE) {
+    return;
+  }
+  repeated_field_write_dimension(getThis(), index, value TSRMLS_CC);
+}
+
+/**
+ * Remove the element at the given index.
+ * This will also be called for: unset($arr)
+ * @param long The index of the element to be removed.
+ * @exception Invalid type for index.
+ * @exception The element to be removed is not at the end of the RepeatedField.
+ */
+PHP_METHOD(RepeatedField, offsetUnset) {
+  long index;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
+      FAILURE) {
+    return;
+  }
+
+  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+  // Only the element at the end of the array can be removed.
+  if (index == -1 ||
+      index != (zend_hash_num_elements(HASH_OF(intern->array)) - 1)) {
+    zend_error(E_USER_ERROR, "Cannot remove element at %d.\n", index);
+    return;
+  }
+
+  zend_hash_index_del(HASH_OF(intern->array), index);
+}
+
+/**
+ * Return the number of stored elements.
+ * This will also be called for: count($arr)
+ * @return long The number of stored elements.
+ */
+PHP_METHOD(RepeatedField, count) {
+  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+  if (zend_parse_parameters_none() == FAILURE) {
+    return;
+  }
+
+  RETURN_LONG(zend_hash_num_elements(HASH_OF(intern->array)));
+}

+ 4 - 4
php/ext/google/protobuf/config.m4

@@ -1,10 +1,10 @@
-dnl lines starting with "dnl" are comments
-
 PHP_ARG_ENABLE(protobuf, whether to enable Protobuf extension, [  --enable-protobuf   Enable Protobuf extension])
 
 if test "$PHP_PROTOBUF" != "no"; then
 
-  dnl this defines the extension
-  PHP_NEW_EXTENSION(protobuf, upb.c protobuf.c def.c message.c storage.c, $ext_shared)
+  PHP_NEW_EXTENSION(
+    protobuf,
+    array.c def.c encode_decode.c map.c message.c protobuf.c storage.c type_check.c upb.c utf8.c,
+    $ext_shared)
 
 fi

+ 343 - 266
php/ext/google/protobuf/def.c

@@ -1,115 +1,213 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 #include "protobuf.h"
 
+// Forward declare.
+static zend_object_value descriptor_create(zend_class_entry *ce TSRMLS_DC);
+static void descriptor_init_c_instance(Descriptor* intern TSRMLS_DC);
+static void descriptor_free_c(Descriptor* object TSRMLS_DC);
+static void descriptor_free(void* object TSRMLS_DC);
+
+static zend_object_value enum_descriptor_create(zend_class_entry *ce TSRMLS_DC);
+static void enum_descriptor_init_c_instance(EnumDescriptor* intern TSRMLS_DC);
+static void enum_descriptor_free_c(EnumDescriptor* object TSRMLS_DC);
+static void enum_descriptor_free(void* object TSRMLS_DC);
+
+static zend_object_value descriptor_pool_create(zend_class_entry *ce TSRMLS_DC);
+static void descriptor_pool_free_c(DescriptorPool* object TSRMLS_DC);
+static void descriptor_pool_free(void* object TSRMLS_DC);
+static void descriptor_pool_init_c_instance(DescriptorPool* pool TSRMLS_DC);
+
 // -----------------------------------------------------------------------------
 // Common Utilities
 // -----------------------------------------------------------------------------
 
-void check_upb_status(const upb_status* status, const char* msg) {
+static void check_upb_status(const upb_status* status, const char* msg) {
   if (!upb_ok(status)) {
-    zend_error("%s: %s\n", msg, upb_status_errmsg(status));
+    zend_error(E_ERROR, "%s: %s\n", msg, upb_status_errmsg(status));
   }
 }
 
+static void upb_filedef_free(void *r) {
+  upb_filedef *f = *(upb_filedef **)r;
+  size_t i;
 
-static upb_def *check_notfrozen(const upb_def *def) {
-  if (upb_def_isfrozen(def)) {
-    zend_error(E_ERROR,
-               "Attempt to modify a frozen descriptor. Once descriptors are "
-               "added to the descriptor pool, they may not be modified.");
+  for (i = 0; i < upb_filedef_depcount(f); i++) {
+    upb_filedef_unref(upb_filedef_dep(f, i), f);
   }
-  return (upb_def *)def;
-}
 
-static upb_msgdef *check_msgdef_notfrozen(const upb_msgdef *def) {
-  return upb_downcast_msgdef_mutable(check_notfrozen((const upb_def *)def));
+  upb_inttable_uninit(&f->defs);
+  upb_inttable_uninit(&f->deps);
+  upb_gfree((void *)f->name);
+  upb_gfree((void *)f->package);
+  upb_gfree(f);
 }
 
-static upb_fielddef *check_fielddef_notfrozen(const upb_fielddef *def) {
-  return upb_downcast_fielddef_mutable(check_notfrozen((const upb_def *)def));
+// Camel-case the field name and append "Entry" for generated map entry name.
+// e.g. map<KeyType, ValueType> foo_map => FooMapEntry
+static void append_map_entry_name(char *result, const char *field_name,
+                                  int pos) {
+  bool cap_next = true;
+  int i;
+
+  for (i = 0; i < strlen(field_name); ++i) {
+    if (field_name[i] == '_') {
+      cap_next = true;
+    } else if (cap_next) {
+      // Note: Do not use ctype.h due to locales.
+      if ('a' <= field_name[i] && field_name[i] <= 'z') {
+        result[pos++] = field_name[i] - 'a' + 'A';
+      } else {
+        result[pos++] = field_name[i];
+      }
+      cap_next = false;
+    } else {
+      result[pos++] = field_name[i];
+    }
+  }
+  strcat(result, "Entry");
 }
 
-#define PROTOBUF_WRAP_INTERN(wrapper, intern, intern_dtor)            \
-  Z_TYPE_P(wrapper) = IS_OBJECT;                                      \
-  Z_OBJVAL_P(wrapper)                                                 \
-      .handle = zend_objects_store_put(                               \
-      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object, \
-      intern_dtor, NULL TSRMLS_CC);                                   \
-  Z_OBJVAL_P(wrapper).handlers = zend_get_std_object_handlers();
-
-#define PROTOBUF_SETUP_ZEND_WRAPPER(class_name, class_name_lower, wrapper,    \
-                                    intern)                                   \
-  Z_TYPE_P(wrapper) = IS_OBJECT;                                              \
-  class_name *intern = ALLOC(class_name);                                     \
-  memset(intern, 0, sizeof(class_name));                                      \
-  class_name_lower##_init_c_instance(intern TSRMLS_CC);                       \
-  Z_OBJVAL_P(wrapper)                                                         \
-      .handle = zend_objects_store_put(intern, NULL, class_name_lower##_free, \
-                                       NULL TSRMLS_CC);                       \
-  Z_OBJVAL_P(wrapper).handlers = zend_get_std_object_handlers();
-
-#define PROTOBUF_CREATE_ZEND_WRAPPER(class_name, class_name_lower, wrapper, \
-                                     intern)                                \
-  MAKE_STD_ZVAL(wrapper);                                                   \
-  PROTOBUF_SETUP_ZEND_WRAPPER(class_name, class_name_lower, wrapper, intern);
-
-#define DEFINE_CLASS(name, name_lower, string_name)                          \
-  zend_class_entry *name_lower##_type;                                       \
+#define CHECK_UPB(code, msg)             \
+  do {                                   \
+    upb_status status = UPB_STATUS_INIT; \
+    code;                                \
+    check_upb_status(&status, msg);      \
+  } while (0)
+
+// Define PHP class
+#define DEFINE_PROTOBUF_INIT_CLASS(name_lower, string_name)                  \
   void name_lower##_init(TSRMLS_D) {                                         \
     zend_class_entry class_type;                                             \
     INIT_CLASS_ENTRY(class_type, string_name, name_lower##_methods);         \
     name_lower##_type = zend_register_internal_class(&class_type TSRMLS_CC); \
     name_lower##_type->create_object = name_lower##_create;                  \
-  }                                                                          \
-  name *php_to_##name_lower(zval *val TSRMLS_DC) {                           \
-    return (name *)zend_object_store_get_object(val TSRMLS_CC);              \
-  }                                                                          \
-  void name_lower##_free(void *object TSRMLS_DC) {                           \
-    name *intern = (name *)object;                                           \
-    name_lower##_free_c(intern TSRMLS_CC);                                   \
-    efree(object);                                                           \
-  }                                                                          \
-  zend_object_value name_lower##_create(zend_class_entry *ce TSRMLS_DC) {    \
-    zend_object_value return_value;                                          \
-    name *intern = (name *)emalloc(sizeof(name));                            \
-    memset(intern, 0, sizeof(name));                                         \
-    name_lower##_init_c_instance(intern TSRMLS_CC);                          \
-    return_value.handle = zend_objects_store_put(                            \
-        intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,      \
-        name_lower##_free, NULL TSRMLS_CC);                                  \
-    return_value.handlers = zend_get_std_object_handlers();                  \
-    return return_value;                                                     \
   }
 
+#define DEFINE_PROTOBUF_CREATE(name, name_lower)                        \
+  static zend_object_value name_lower##_create(                         \
+      zend_class_entry* ce TSRMLS_DC) {                                 \
+    zend_object_value return_value;                                     \
+    name* intern = (name*)emalloc(sizeof(name));                        \
+    memset(intern, 0, sizeof(name));                                    \
+    name_lower##_init_c_instance(intern TSRMLS_CC);                     \
+    return_value.handle = zend_objects_store_put(                       \
+        intern, (zend_objects_store_dtor_t)zend_objects_destroy_object, \
+        name_lower##_free, NULL TSRMLS_CC);                             \
+    return_value.handlers = zend_get_std_object_handlers();             \
+    return return_value;                                                \
+  }
+
+#define DEFINE_PROTOBUF_FREE(name, name_lower)            \
+  static void name_lower##_free(void* object TSRMLS_DC) { \
+    name* intern = (name*)object;                         \
+    name_lower##_free_c(intern TSRMLS_CC);                \
+    efree(object);                                        \
+  }
+
+#define DEFINE_CLASS(name, name_lower, string_name) \
+  zend_class_entry* name_lower##_type;              \
+  DEFINE_PROTOBUF_FREE(name, name_lower)            \
+  DEFINE_PROTOBUF_CREATE(name, name_lower)          \
+  DEFINE_PROTOBUF_INIT_CLASS(name_lower, string_name)
+
+// -----------------------------------------------------------------------------
+// GPBType
+// -----------------------------------------------------------------------------
+
+zend_class_entry* gpb_type_type;
+
+static zend_function_entry gpb_type_methods[] = {
+  ZEND_FE_END
+};
+
+void gpb_type_init(TSRMLS_D) {
+  zend_class_entry class_type;
+  INIT_CLASS_ENTRY(class_type, "Google\\Protobuf\\Internal\\GPBType",
+                   gpb_type_methods);
+  gpb_type_type = zend_register_internal_class(&class_type TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("DOUBLE"),  1 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("FLOAT"),   2 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("INT64"),   3 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("UINT64"),  4 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("INT32"),   5 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("FIXED64"), 6 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("FIXED32"), 7 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("BOOL"),    8 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("STRING"),  9 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("GROUP"),   10 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("MESSAGE"), 11 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("BYTES"),   12 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("UINT32"),  13 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("ENUM"),    14 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("SFIXED32"),
+                                   15 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("SFIXED64"),
+                                   16 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("SINT32"), 17 TSRMLS_CC);
+  zend_declare_class_constant_long(gpb_type_type, STR("SINT64"), 18 TSRMLS_CC);
+}
+
 // -----------------------------------------------------------------------------
 // DescriptorPool
 // -----------------------------------------------------------------------------
 
 static zend_function_entry descriptor_pool_methods[] = {
-  PHP_ME(DescriptorPool, addMessage, NULL, ZEND_ACC_PUBLIC)
-  PHP_ME(DescriptorPool, finalize, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(DescriptorPool, getGeneratedPool, NULL,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(DescriptorPool, internalAddGeneratedFile, NULL, ZEND_ACC_PUBLIC)
   ZEND_FE_END
 };
 
 DEFINE_CLASS(DescriptorPool, descriptor_pool,
-             "Google\\Protobuf\\DescriptorPool");
+             "Google\\Protobuf\\Internal\\DescriptorPool");
 
+zval* generated_pool_php;  // wrapper of generated pool
 DescriptorPool *generated_pool;  // The actual generated pool
 
-ZEND_FUNCTION(get_generated_pool) {
-  if (PROTOBUF_G(generated_pool) == NULL) {
-    MAKE_STD_ZVAL(PROTOBUF_G(generated_pool));
-    Z_TYPE_P(PROTOBUF_G(generated_pool)) = IS_OBJECT;
+static void init_generated_pool_once(TSRMLS_D) {
+  if (generated_pool_php == NULL) {
+    MAKE_STD_ZVAL(generated_pool_php);
+    Z_TYPE_P(generated_pool_php) = IS_OBJECT;
     generated_pool = ALLOC(DescriptorPool);
     descriptor_pool_init_c_instance(generated_pool TSRMLS_CC);
-    Z_OBJ_HANDLE_P(PROTOBUF_G(generated_pool)) = zend_objects_store_put(
+    Z_OBJ_HANDLE_P(generated_pool_php) = zend_objects_store_put(
         generated_pool, NULL,
-        (zend_objects_free_object_storage_t)descriptor_pool_free, NULL TSRMLS_CC);
-    Z_OBJ_HT_P(PROTOBUF_G(generated_pool)) = zend_get_std_object_handlers();
+        (zend_objects_free_object_storage_t)descriptor_pool_free,
+        NULL TSRMLS_CC);
+    Z_OBJ_HT_P(generated_pool_php) = zend_get_std_object_handlers();
   }
-  RETURN_ZVAL(PROTOBUF_G(generated_pool), 1, 0);
 }
 
-void descriptor_pool_init_c_instance(DescriptorPool* pool TSRMLS_DC) {
+static void descriptor_pool_init_c_instance(DescriptorPool *pool TSRMLS_DC) {
   zend_object_std_init(&pool->std, descriptor_pool_type TSRMLS_CC);
   pool->symtab = upb_symtab_new(&pool->symtab);
 
@@ -117,31 +215,21 @@ void descriptor_pool_init_c_instance(DescriptorPool* pool TSRMLS_DC) {
   zend_hash_init(pool->pending_list, 1, NULL, ZVAL_PTR_DTOR, 0);
 }
 
-void descriptor_pool_free_c(DescriptorPool *pool TSRMLS_DC) {
+static void descriptor_pool_free_c(DescriptorPool *pool TSRMLS_DC) {
   upb_symtab_unref(pool->symtab, &pool->symtab);
+
   zend_hash_destroy(pool->pending_list);
   FREE_HASHTABLE(pool->pending_list);
 }
 
-PHP_METHOD(DescriptorPool, addMessage) {
-  char *name = NULL;
-  int str_len;
-  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &str_len) ==
-      FAILURE) {
-    return;
+static void validate_enumdef(const upb_enumdef *enumdef) {
+  // Verify that an entry exists with integer value 0. (This is the default
+  // value.)
+  const char *lookup = upb_enumdef_iton(enumdef, 0);
+  if (lookup == NULL) {
+    zend_error(E_USER_ERROR,
+               "Enum definition does not contain a value for '0'.");
   }
-
-  zval* retval = NULL;
-  PROTOBUF_CREATE_ZEND_WRAPPER(MessageBuilderContext, message_builder_context,
-                               retval, context);
-
-  MAKE_STD_ZVAL(context->pool);
-  ZVAL_ZVAL(context->pool, getThis(), 1, 0);
-
-  Descriptor *desc = php_to_descriptor(context->descriptor TSRMLS_CC);
-  Descriptor_name_set(desc, name);
-
-  RETURN_ZVAL(retval, 0, 1);
 }
 
 static void validate_msgdef(const upb_msgdef* msgdef) {
@@ -157,35 +245,123 @@ static void validate_msgdef(const upb_msgdef* msgdef) {
   }
 }
 
-PHP_METHOD(DescriptorPool, finalize) {
-  DescriptorPool *self = php_to_descriptor_pool(getThis() TSRMLS_CC);
-  Bucket *temp;
-  int i, num;
+PHP_METHOD(DescriptorPool, getGeneratedPool) {
+  init_generated_pool_once(TSRMLS_C);
+  RETURN_ZVAL(generated_pool_php, 1, 0);
+}
+
+static void convert_to_class_name_inplace(char *proto_name,
+                                          size_t pkg_name_len) {
+  size_t i;
+  bool first_char = false;
 
-  num = zend_hash_num_elements(self->pending_list);
-  upb_def **defs = emalloc(sizeof(upb_def *) * num);
+  for (i = 0; i <= pkg_name_len + 1; i++) {
+    // PHP package uses camel case.
+    if (!first_char && proto_name[i] != '.') {
+      first_char = true;
+      proto_name[i] += 'A' - 'a';
+    }
+    // php packages are divided by '\'.
+    if (proto_name[i] == '.') {
+      first_char = false;
+      proto_name[i] = '\\';
+    }
+  }
+
+  // Submessage is concatenated with its containing messages by '_'.
+  for (i = pkg_name_len; i < strlen(proto_name); i++) {
+    if (proto_name[i] == '.') {
+      proto_name[i] = '_';
+    }
+  }
+}
+
+PHP_METHOD(DescriptorPool, internalAddGeneratedFile) {
+  char *data = NULL;
+  int data_len;
+  upb_filedef **files;
+  size_t i;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &data, &data_len) ==
+      FAILURE) {
+    return;
+  }
 
-  for (i = 0, temp = self->pending_list->pListHead; temp != NULL;
-       temp = temp->pListNext) {
-    zval *def_php = *(zval **)temp->pData;
-    Descriptor* desc = php_to_descriptor(def_php TSRMLS_CC);
-    defs[i] = (upb_def *)desc->msgdef;
-    validate_msgdef((const upb_msgdef *)defs[i++]);
+  DescriptorPool *pool = UNBOX(DescriptorPool, getThis());
+  CHECK_UPB(files = upb_loaddescriptor(data, data_len, &pool, &status),
+            "Parse binary descriptors to internal descriptors failed");
+
+  // This method is called only once in each file.
+  assert(files[0] != NULL);
+  assert(files[1] == NULL);
+
+  CHECK_UPB(upb_symtab_addfile(pool->symtab, files[0], &status),
+            "Unable to add file to DescriptorPool");
+
+  // For each enum/message, we need its PHP class, upb descriptor and its PHP
+  // wrapper. These information are needed later for encoding, decoding and type
+  // checking. However, sometimes we just have one of them. In order to find
+  // them quickly, here, we store the mapping for them.
+  for (i = 0; i < upb_filedef_defcount(files[0]); i++) {
+    const upb_def *def = upb_filedef_def(files[0], i);
+    switch (upb_def_type(def)) {
+#define CASE_TYPE(def_type, def_type_lower, desc_type, desc_type_lower)        \
+  case UPB_DEF_##def_type: {                                                   \
+    desc_type *desc;                                                           \
+    zval *desc_php;                                                            \
+    CREATE(desc_type, desc, desc_type_lower##_init_c_instance);                \
+    BOX(desc_type, desc_php, desc, desc_type_lower##_free);                    \
+    Z_DELREF_P(desc_php);                                                      \
+    const upb_##def_type_lower *def_type_lower =                               \
+        upb_downcast_##def_type_lower(def);                                    \
+    desc->def_type_lower = def_type_lower;                                     \
+    add_def_obj(desc->def_type_lower, desc_php);                               \
+    /* Unlike other messages, MapEntry is shared by all map fields and doesn't \
+     * have generated PHP class.*/                                             \
+    if (upb_def_type(def) == UPB_DEF_MSG && upb_msgdef_mapentry(def)) {        \
+      break;                                                                   \
+    }                                                                          \
+    /* Prepend '.' to package name to make it absolute. */                     \
+    const char *fullname = upb_##def_type_lower##_fullname(def_type_lower);    \
+    char *klass_name = ecalloc(sizeof(char), 2 + strlen(fullname));            \
+    klass_name[0] = '.';                                                       \
+    strcpy(&klass_name[1], fullname);                                          \
+    size_t pkg_name_len = strlen(upb_filedef_package(files[0]));               \
+    convert_to_class_name_inplace(klass_name, pkg_name_len);                   \
+    zend_class_entry **pce;                                                    \
+    if (zend_lookup_class(klass_name, strlen(klass_name), &pce TSRMLS_CC) ==   \
+        FAILURE) {                                                             \
+      zend_error(E_ERROR, "Generated message class %s hasn't been defined",    \
+                 klass_name);                                                  \
+      return;                                                                  \
+    } else {                                                                   \
+      desc->klass = *pce;                                                      \
+    }                                                                          \
+    add_ce_obj(desc->klass, desc_php);                                         \
+    efree(klass_name);                                                         \
+    break;                                                                     \
   }
 
-  CHECK_UPB(upb_symtab_add(self->symtab, (upb_def **)defs, num, NULL, &status),
-            "Unable to add defs to DescriptorPool");
+      CASE_TYPE(MSG, msgdef, Descriptor, descriptor)
+      CASE_TYPE(ENUM, enumdef, EnumDescriptor, enum_descriptor)
+#undef CASE_TYPE
 
-  for (temp = self->pending_list->pListHead; temp != NULL;
-       temp = temp->pListNext) {
-    // zval *def_php = *(zval **)temp->pData;
-    // Descriptor* desc = php_to_descriptor(def_php TSRMLS_CC);
-    build_class_from_descriptor((zval *)temp->pDataPtr TSRMLS_CC);
+      default:
+        break;
+    }
+  }
+
+  for (i = 0; i < upb_filedef_defcount(files[0]); i++) {
+    const upb_def *def = upb_filedef_def(files[0], i);
+    if (upb_def_type(def) == UPB_DEF_MSG) {
+      const upb_msgdef *msgdef = upb_downcast_msgdef(def);
+      zval *desc_php = get_def_obj(msgdef);
+      build_class_from_descriptor(desc_php TSRMLS_CC);
+    }
   }
 
-  FREE(defs);
-  zend_hash_destroy(self->pending_list);
-  zend_hash_init(self->pending_list, 1, NULL, ZVAL_PTR_DTOR, 0);
+  upb_filedef_unref(files[0], &pool);
+  upb_gfree(files);
 }
 
 // -----------------------------------------------------------------------------
@@ -196,186 +372,87 @@ static zend_function_entry descriptor_methods[] = {
   ZEND_FE_END
 };
 
-DEFINE_CLASS(Descriptor, descriptor, "Google\\Protobuf\\Descriptor");
+DEFINE_CLASS(Descriptor, descriptor, "Google\\Protobuf\\Internal\\Descriptor");
 
-void descriptor_free_c(Descriptor *self TSRMLS_DC) {
-  upb_msg_field_iter iter;
-  upb_msg_field_begin(&iter, self->msgdef);
-  while (!upb_msg_field_done(&iter)) {
-    upb_fielddef *fielddef = upb_msg_iter_field(&iter);
-    upb_fielddef_unref(fielddef, &fielddef);
-    upb_msg_field_next(&iter);
-  }
-  upb_msgdef_unref(self->msgdef, &self->msgdef);
+static void descriptor_free_c(Descriptor *self TSRMLS_DC) {
   if (self->layout) {
     free_layout(self->layout);
   }
+  if (self->fill_handlers) {
+    upb_handlers_unref(self->fill_handlers, &self->fill_handlers);
+  }
+  if (self->fill_method) {
+    upb_pbdecodermethod_unref(self->fill_method, &self->fill_method);
+  }
+  if (self->pb_serialize_handlers) {
+    upb_handlers_unref(self->pb_serialize_handlers,
+                       &self->pb_serialize_handlers);
+  }
 }
 
-static void descriptor_add_field(Descriptor *desc,
-                                 const upb_fielddef *fielddef) {
-  upb_msgdef *mut_def = check_msgdef_notfrozen(desc->msgdef);
-  upb_fielddef *mut_field_def = check_fielddef_notfrozen(fielddef);
-  CHECK_UPB(upb_msgdef_addfield(mut_def, mut_field_def, NULL, &status),
-            "Adding field to Descriptor failed");
-  // add_def_obj(fielddef, obj);
-}
-
-void descriptor_init_c_instance(Descriptor* desc TSRMLS_DC) {
+static void descriptor_init_c_instance(Descriptor *desc TSRMLS_DC) {
   zend_object_std_init(&desc->std, descriptor_type TSRMLS_CC);
-  desc->msgdef = upb_msgdef_new(&desc->msgdef);
+  desc->msgdef = NULL;
   desc->layout = NULL;
-  // MAKE_STD_ZVAL(intern->klass);
-  // ZVAL_NULL(intern->klass);
+  desc->klass = NULL;
+  desc->fill_handlers = NULL;
+  desc->fill_method = NULL;
   desc->pb_serialize_handlers = NULL;
 }
 
-void Descriptor_name_set(Descriptor *desc, const char *name) {
-  upb_msgdef *mut_def = check_msgdef_notfrozen(desc->msgdef);
-  CHECK_UPB(upb_msgdef_setfullname(mut_def, name, &status),
-            "Error setting Descriptor name");
-}
-
 // -----------------------------------------------------------------------------
-// FieldDescriptor
+// EnumDescriptor
 // -----------------------------------------------------------------------------
 
-static void field_descriptor_name_set(const upb_fielddef* fielddef,
-                                      const char *name) {
-  upb_fielddef *mut_def = check_fielddef_notfrozen(fielddef);
-  CHECK_UPB(upb_fielddef_setname(mut_def, name, &status),
-            "Error setting FieldDescriptor name");
-}
-
-static void field_descriptor_label_set(const upb_fielddef* fielddef,
-                                       upb_label_t upb_label) {
-  upb_fielddef *mut_def = check_fielddef_notfrozen(fielddef);
-  upb_fielddef_setlabel(mut_def, upb_label);
-}
-
-upb_fieldtype_t string_to_descriptortype(const char *type) {
-#define CONVERT(upb, str)   \
-  if (!strcmp(type, str)) { \
-    return UPB_DESCRIPTOR_TYPE_##upb;  \
-  }
-
-  CONVERT(FLOAT, "float");
-  CONVERT(DOUBLE, "double");
-  CONVERT(BOOL, "bool");
-  CONVERT(STRING, "string");
-  CONVERT(BYTES, "bytes");
-  CONVERT(MESSAGE, "message");
-  CONVERT(GROUP, "group");
-  CONVERT(ENUM, "enum");
-  CONVERT(INT32, "int32");
-  CONVERT(INT64, "int64");
-  CONVERT(UINT32, "uint32");
-  CONVERT(UINT64, "uint64");
-  CONVERT(SINT32, "sint32");
-  CONVERT(SINT64, "sint64");
-  CONVERT(FIXED32, "fixed32");
-  CONVERT(FIXED64, "fixed64");
-  CONVERT(SFIXED32, "sfixed32");
-  CONVERT(SFIXED64, "sfixed64");
-
-#undef CONVERT
+static zend_function_entry enum_descriptor_methods[] = {
+  ZEND_FE_END
+};
 
-  zend_error(E_ERROR, "Unknown field type.");
-  return 0;
-}
+DEFINE_CLASS(EnumDescriptor, enum_descriptor,
+             "Google\\Protobuf\\Internal\\EnumDescriptor");
 
-static void field_descriptor_type_set(const upb_fielddef* fielddef,
-                                      const char *type) {
-  upb_fielddef *mut_def = check_fielddef_notfrozen(fielddef);
-  upb_fielddef_setdescriptortype(mut_def, string_to_descriptortype(type));
+static void enum_descriptor_free_c(EnumDescriptor *self TSRMLS_DC) {
 }
 
-static void field_descriptor_number_set(const upb_fielddef* fielddef,
-                                        int number) {
-  upb_fielddef *mut_def = check_fielddef_notfrozen(fielddef);
-  CHECK_UPB(upb_fielddef_setnumber(mut_def, number, &status),
-            "Error setting field number");
+static void enum_descriptor_init_c_instance(EnumDescriptor *self TSRMLS_DC) {
+  zend_object_std_init(&self->std, enum_descriptor_type TSRMLS_CC);
+  self->enumdef = NULL;
+  self->klass = NULL;
 }
 
 // -----------------------------------------------------------------------------
-// MessageBuilderContext
+// FieldDescriptor
 // -----------------------------------------------------------------------------
 
-static zend_function_entry message_builder_context_methods[] = {
-    PHP_ME(MessageBuilderContext, finalizeToPool, NULL, ZEND_ACC_PUBLIC)
-    PHP_ME(MessageBuilderContext, optional, NULL, ZEND_ACC_PUBLIC)
-    {NULL, NULL, NULL}
-};
-
-DEFINE_CLASS(MessageBuilderContext, message_builder_context,
-             "Google\\Protobuf\\Internal\\MessageBuilderContext");
-
-void message_builder_context_free_c(MessageBuilderContext *context TSRMLS_DC) {
-  zval_ptr_dtor(&context->descriptor);
-  zval_ptr_dtor(&context->pool);
-}
-
-void message_builder_context_init_c_instance(
-    MessageBuilderContext *context TSRMLS_DC) {
-  zend_object_std_init(&context->std, message_builder_context_type TSRMLS_CC);
-  PROTOBUF_CREATE_ZEND_WRAPPER(Descriptor, descriptor, context->descriptor,
-                               desc);
-}
+upb_fieldtype_t to_fieldtype(upb_descriptortype_t type) {
+  switch (type) {
+#define CASE(descriptor_type, type)           \
+  case UPB_DESCRIPTOR_TYPE_##descriptor_type: \
+    return UPB_TYPE_##type;
+
+  CASE(FLOAT,    FLOAT);
+  CASE(DOUBLE,   DOUBLE);
+  CASE(BOOL,     BOOL);
+  CASE(STRING,   STRING);
+  CASE(BYTES,    BYTES);
+  CASE(MESSAGE,  MESSAGE);
+  CASE(GROUP,    MESSAGE);
+  CASE(ENUM,     ENUM);
+  CASE(INT32,    INT32);
+  CASE(INT64,    INT64);
+  CASE(UINT32,   UINT32);
+  CASE(UINT64,   UINT64);
+  CASE(SINT32,   INT32);
+  CASE(SINT64,   INT64);
+  CASE(FIXED32,  UINT32);
+  CASE(FIXED64,  UINT64);
+  CASE(SFIXED32, INT32);
+  CASE(SFIXED64, INT64);
 
-static void msgdef_add_field(Descriptor *desc, upb_label_t upb_label,
-                             const char *name, const char *type, int number,
-                             const char *type_class) {
-  upb_fielddef *fielddef = upb_fielddef_new(&fielddef);
-  upb_fielddef_setpacked(fielddef, false);
-
-  field_descriptor_label_set(fielddef, upb_label);
-  field_descriptor_name_set(fielddef, name);
-  field_descriptor_type_set(fielddef, type);
-  field_descriptor_number_set(fielddef, number);
-
-// //   if (type_class != Qnil) {
-// //     if (TYPE(type_class) != T_STRING) {
-// //       rb_raise(rb_eArgError, "Expected string for type class");
-// //     }
-// //     // Make it an absolute type name by prepending a dot.
-// //     type_class = rb_str_append(rb_str_new2("."), type_class);
-// //     rb_funcall(fielddef, rb_intern("submsg_name="), 1, type_class);
-// //   }
-  descriptor_add_field(desc, fielddef);
-}
+#undef CONVERT
 
-PHP_METHOD(MessageBuilderContext, optional) {
-  MessageBuilderContext *self = php_to_message_builder_context(getThis() TSRMLS_CC);
-  Descriptor *desc = php_to_descriptor(self->descriptor TSRMLS_CC);
-  // VALUE name, type, number, type_class;
-  const char *name, *type, *type_class;
-  int number, name_str_len, type_str_len, type_class_str_len;
-  if (ZEND_NUM_ARGS() == 3) {
-    if (zend_parse_parameters(3 TSRMLS_CC, "ssl", &name,
-                              &name_str_len, &type, &type_str_len, &number) == FAILURE) {
-      return;
-    }
-  } else {
-    if (zend_parse_parameters(4 TSRMLS_CC, "ssls", &name,
-                              &name_str_len, &type, &type_str_len, &number, &type_class,
-                              &type_class_str_len) == FAILURE) {
-      return;
-    }
   }
 
-  msgdef_add_field(desc, UPB_LABEL_OPTIONAL, name, type, number, type_class);
-
-  zval_copy_ctor(getThis());
-  RETURN_ZVAL(getThis(), 1, 0);
-}
-
-PHP_METHOD(MessageBuilderContext, finalizeToPool) {
-  MessageBuilderContext *self = php_to_message_builder_context(getThis() TSRMLS_CC);
-  DescriptorPool *pool = php_to_descriptor_pool(self->pool TSRMLS_CC);
-  Descriptor* desc = php_to_descriptor(self->descriptor TSRMLS_CC);
-
-  Z_ADDREF_P(self->descriptor);
-  zend_hash_next_index_insert(pool->pending_list, &self->descriptor,
-                              sizeof(zval *), NULL);
-  RETURN_ZVAL(self->pool, 1, 0);
+  zend_error(E_ERROR, "Unknown field type.");
+  return 0;
 }

+ 1245 - 0
php/ext/google/protobuf/encode_decode.c

@@ -0,0 +1,1245 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "protobuf.h"
+
+/* stringsink *****************************************************************/
+
+typedef struct {
+  upb_byteshandler handler;
+  upb_bytessink sink;
+  char *ptr;
+  size_t len, size;
+} stringsink;
+
+
+static void *stringsink_start(void *_sink, const void *hd, size_t size_hint) {
+  stringsink *sink = _sink;
+  sink->len = 0;
+  return sink;
+}
+
+static size_t stringsink_string(void *_sink, const void *hd, const char *ptr,
+                                size_t len, const upb_bufhandle *handle) {
+  stringsink *sink = _sink;
+  size_t new_size = sink->size;
+
+  UPB_UNUSED(hd);
+  UPB_UNUSED(handle);
+
+  while (sink->len + len > new_size) {
+    new_size *= 2;
+  }
+
+  if (new_size != sink->size) {
+    sink->ptr = realloc(sink->ptr, new_size);
+    sink->size = new_size;
+  }
+
+  memcpy(sink->ptr + sink->len, ptr, len);
+  sink->len += len;
+
+  return len;
+}
+
+void stringsink_init(stringsink *sink) {
+  upb_byteshandler_init(&sink->handler);
+  upb_byteshandler_setstartstr(&sink->handler, stringsink_start, NULL);
+  upb_byteshandler_setstring(&sink->handler, stringsink_string, NULL);
+
+  upb_bytessink_reset(&sink->sink, &sink->handler, sink);
+
+  sink->size = 32;
+  sink->ptr = malloc(sink->size);
+  sink->len = 0;
+}
+
+void stringsink_uninit(stringsink *sink) { free(sink->ptr); }
+
+/* stackenv *****************************************************************/
+
+// Stack-allocated context during an encode/decode operation. Contains the upb
+// environment and its stack-based allocator, an initial buffer for allocations
+// to avoid malloc() when possible, and a template for PHP exception messages
+// if any error occurs.
+#define STACK_ENV_STACKBYTES 4096
+typedef struct {
+  upb_env env;
+  const char *php_error_template;
+  char allocbuf[STACK_ENV_STACKBYTES];
+} stackenv;
+
+
+static void stackenv_init(stackenv* se, const char* errmsg);
+static void stackenv_uninit(stackenv* se);
+
+// Callback invoked by upb if any error occurs during parsing or serialization.
+static bool env_error_func(void* ud, const upb_status* status) {
+    stackenv* se = ud;
+    // Free the env -- zend_error will longjmp up the stack past the
+    // encode/decode function so it would not otherwise have been freed.
+    stackenv_uninit(se);
+
+    // TODO(teboring): have a way to verify that this is actually a parse error,
+    // instead of just throwing "parse error" unconditionally.
+    zend_error(E_ERROR, se->php_error_template, upb_status_errmsg(status));
+    // Never reached.
+    return false;
+}
+
+static void stackenv_init(stackenv* se, const char* errmsg) {
+  se->php_error_template = errmsg;
+  upb_env_init2(&se->env, se->allocbuf, sizeof(se->allocbuf), NULL);
+  upb_env_seterrorfunc(&se->env, env_error_func, se);
+}
+
+static void stackenv_uninit(stackenv* se) {
+  upb_env_uninit(&se->env);
+}
+
+// -----------------------------------------------------------------------------
+// Parsing.
+// -----------------------------------------------------------------------------
+
+#define DEREF(msg, ofs, type) *(type*)(((uint8_t *)msg) + ofs)
+
+// Creates a handlerdata that simply contains the offset for this field.
+static const void* newhandlerdata(upb_handlers* h, uint32_t ofs) {
+  size_t* hd_ofs = (size_t*)malloc(sizeof(size_t));
+  *hd_ofs = ofs;
+  upb_handlers_addcleanup(h, hd_ofs, free);
+  return hd_ofs;
+}
+
+typedef struct {
+  size_t ofs;
+  const upb_msgdef *md;
+} submsg_handlerdata_t;
+
+// Creates a handlerdata that contains offset and submessage type information.
+static const void *newsubmsghandlerdata(upb_handlers* h, uint32_t ofs,
+                                        const upb_fielddef* f) {
+  submsg_handlerdata_t* hd =
+      (submsg_handlerdata_t*)malloc(sizeof(submsg_handlerdata_t));
+  hd->ofs = ofs;
+  hd->md = upb_fielddef_msgsubdef(f);
+  upb_handlers_addcleanup(h, hd, free);
+  return hd;
+}
+
+typedef struct {
+  size_t ofs;              // union data slot
+  size_t case_ofs;         // oneof_case field
+  int property_ofs;        // properties table cache
+  uint32_t oneof_case_num; // oneof-case number to place in oneof_case field
+  const upb_msgdef *md;    // msgdef, for oneof submessage handler
+} oneof_handlerdata_t;
+
+static const void *newoneofhandlerdata(upb_handlers *h,
+                                       uint32_t ofs,
+                                       uint32_t case_ofs,
+                                       int property_ofs,
+                                       const upb_fielddef *f) {
+  oneof_handlerdata_t* hd =
+      (oneof_handlerdata_t*)malloc(sizeof(oneof_handlerdata_t));
+  hd->ofs = ofs;
+  hd->case_ofs = case_ofs;
+  hd->property_ofs = property_ofs;
+  // We reuse the field tag number as a oneof union discriminant tag. Note that
+  // we don't expose these numbers to the user, so the only requirement is that
+  // we have some unique ID for each union case/possibility. The field tag
+  // numbers are already present and are easy to use so there's no reason to
+  // create a separate ID space. In addition, using the field tag number here
+  // lets us easily look up the field in the oneof accessor.
+  hd->oneof_case_num = upb_fielddef_number(f);
+  if (upb_fielddef_type(f) == UPB_TYPE_MESSAGE) {
+    hd->md = upb_fielddef_msgsubdef(f);
+  } else {
+    hd->md = NULL;
+  }
+  upb_handlers_addcleanup(h, hd, free);
+  return hd;
+}
+
+// A handler that starts a repeated field.  Gets the Repeated*Field instance for
+// this field (such an instance always exists even in an empty message).
+static void *startseq_handler(void* closure, const void* hd) {
+  MessageHeader* msg = closure;
+  const size_t *ofs = hd;
+  return (void*)(*DEREF(msg, *ofs, zval**));
+}
+
+// Handlers that append primitive values to a repeated field.
+#define DEFINE_APPEND_HANDLER(type, ctype)                             \
+  static bool append##type##_handler(void* closure, const void* hd,    \
+                                     ctype val) {                      \
+    zval* array = (zval*)closure;                                      \
+    RepeatedField* intern =                                            \
+        (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC); \
+    repeated_field_push_native(intern, &val);                          \
+    return true;                                                       \
+  }
+
+DEFINE_APPEND_HANDLER(bool,   bool)
+DEFINE_APPEND_HANDLER(int32,  int32_t)
+DEFINE_APPEND_HANDLER(uint32, uint32_t)
+DEFINE_APPEND_HANDLER(float,  float)
+DEFINE_APPEND_HANDLER(int64,  int64_t)
+DEFINE_APPEND_HANDLER(uint64, uint64_t)
+DEFINE_APPEND_HANDLER(double, double)
+
+// Appends a string to a repeated field.
+static void* appendstr_handler(void *closure,
+                               const void *hd,
+                               size_t size_hint) {
+  zval* array = (zval*)closure;
+  RepeatedField* intern =
+      (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC);
+
+  zval* str;
+  MAKE_STD_ZVAL(str);
+  ZVAL_STRING(str, "", 1);
+
+  repeated_field_push_native(intern, &str TSRMLS_CC);
+  return (void*)str;
+}
+
+// Appends a 'bytes' string to a repeated field.
+static void* appendbytes_handler(void *closure,
+                                 const void *hd,
+                                 size_t size_hint) {
+  zval* array = (zval*)closure;
+  RepeatedField* intern =
+      (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC);
+
+  zval* str;
+  MAKE_STD_ZVAL(str);
+  ZVAL_STRING(str, "", 1);
+
+  repeated_field_push_native(intern, &str TSRMLS_CC);
+  return (void*)str;
+}
+
+static void *empty_php_string(zval** value_ptr) {
+  SEPARATE_ZVAL_IF_NOT_REF(value_ptr);
+  zval* str = *value_ptr;
+  zval_dtor(str);
+  ZVAL_STRINGL(str, "", 0, 1);
+  return (void*)str;
+}
+
+// Sets a non-repeated string field in a message.
+static void* str_handler(void *closure,
+                         const void *hd,
+                         size_t size_hint) {
+  MessageHeader* msg = closure;
+  const size_t *ofs = hd;
+  return empty_php_string(DEREF(msg, *ofs, zval**));
+}
+
+// Sets a non-repeated 'bytes' field in a message.
+static void* bytes_handler(void *closure,
+                           const void *hd,
+                           size_t size_hint) {
+  MessageHeader* msg = closure;
+  const size_t *ofs = hd;
+  return empty_php_string(DEREF(msg, *ofs, zval**));
+}
+
+static size_t stringdata_handler(void* closure, const void* hd,
+                                 const char* str, size_t len,
+                                 const upb_bufhandle* handle) {
+  zval* php_str = (zval*)closure;
+
+  char* old_str = Z_STRVAL_P(php_str);
+  size_t old_len = Z_STRLEN_P(php_str);
+  assert(old_str != NULL);
+
+  char* new_str = emalloc(old_len + len + 1);
+
+  memcpy(new_str, old_str, old_len);
+  memcpy(new_str + old_len, str, len);
+  new_str[old_len + len] = 0;
+  FREE(old_str);
+
+  Z_STRVAL_P(php_str) = new_str;
+  Z_STRLEN_P(php_str) = old_len + len;
+
+  return len;
+}
+
+// Appends a submessage to a repeated field.
+static void *appendsubmsg_handler(void *closure, const void *hd) {
+  zval* array = (zval*)closure;
+  RepeatedField* intern =
+      (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC);
+
+  const submsg_handlerdata_t *submsgdata = hd;
+  zval* subdesc_php = get_def_obj((void*)submsgdata->md);
+  Descriptor* subdesc = zend_object_store_get_object(subdesc_php TSRMLS_CC);
+  zend_class_entry* subklass = subdesc->klass;
+  MessageHeader* submsg;
+
+  zval* val = NULL;
+  MAKE_STD_ZVAL(val);
+  Z_TYPE_P(val) = IS_OBJECT;
+  Z_OBJVAL_P(val) = subklass->create_object(subklass TSRMLS_CC);
+
+  repeated_field_push_native(intern, &val TSRMLS_CC);
+
+  submsg = zend_object_store_get_object(val TSRMLS_CC);
+  return submsg;
+}
+
+// Sets a non-repeated submessage field in a message.
+static void *submsg_handler(void *closure, const void *hd) {
+  MessageHeader* msg = closure;
+  const submsg_handlerdata_t* submsgdata = hd;
+  zval* subdesc_php = get_def_obj((void*)submsgdata->md);
+  Descriptor* subdesc = zend_object_store_get_object(subdesc_php TSRMLS_CC);
+  zend_class_entry* subklass = subdesc->klass;
+  zval* submsg_php;
+  MessageHeader* submsg;
+
+  if (Z_TYPE_P(*DEREF(msg, submsgdata->ofs, zval**)) == IS_NULL) {
+    zval* val = NULL;
+    MAKE_STD_ZVAL(val);
+    Z_TYPE_P(val) = IS_OBJECT;
+    Z_OBJVAL_P(val) = subklass->create_object(subklass TSRMLS_CC);
+
+    zval_ptr_dtor(DEREF(msg, submsgdata->ofs, zval**));
+    *DEREF(msg, submsgdata->ofs, zval**) = val;
+  }
+
+  submsg_php = *DEREF(msg, submsgdata->ofs, zval**);
+
+  submsg = zend_object_store_get_object(submsg_php TSRMLS_CC);
+  return submsg;
+}
+
+// Handler data for startmap/endmap handlers.
+typedef struct {
+  size_t ofs;
+  upb_fieldtype_t key_field_type;
+  upb_fieldtype_t value_field_type;
+
+  // We know that we can hold this reference because the handlerdata has the
+  // same lifetime as the upb_handlers struct, and the upb_handlers struct holds
+  // a reference to the upb_msgdef, which in turn has references to its subdefs.
+  const upb_def* value_field_subdef;
+} map_handlerdata_t;
+
+// Temporary frame for map parsing: at the beginning of a map entry message, a
+// submsg handler allocates a frame to hold (i) a reference to the Map object
+// into which this message will be inserted and (ii) storage slots to
+// temporarily hold the key and value for this map entry until the end of the
+// submessage. When the submessage ends, another handler is called to insert the
+// value into the map.
+typedef struct {
+  zval* map;
+  char key_storage[NATIVE_SLOT_MAX_SIZE];
+  char value_storage[NATIVE_SLOT_MAX_SIZE];
+} map_parse_frame_t;
+
+static void map_slot_init(void* memory, upb_fieldtype_t type) {
+  switch (type) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      // Store zval** in memory in order to be consistent with the layout of
+      // singular fields.
+      zval** holder = ALLOC(zval*);
+      zval* tmp;
+      MAKE_STD_ZVAL(tmp);
+      ZVAL_STRINGL(tmp, "", 0, 1);
+      *holder = tmp;
+      *(zval***)memory = holder;
+      break;
+    }
+    case UPB_TYPE_MESSAGE: {
+      zval** holder = ALLOC(zval*);
+      zval* tmp;
+      MAKE_STD_ZVAL(tmp);
+      ZVAL_NULL(tmp);
+      *holder = tmp;
+      *(zval***)memory = holder;
+      break;
+    }
+    default:
+      native_slot_init(type, memory, NULL);
+  }
+}
+
+static void map_slot_uninit(void* memory, upb_fieldtype_t type) {
+  switch (type) {
+    case UPB_TYPE_MESSAGE:
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      zval** holder = *(zval***)memory;
+      zval_ptr_dtor(holder);
+      FREE(holder);
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+static void map_slot_key(upb_fieldtype_t type, const void* from, char** keyval,
+                         size_t* length) {
+  if (type == UPB_TYPE_STRING) {
+    zval* key_php = **(zval***)from;
+    *keyval = Z_STRVAL_P(key_php);
+    *length = Z_STRLEN_P(key_php);
+  } else {
+    *keyval = from;
+    *length = native_slot_size(type);
+  }
+}
+
+static void map_slot_value(upb_fieldtype_t type, const void* from, upb_value* v) {
+  size_t len;
+  void* to = upb_value_memory(v);
+#ifndef NDEBUG
+  v->ctype = UPB_CTYPE_UINT64;
+#endif
+
+  memset(to, 0, native_slot_size(type));
+
+  switch (type) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+    case UPB_TYPE_MESSAGE: {
+      *(zval**)to = **(zval***)from;
+      Z_ADDREF_PP((zval**)to);
+      break;
+    }
+    default:
+      len = native_slot_size(type);
+      memcpy(to, from, len);
+  }
+}
+
+// Handler to begin a map entry: allocates a temporary frame. This is the
+// 'startsubmsg' handler on the msgdef that contains the map field.
+static void *startmapentry_handler(void *closure, const void *hd) {
+  MessageHeader* msg = closure;
+  const map_handlerdata_t* mapdata = hd;
+  zval* map = *DEREF(msg, mapdata->ofs, zval**);
+
+  map_parse_frame_t* frame = ALLOC(map_parse_frame_t);
+  frame->map = map;
+
+  map_slot_init(&frame->key_storage, mapdata->key_field_type);
+  map_slot_init(&frame->value_storage, mapdata->value_field_type);
+
+  return frame;
+}
+
+// Handler to end a map entry: inserts the value defined during the message into
+// the map. This is the 'endmsg' handler on the map entry msgdef.
+static bool endmap_handler(void *closure, const void *hd, upb_status* s) {
+  map_parse_frame_t* frame = closure;
+  const map_handlerdata_t* mapdata = hd;
+
+  Map *map = (Map *)zend_object_store_get_object(frame->map TSRMLS_CC);
+
+  const char* keyval = NULL;
+  upb_value v;
+  size_t length;
+
+  map_slot_key(map->key_type, &frame->key_storage, &keyval, &length);
+  map_slot_value(map->value_type, &frame->value_storage, &v);
+
+  map_index_set(map, keyval, length, v);
+
+  map_slot_uninit(&frame->key_storage, mapdata->key_field_type);
+  map_slot_uninit(&frame->value_storage, mapdata->value_field_type);
+  FREE(frame);
+
+  return true;
+}
+
+// Allocates a new map_handlerdata_t given the map entry message definition. If
+// the offset of the field within the parent message is also given, that is
+// added to the handler data as well. Note that this is called *twice* per map
+// field: once in the parent message handler setup when setting the startsubmsg
+// handler and once in the map entry message handler setup when setting the
+// key/value and endmsg handlers. The reason is that there is no easy way to
+// pass the handlerdata down to the sub-message handler setup.
+static map_handlerdata_t* new_map_handlerdata(
+    size_t ofs,
+    const upb_msgdef* mapentry_def,
+    Descriptor* desc) {
+  const upb_fielddef* key_field;
+  const upb_fielddef* value_field;
+  // TODO(teboring): Use emalloc and efree.
+  map_handlerdata_t* hd =
+      (map_handlerdata_t*)malloc(sizeof(map_handlerdata_t));
+
+  hd->ofs = ofs;
+  key_field = upb_msgdef_itof(mapentry_def, MAP_KEY_FIELD);
+  assert(key_field != NULL);
+  hd->key_field_type = upb_fielddef_type(key_field);
+  value_field = upb_msgdef_itof(mapentry_def, MAP_VALUE_FIELD);
+  assert(value_field != NULL);
+  hd->value_field_type = upb_fielddef_type(value_field);
+  hd->value_field_subdef = upb_fielddef_subdef(value_field);
+
+  return hd;
+}
+
+// Handlers that set primitive values in oneofs.
+#define DEFINE_ONEOF_HANDLER(type, ctype)                           \
+  static bool oneof##type##_handler(void *closure, const void *hd,  \
+                                     ctype val) {                   \
+    const oneof_handlerdata_t *oneofdata = hd;                      \
+    DEREF(closure, oneofdata->case_ofs, uint32_t) =                 \
+        oneofdata->oneof_case_num;                                  \
+    DEREF(closure, oneofdata->ofs, ctype) = val;                    \
+    return true;                                                    \
+  }
+
+DEFINE_ONEOF_HANDLER(bool,   bool)
+DEFINE_ONEOF_HANDLER(int32,  int32_t)
+DEFINE_ONEOF_HANDLER(uint32, uint32_t)
+DEFINE_ONEOF_HANDLER(float,  float)
+DEFINE_ONEOF_HANDLER(int64,  int64_t)
+DEFINE_ONEOF_HANDLER(uint64, uint64_t)
+DEFINE_ONEOF_HANDLER(double, double)
+
+#undef DEFINE_ONEOF_HANDLER
+
+// Handlers for strings in a oneof.
+static void *oneofstr_handler(void *closure,
+                              const void *hd,
+                              size_t size_hint) {
+  MessageHeader* msg = closure;
+  const oneof_handlerdata_t *oneofdata = hd;
+
+  DEREF(msg, oneofdata->case_ofs, uint32_t) =
+      oneofdata->oneof_case_num;
+  DEREF(msg, oneofdata->ofs, zval**) =
+      &(msg->std.properties_table)[oneofdata->property_ofs];
+
+  return empty_php_string(DEREF(msg, oneofdata->ofs, zval**));
+}
+
+static void *oneofbytes_handler(void *closure,
+                                const void *hd,
+                                size_t size_hint) {
+  MessageHeader* msg = closure;
+  const oneof_handlerdata_t *oneofdata = hd;
+
+  DEREF(msg, oneofdata->case_ofs, uint32_t) =
+      oneofdata->oneof_case_num;
+  DEREF(msg, oneofdata->ofs, zval**) =
+      &(msg->std.properties_table)[oneofdata->property_ofs];
+
+  // TODO(teboring): Add it back.
+  // rb_enc_associate(str, kRubyString8bitEncoding);
+
+  SEPARATE_ZVAL_IF_NOT_REF(DEREF(msg, oneofdata->ofs, zval**));
+  zval* str = *DEREF(msg, oneofdata->ofs, zval**);
+  zval_dtor(str);
+  ZVAL_STRINGL(str, "", 0, 1);
+  return (void*)str;
+}
+
+// Handler for a submessage field in a oneof.
+static void *oneofsubmsg_handler(void *closure,
+                                 const void *hd) {
+  MessageHeader* msg = closure;
+  const oneof_handlerdata_t *oneofdata = hd;
+  uint32_t oldcase = DEREF(msg, oneofdata->case_ofs, uint32_t);
+  zval* subdesc_php = get_def_obj((void*)oneofdata->md);
+  Descriptor* subdesc = zend_object_store_get_object(subdesc_php TSRMLS_CC);
+  zend_class_entry* subklass = subdesc->klass;
+  zval* submsg_php;
+  MessageHeader* submsg;
+
+  if (oldcase != oneofdata->oneof_case_num) {
+    DEREF(msg, oneofdata->ofs, zval**) =
+        &(msg->std.properties_table)[oneofdata->property_ofs];
+  }
+
+  if (Z_TYPE_P(*DEREF(msg, oneofdata->ofs, zval**)) == IS_NULL) {
+    zval* val = NULL;
+    MAKE_STD_ZVAL(val);
+    Z_TYPE_P(val) = IS_OBJECT;
+    Z_OBJVAL_P(val) = subklass->create_object(subklass TSRMLS_CC);
+
+    zval_ptr_dtor(DEREF(msg, oneofdata->ofs, zval**));
+    *DEREF(msg, oneofdata->ofs, zval**) = val;
+  }
+
+  DEREF(msg, oneofdata->case_ofs, uint32_t) =
+      oneofdata->oneof_case_num;
+
+  submsg_php = *DEREF(msg, oneofdata->ofs, zval**);
+  submsg = zend_object_store_get_object(submsg_php TSRMLS_CC);
+  return submsg;
+}
+
+// Set up handlers for a repeated field.
+static void add_handlers_for_repeated_field(upb_handlers *h,
+                                            const upb_fielddef *f,
+                                            size_t offset) {
+  upb_handlerattr attr = UPB_HANDLERATTR_INITIALIZER;
+  upb_handlerattr_sethandlerdata(&attr, newhandlerdata(h, offset));
+  upb_handlers_setstartseq(h, f, startseq_handler, &attr);
+  upb_handlerattr_uninit(&attr);
+
+  switch (upb_fielddef_type(f)) {
+
+#define SET_HANDLER(utype, ltype)                                 \
+  case utype:                                                     \
+    upb_handlers_set##ltype(h, f, append##ltype##_handler, NULL); \
+    break;
+
+    SET_HANDLER(UPB_TYPE_BOOL,   bool);
+    SET_HANDLER(UPB_TYPE_INT32,  int32);
+    SET_HANDLER(UPB_TYPE_UINT32, uint32);
+    SET_HANDLER(UPB_TYPE_ENUM,   int32);
+    SET_HANDLER(UPB_TYPE_FLOAT,  float);
+    SET_HANDLER(UPB_TYPE_INT64,  int64);
+    SET_HANDLER(UPB_TYPE_UINT64, uint64);
+    SET_HANDLER(UPB_TYPE_DOUBLE, double);
+
+#undef SET_HANDLER
+
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      bool is_bytes = upb_fielddef_type(f) == UPB_TYPE_BYTES;
+      upb_handlers_setstartstr(h, f, is_bytes ?
+                               appendbytes_handler : appendstr_handler,
+                               NULL);
+      upb_handlers_setstring(h, f, stringdata_handler, NULL);
+      break;
+    }
+    case UPB_TYPE_MESSAGE: {
+      upb_handlerattr attr = UPB_HANDLERATTR_INITIALIZER;
+      upb_handlerattr_sethandlerdata(&attr, newsubmsghandlerdata(h, 0, f));
+      upb_handlers_setstartsubmsg(h, f, appendsubmsg_handler, &attr);
+      upb_handlerattr_uninit(&attr);
+      break;
+    }
+  }
+}
+
+// Set up handlers for a singular field.
+static void add_handlers_for_singular_field(upb_handlers *h,
+                                            const upb_fielddef *f,
+                                            size_t offset) {
+  switch (upb_fielddef_type(f)) {
+    case UPB_TYPE_BOOL:
+    case UPB_TYPE_INT32:
+    case UPB_TYPE_UINT32:
+    case UPB_TYPE_ENUM:
+    case UPB_TYPE_FLOAT:
+    case UPB_TYPE_INT64:
+    case UPB_TYPE_UINT64:
+    case UPB_TYPE_DOUBLE:
+      upb_shim_set(h, f, offset, -1);
+      break;
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      bool is_bytes = upb_fielddef_type(f) == UPB_TYPE_BYTES;
+      upb_handlerattr attr = UPB_HANDLERATTR_INITIALIZER;
+      upb_handlerattr_sethandlerdata(&attr, newhandlerdata(h, offset));
+      upb_handlers_setstartstr(h, f,
+                               is_bytes ? bytes_handler : str_handler,
+                               &attr);
+      upb_handlers_setstring(h, f, stringdata_handler, &attr);
+      upb_handlerattr_uninit(&attr);
+      break;
+    }
+    case UPB_TYPE_MESSAGE: {
+      upb_handlerattr attr = UPB_HANDLERATTR_INITIALIZER;
+      upb_handlerattr_sethandlerdata(&attr, newsubmsghandlerdata(h, offset, f));
+      upb_handlers_setstartsubmsg(h, f, submsg_handler, &attr);
+      upb_handlerattr_uninit(&attr);
+      break;
+    }
+  }
+}
+
+// Adds handlers to a map field.
+static void add_handlers_for_mapfield(upb_handlers* h,
+                                      const upb_fielddef* fielddef,
+                                      size_t offset,
+                                      Descriptor* desc) {
+  const upb_msgdef* map_msgdef = upb_fielddef_msgsubdef(fielddef);
+  map_handlerdata_t* hd = new_map_handlerdata(offset, map_msgdef, desc);
+  upb_handlerattr attr = UPB_HANDLERATTR_INITIALIZER;
+
+  upb_handlers_addcleanup(h, hd, free);
+  upb_handlerattr_sethandlerdata(&attr, hd);
+  upb_handlers_setstartsubmsg(h, fielddef, startmapentry_handler, &attr);
+  upb_handlerattr_uninit(&attr);
+}
+
+// Adds handlers to a map-entry msgdef.
+static void add_handlers_for_mapentry(const upb_msgdef* msgdef, upb_handlers* h,
+                                      Descriptor* desc) {
+  const upb_fielddef* key_field = map_entry_key(msgdef);
+  const upb_fielddef* value_field = map_entry_value(msgdef);
+  map_handlerdata_t* hd = new_map_handlerdata(0, msgdef, desc);
+  upb_handlerattr attr = UPB_HANDLERATTR_INITIALIZER;
+
+  upb_handlers_addcleanup(h, hd, free);
+  upb_handlerattr_sethandlerdata(&attr, hd);
+  upb_handlers_setendmsg(h, endmap_handler, &attr);
+
+  add_handlers_for_singular_field(h, key_field,
+                                  offsetof(map_parse_frame_t, key_storage));
+  add_handlers_for_singular_field(h, value_field,
+                                  offsetof(map_parse_frame_t, value_storage));
+}
+
+// Set up handlers for a oneof field.
+static void add_handlers_for_oneof_field(upb_handlers *h,
+                                         const upb_fielddef *f,
+                                         size_t offset,
+                                         size_t oneof_case_offset,
+                                         int property_cache_offset) {
+
+  upb_handlerattr attr = UPB_HANDLERATTR_INITIALIZER;
+  upb_handlerattr_sethandlerdata(
+      &attr, newoneofhandlerdata(h, offset, oneof_case_offset,
+                                 property_cache_offset, f));
+
+  switch (upb_fielddef_type(f)) {
+
+#define SET_HANDLER(utype, ltype)                                 \
+  case utype:                                                     \
+    upb_handlers_set##ltype(h, f, oneof##ltype##_handler, &attr); \
+    break;
+
+    SET_HANDLER(UPB_TYPE_BOOL,   bool);
+    SET_HANDLER(UPB_TYPE_INT32,  int32);
+    SET_HANDLER(UPB_TYPE_UINT32, uint32);
+    SET_HANDLER(UPB_TYPE_ENUM,   int32);
+    SET_HANDLER(UPB_TYPE_FLOAT,  float);
+    SET_HANDLER(UPB_TYPE_INT64,  int64);
+    SET_HANDLER(UPB_TYPE_UINT64, uint64);
+    SET_HANDLER(UPB_TYPE_DOUBLE, double);
+
+#undef SET_HANDLER
+
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      bool is_bytes = upb_fielddef_type(f) == UPB_TYPE_BYTES;
+      upb_handlers_setstartstr(h, f, is_bytes ?
+                               oneofbytes_handler : oneofstr_handler,
+                               &attr);
+      upb_handlers_setstring(h, f, stringdata_handler, NULL);
+      break;
+    }
+    case UPB_TYPE_MESSAGE: {
+      upb_handlers_setstartsubmsg(h, f, oneofsubmsg_handler, &attr);
+      break;
+    }
+  }
+
+  upb_handlerattr_uninit(&attr);
+}
+
+static void add_handlers_for_message(const void *closure, upb_handlers *h) {
+  const upb_msgdef* msgdef = upb_handlers_msgdef(h);
+  Descriptor* desc = (Descriptor*)zend_object_store_get_object(
+      get_def_obj((void*)msgdef) TSRMLS_CC);
+  upb_msg_field_iter i;
+
+  // If this is a mapentry message type, set up a special set of handlers and
+  // bail out of the normal (user-defined) message type handling.
+  if (upb_msgdef_mapentry(msgdef)) {
+    add_handlers_for_mapentry(msgdef, h, desc);
+    return;
+  }
+
+  // Ensure layout exists. We may be invoked to create handlers for a given
+  // message if we are included as a submsg of another message type before our
+  // class is actually built, so to work around this, we just create the layout
+  // (and handlers, in the class-building function) on-demand.
+  if (desc->layout == NULL) {
+    desc->layout = create_layout(desc->msgdef);
+  }
+
+  for (upb_msg_field_begin(&i, desc->msgdef);
+       !upb_msg_field_done(&i);
+       upb_msg_field_next(&i)) {
+    const upb_fielddef *f = upb_msg_iter_field(&i);
+    size_t offset = desc->layout->fields[upb_fielddef_index(f)].offset +
+        sizeof(MessageHeader);
+
+    if (upb_fielddef_containingoneof(f)) {
+      size_t oneof_case_offset =
+          desc->layout->fields[upb_fielddef_index(f)].case_offset +
+          sizeof(MessageHeader);
+      int property_cache_index =
+          desc->layout->fields[upb_fielddef_index(f)].cache_index;
+      add_handlers_for_oneof_field(h, f, offset, oneof_case_offset,
+                                   property_cache_index);
+    } else if (is_map_field(f)) {
+      add_handlers_for_mapfield(h, f, offset, desc);
+    } else if (upb_fielddef_isseq(f)) {
+      add_handlers_for_repeated_field(h, f, offset);
+    } else {
+      add_handlers_for_singular_field(h, f, offset);
+    }
+  }
+}
+
+// Creates upb handlers for populating a message.
+static const upb_handlers *new_fill_handlers(Descriptor* desc,
+                                             const void* owner) {
+  // TODO(cfallin, haberman): once upb gets a caching/memoization layer for
+  // handlers, reuse subdef handlers so that e.g. if we already parse
+  // B-with-field-of-type-C, we don't have to rebuild the whole hierarchy to
+  // parse A-with-field-of-type-B-with-field-of-type-C.
+  return upb_handlers_newfrozen(desc->msgdef, owner,
+                                add_handlers_for_message, NULL);
+}
+
+// Constructs the handlers for filling a message's data into an in-memory
+// object.
+const upb_handlers* get_fill_handlers(Descriptor* desc) {
+  if (!desc->fill_handlers) {
+    desc->fill_handlers =
+        new_fill_handlers(desc, &desc->fill_handlers);
+  }
+  return desc->fill_handlers;
+}
+
+const upb_pbdecodermethod *new_fillmsg_decodermethod(Descriptor* desc,
+                                                     const void* owner) {
+  const upb_handlers* handlers = get_fill_handlers(desc);
+  upb_pbdecodermethodopts opts;
+  upb_pbdecodermethodopts_init(&opts, handlers);
+
+  return upb_pbdecodermethod_new(&opts, owner);
+}
+
+static const upb_pbdecodermethod *msgdef_decodermethod(Descriptor* desc) {
+  if (desc->fill_method == NULL) {
+    desc->fill_method = new_fillmsg_decodermethod(
+        desc, &desc->fill_method);
+  }
+  return desc->fill_method;
+}
+
+// -----------------------------------------------------------------------------
+// Serializing.
+// -----------------------------------------------------------------------------
+
+static void putmsg(zval* msg, const Descriptor* desc, upb_sink* sink,
+                   int depth);
+
+static void putstr(zval* str, const upb_fielddef* f, upb_sink* sink);
+
+static void putrawstr(const char* str, int len, const upb_fielddef* f,
+                      upb_sink* sink);
+
+static void putsubmsg(zval* submsg, const upb_fielddef* f, upb_sink* sink,
+                      int depth);
+
+static void putarray(zval* array, const upb_fielddef* f, upb_sink* sink,
+                     int depth);
+static void putmap(zval* map, const upb_fielddef* f, upb_sink* sink, int depth);
+
+static upb_selector_t getsel(const upb_fielddef* f, upb_handlertype_t type) {
+  upb_selector_t ret;
+  bool ok = upb_handlers_getselector(f, type, &ret);
+  UPB_ASSERT(ok);
+  return ret;
+}
+
+static void put_optional_value(void* memory, int len, const upb_fielddef* f,
+                               int depth, upb_sink* sink) {
+  assert(upb_fielddef_label(f) == UPB_LABEL_OPTIONAL);
+
+  switch (upb_fielddef_type(f)) {
+#define T(upbtypeconst, upbtype, ctype, default_value)                         \
+  case upbtypeconst: {                                                         \
+    ctype value = DEREF(memory, 0, ctype);                                     \
+    if (value != default_value) {                                              \
+      upb_selector_t sel = getsel(f, upb_handlers_getprimitivehandlertype(f)); \
+      upb_sink_put##upbtype(sink, sel, value);                                 \
+    }                                                                          \
+  } break;
+
+    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_ENUM, int32, int32_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)
+
+#undef T
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+      putrawstr(memory, len, f, sink);
+      break;
+    case UPB_TYPE_MESSAGE: {
+      zval* submsg = *(zval**)memory;
+      putsubmsg(submsg, f, sink, depth);
+      break;
+    }
+    default:
+      assert(false);
+  }
+}
+
+// Only string/bytes fields are stored as zval.
+static const char* raw_value(void* memory, const upb_fielddef* f) {
+  switch (upb_fielddef_type(f)) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+      return Z_STRVAL_PP((zval**)memory);
+      break;
+    default:
+      return memory;
+  }
+}
+
+static int raw_value_len(void* memory, int len, const upb_fielddef* f) {
+  switch (upb_fielddef_type(f)) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+      return Z_STRLEN_PP((zval**)memory);
+      break;
+    default:
+      return len;
+  }
+}
+
+static void putmap(zval* map, const upb_fielddef* f, upb_sink* sink,
+                   int depth) {
+  Map* self;
+  upb_sink subsink;
+  const upb_fielddef* key_field;
+  const upb_fielddef* value_field;
+  MapIter it;
+  int len;
+
+  if (map == NULL) return;
+  self = UNBOX(Map, map);
+
+  upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
+
+  assert(upb_fielddef_type(f) == UPB_TYPE_MESSAGE);
+  key_field = map_field_key(f);
+  value_field = map_field_value(f);
+
+  for (map_begin(map, &it); !map_done(&it); map_next(&it)) {
+    upb_status status;
+
+    upb_sink entry_sink;
+    upb_sink_startsubmsg(&subsink, getsel(f, UPB_HANDLER_STARTSUBMSG),
+                         &entry_sink);
+    upb_sink_startmsg(&entry_sink);
+
+    // Serialize key.
+    const char *key = map_iter_key(&it, &len);
+    put_optional_value(key, len, key_field, depth + 1, &entry_sink);
+
+    // Serialize value.
+    upb_value value = map_iter_value(&it, &len);
+    put_optional_value(raw_value(upb_value_memory(&value), value_field),
+                       raw_value_len(upb_value_memory(&value), len, value_field),
+                       value_field, depth + 1, &entry_sink);
+
+    upb_sink_endmsg(&entry_sink, &status);
+    upb_sink_endsubmsg(&subsink, getsel(f, UPB_HANDLER_ENDSUBMSG));
+  }
+
+  upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
+}
+
+static void putmsg(zval* msg_php, const Descriptor* desc, upb_sink* sink,
+                   int depth) {
+  upb_msg_field_iter i;
+  upb_status status;
+
+  upb_sink_startmsg(sink);
+
+  // Protect against cycles (possible because users may freely reassign message
+  // and repeated fields) by imposing a maximum recursion depth.
+  if (depth > ENCODE_MAX_NESTING) {
+    zend_error(E_ERROR,
+             "Maximum recursion depth exceeded during encoding.");
+  }
+
+  MessageHeader* msg = zend_object_store_get_object(msg_php TSRMLS_CC);
+
+  for (upb_msg_field_begin(&i, desc->msgdef); !upb_msg_field_done(&i);
+       upb_msg_field_next(&i)) {
+    upb_fielddef* f = upb_msg_iter_field(&i);
+    uint32_t offset = desc->layout->fields[upb_fielddef_index(f)].offset +
+                      sizeof(MessageHeader);
+
+    if (upb_fielddef_containingoneof(f)) {
+      uint32_t oneof_case_offset =
+          desc->layout->fields[upb_fielddef_index(f)].case_offset +
+          sizeof(MessageHeader);
+      // For a oneof, check that this field is actually present -- skip all the
+      // below if not.
+      if (DEREF(msg, oneof_case_offset, uint32_t) != upb_fielddef_number(f)) {
+        continue;
+      }
+      // Otherwise, fall through to the appropriate singular-field handler
+      // below.
+    }
+
+    if (is_map_field(f)) {
+      zval* map = *DEREF(msg, offset, zval**);
+      if (map != NULL) {
+        putmap(map, f, sink, depth);
+      }
+    } else if (upb_fielddef_isseq(f)) {
+      zval* array = *DEREF(msg, offset, zval**);
+      if (array != NULL) {
+        putarray(array, f, sink, depth);
+      }
+    } else if (upb_fielddef_isstring(f)) {
+      zval* str = *DEREF(msg, offset, zval**);
+      if (Z_STRLEN_P(str) > 0) {
+        putstr(str, f, sink);
+      }
+    } else if (upb_fielddef_issubmsg(f)) {
+      putsubmsg(*DEREF(msg, offset, zval**), f, sink, depth);
+    } else {
+      upb_selector_t sel = getsel(f, upb_handlers_getprimitivehandlertype(f));
+
+#define T(upbtypeconst, upbtype, ctype, default_value) \
+  case upbtypeconst: {                                 \
+    ctype value = DEREF(msg, offset, ctype);           \
+    if (value != default_value) {                      \
+      upb_sink_put##upbtype(sink, sel, value);         \
+    }                                                  \
+  } break;
+
+      switch (upb_fielddef_type(f)) {
+        T(UPB_TYPE_FLOAT, float, float, 0.0)
+        T(UPB_TYPE_DOUBLE, double, double, 0.0)
+        T(UPB_TYPE_BOOL, bool, uint8_t, 0)
+        case UPB_TYPE_ENUM:
+          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:
+        case UPB_TYPE_MESSAGE:
+          zend_error(E_ERROR, "Internal error.");
+      }
+
+#undef T
+    }
+  }
+
+  upb_sink_endmsg(sink, &status);
+}
+
+static void putstr(zval* str, const upb_fielddef *f, upb_sink *sink) {
+  upb_sink subsink;
+
+  if (ZVAL_IS_NULL(str)) return;
+
+  assert(Z_TYPE_P(str) == IS_STRING);
+
+  // Ensure that the string has the correct encoding. We also check at field-set
+  // time, but the user may have mutated the string object since then.
+  if (upb_fielddef_type(f) == UPB_TYPE_STRING &&
+      !is_structurally_valid_utf8(Z_STRVAL_P(str), Z_STRLEN_P(str))) {
+    zend_error(E_USER_ERROR, "Given string is not UTF8 encoded.");
+    return;
+  }
+
+  upb_sink_startstr(sink, getsel(f, UPB_HANDLER_STARTSTR), Z_STRLEN_P(str),
+                    &subsink);
+  upb_sink_putstring(&subsink, getsel(f, UPB_HANDLER_STRING), Z_STRVAL_P(str),
+                     Z_STRLEN_P(str), NULL);
+  upb_sink_endstr(sink, getsel(f, UPB_HANDLER_ENDSTR));
+}
+
+static void putrawstr(const char* str, int len, const upb_fielddef* f,
+                      upb_sink* sink) {
+  upb_sink subsink;
+
+  if (len == 0) return;
+
+  // Ensure that the string has the correct encoding. We also check at field-set
+  // time, but the user may have mutated the string object since then.
+  if (upb_fielddef_type(f) == UPB_TYPE_STRING &&
+      !is_structurally_valid_utf8(str, len)) {
+    zend_error(E_USER_ERROR, "Given string is not UTF8 encoded.");
+    return;
+  }
+
+  upb_sink_startstr(sink, getsel(f, UPB_HANDLER_STARTSTR), len, &subsink);
+  upb_sink_putstring(&subsink, getsel(f, UPB_HANDLER_STRING), str, len, NULL);
+  upb_sink_endstr(sink, getsel(f, UPB_HANDLER_ENDSTR));
+}
+
+static void putsubmsg(zval* submsg, const upb_fielddef* f, upb_sink* sink,
+                      int depth) {
+  upb_sink subsink;
+
+  if (Z_TYPE_P(submsg) == IS_NULL) return;
+
+  zval* php_descriptor = get_def_obj(upb_fielddef_msgsubdef(f));
+  Descriptor* subdesc =
+      (Descriptor*)zend_object_store_get_object(php_descriptor TSRMLS_CC);
+
+  upb_sink_startsubmsg(sink, getsel(f, UPB_HANDLER_STARTSUBMSG), &subsink);
+  putmsg(submsg, subdesc, &subsink, depth + 1);
+  upb_sink_endsubmsg(sink, getsel(f, UPB_HANDLER_ENDSUBMSG));
+}
+
+static void putarray(zval* array, const upb_fielddef* f, upb_sink* sink,
+                     int depth) {
+  upb_sink subsink;
+  upb_fieldtype_t type = upb_fielddef_type(f);
+  upb_selector_t sel = 0;
+  int size, i;
+
+  assert(array != NULL);
+  RepeatedField* intern =
+      (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC);
+  size = zend_hash_num_elements(HASH_OF(intern->array));
+  if (size == 0) return;
+
+  upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
+
+  if (upb_fielddef_isprimitive(f)) {
+    sel = getsel(f, upb_handlers_getprimitivehandlertype(f));
+  }
+
+  for (i = 0; i < size; i++) {
+    void* memory = repeated_field_index_native(intern, i);
+    switch (type) {
+#define T(upbtypeconst, upbtype, ctype)                      \
+  case upbtypeconst:                                         \
+    upb_sink_put##upbtype(&subsink, sel, *((ctype*)memory)); \
+    break;
+
+      T(UPB_TYPE_FLOAT, float, float)
+      T(UPB_TYPE_DOUBLE, double, double)
+      T(UPB_TYPE_BOOL, bool, int8_t)
+      case UPB_TYPE_ENUM:
+        T(UPB_TYPE_INT32, int32, int32_t)
+        T(UPB_TYPE_UINT32, uint32, uint32_t)
+        T(UPB_TYPE_INT64, int64, int64_t)
+        T(UPB_TYPE_UINT64, uint64, uint64_t)
+
+      case UPB_TYPE_STRING:
+      case UPB_TYPE_BYTES:
+        putstr(*((zval**)memory), f, &subsink);
+        break;
+      case UPB_TYPE_MESSAGE:
+        putsubmsg(*((zval**)memory), f, &subsink, depth);
+        break;
+
+#undef T
+    }
+  }
+  upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
+}
+
+static const upb_handlers* msgdef_pb_serialize_handlers(Descriptor* desc) {
+  if (desc->pb_serialize_handlers == NULL) {
+    desc->pb_serialize_handlers =
+        upb_pb_encoder_newhandlers(desc->msgdef, &desc->pb_serialize_handlers);
+  }
+  return desc->pb_serialize_handlers;
+}
+
+// -----------------------------------------------------------------------------
+// PHP encode/decode methods
+// -----------------------------------------------------------------------------
+
+PHP_METHOD(Message, encode) {
+  zval* php_descriptor = get_ce_obj(Z_OBJCE_P(getThis()));
+  Descriptor* desc =
+      (Descriptor*)zend_object_store_get_object(php_descriptor TSRMLS_CC);
+
+  stringsink sink;
+  stringsink_init(&sink);
+
+  {
+    const upb_handlers* serialize_handlers = msgdef_pb_serialize_handlers(desc);
+
+    stackenv se;
+    upb_pb_encoder* encoder;
+
+    stackenv_init(&se, "Error occurred during encoding: %s");
+    encoder = upb_pb_encoder_create(&se.env, serialize_handlers, &sink.sink);
+
+    putmsg(getThis(), desc, upb_pb_encoder_input(encoder), 0);
+
+    RETVAL_STRINGL(sink.ptr, sink.len, 1);
+
+    stackenv_uninit(&se);
+    stringsink_uninit(&sink);
+  }
+}
+
+PHP_METHOD(Message, decode) {
+  zval* php_descriptor = get_ce_obj(Z_OBJCE_P(getThis()));
+  Descriptor* desc =
+      (Descriptor*)zend_object_store_get_object(php_descriptor TSRMLS_CC);
+  MessageHeader* msg = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+  char *data = NULL;
+  int data_len;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &data, &data_len) ==
+      FAILURE) {
+    return;
+  }
+
+  {
+    const upb_pbdecodermethod* method = msgdef_decodermethod(desc);
+    const upb_handlers* h = upb_pbdecodermethod_desthandlers(method);
+    stackenv se;
+    upb_sink sink;
+    upb_pbdecoder* decoder;
+    stackenv_init(&se, "Error occurred during parsing: %s");
+
+    upb_sink_reset(&sink, h, msg);
+    decoder = upb_pbdecoder_create(&se.env, method, &sink);
+    upb_bufsrc_putbuf(data, data_len, upb_pbdecoder_input(decoder));
+
+    stackenv_uninit(&se);
+  }
+}

+ 470 - 0
php/ext/google/protobuf/map.c

@@ -0,0 +1,470 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <ext/spl/spl_iterators.h>
+#include <Zend/zend_API.h>
+#include <Zend/zend_interfaces.h>
+
+#include "protobuf.h"
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetGet, 0, 0, 1)
+  ZEND_ARG_INFO(0, index)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetSet, 0, 0, 2)
+  ZEND_ARG_INFO(0, index)
+  ZEND_ARG_INFO(0, newval)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_void, 0)
+ZEND_END_ARG_INFO()
+
+// Utilities
+
+void* upb_value_memory(upb_value* v) {
+  return (void*)(&v->val);
+}
+
+// -----------------------------------------------------------------------------
+// Basic map operations on top of upb's strtable.
+//
+// Note that we roll our own `Map` container here because, as for
+// `RepeatedField`, we want a strongly-typed container. This is so that any user
+// errors due to incorrect map key or value types are raised as close as
+// possible to the error site, rather than at some deferred point (e.g.,
+// serialization).
+//
+// We build our `Map` on top of upb_strtable so that we're able to take
+// advantage of the native_slot storage abstraction, as RepeatedField does.
+// (This is not quite a perfect mapping -- see the key conversions below -- but
+// gives us full support and error-checking for all value types for free.)
+// -----------------------------------------------------------------------------
+
+// Map values are stored using the native_slot abstraction (as with repeated
+// field values), but keys are a bit special. Since we use a strtable, we need
+// to store keys as sequences of bytes such that equality of those bytes maps
+// one-to-one to equality of keys. We store strings directly (i.e., they map to
+// their own bytes) and integers as native integers (using the native_slot
+// abstraction).
+
+// Note that there is another tradeoff here in keeping string keys as native
+// strings rather than PHP strings: traversing the Map requires conversion to
+// PHP string values on every traversal, potentially creating more garbage. We
+// should consider ways to cache a PHP version of the key if this becomes an
+// issue later.
+
+// Forms a key to use with the underlying strtable from a PHP key value. |buf|
+// must point to TABLE_KEY_BUF_LENGTH bytes of temporary space, used to
+// construct a key byte sequence if needed. |out_key| and |out_length| provide
+// the resulting key data/length.
+#define TABLE_KEY_BUF_LENGTH 8  // sizeof(uint64_t)
+static bool table_key(Map* self, zval* key,
+                      char* buf,
+                      const char** out_key,
+                      size_t* out_length) {
+  switch (self->key_type) {
+    case UPB_TYPE_STRING:
+      if (!protobuf_convert_to_string(key)) {
+        return false;
+      }
+      if (!is_structurally_valid_utf8(Z_STRVAL_P(key), Z_STRLEN_P(key))) {
+        zend_error(E_USER_ERROR, "Given key is not UTF8 encoded.");
+        return false;
+      }
+      *out_key = Z_STRVAL_P(key);
+      *out_length = Z_STRLEN_P(key);
+      break;
+
+#define CASE_TYPE(upb_type, type, c_type, php_type)                   \
+  case UPB_TYPE_##upb_type: {                                         \
+    c_type type##_value;                                              \
+    if (!protobuf_convert_to_##type(key, &type##_value)) {            \
+      return false;                                                   \
+    }                                                                 \
+    native_slot_set(self->key_type, NULL, buf, key);                  \
+    *out_key = buf;                                                   \
+    *out_length = native_slot_size(self->key_type);                   \
+    break;                                                            \
+  }
+      CASE_TYPE(BOOL, bool, int8_t, BOOL)
+      CASE_TYPE(INT32, int32, int32_t, LONG)
+      CASE_TYPE(INT64, int64, int64_t, LONG)
+      CASE_TYPE(UINT32, uint32, uint32_t, LONG)
+      CASE_TYPE(UINT64, uint64, uint64_t, LONG)
+
+#undef CASE_TYPE
+
+    default:
+      // Map constructor should not allow a Map with another key type to be
+      // constructed.
+      assert(false);
+      break;
+  }
+
+  return true;
+}
+
+// -----------------------------------------------------------------------------
+// MapField methods
+// -----------------------------------------------------------------------------
+
+static zend_function_entry map_field_methods[] = {
+  PHP_ME(MapField, __construct,  NULL,              ZEND_ACC_PUBLIC)
+  PHP_ME(MapField, offsetExists, arginfo_offsetGet, ZEND_ACC_PUBLIC)
+  PHP_ME(MapField, offsetGet,    arginfo_offsetGet, ZEND_ACC_PUBLIC)
+  PHP_ME(MapField, offsetSet,    arginfo_offsetSet, ZEND_ACC_PUBLIC)
+  PHP_ME(MapField, offsetUnset,  arginfo_offsetGet, ZEND_ACC_PUBLIC)
+  PHP_ME(MapField, count,        arginfo_void,      ZEND_ACC_PUBLIC)
+  ZEND_FE_END
+};
+
+// -----------------------------------------------------------------------------
+// MapField creation/desctruction
+// -----------------------------------------------------------------------------
+
+zend_class_entry* map_field_type;
+zend_object_handlers* map_field_handlers;
+
+static map_begin_internal(Map *map, MapIter *iter) {
+  iter->self = map;
+  upb_strtable_begin(&iter->it, &map->table);
+}
+
+static HashTable *map_field_get_gc(zval *object, zval ***table,
+                                        int *n TSRMLS_DC) {
+  // TODO(teboring): Unfortunately, zend engine does not support garbage
+  // collection for custom array. We have to use zend engine's native array
+  // instead.
+  *table = NULL;
+  *n = 0;
+  return NULL;
+}
+
+void map_field_init(TSRMLS_D) {
+  zend_class_entry class_type;
+  const char* class_name = "Google\\Protobuf\\Internal\\MapField";
+  INIT_CLASS_ENTRY_EX(class_type, class_name, strlen(class_name),
+                      map_field_methods);
+
+  map_field_type = zend_register_internal_class(&class_type TSRMLS_CC);
+  map_field_type->create_object = map_field_create;
+
+  zend_class_implements(map_field_type TSRMLS_CC, 2, spl_ce_ArrayAccess,
+                        spl_ce_Countable);
+
+  map_field_handlers = PEMALLOC(zend_object_handlers);
+  memcpy(map_field_handlers, zend_get_std_object_handlers(),
+         sizeof(zend_object_handlers));
+  map_field_handlers->get_gc = map_field_get_gc;
+}
+
+zend_object_value map_field_create(zend_class_entry *ce TSRMLS_DC) {
+  zend_object_value retval = {0};
+  Map *intern;
+
+  intern = emalloc(sizeof(Map));
+  memset(intern, 0, sizeof(Map));
+
+  zend_object_std_init(&intern->std, ce TSRMLS_CC);
+  object_properties_init(&intern->std, ce);
+
+  // Table value type is always UINT64: this ensures enough space to store the
+  // native_slot value.
+  if (!upb_strtable_init(&intern->table, UPB_CTYPE_UINT64)) {
+    zend_error(E_USER_ERROR, "Could not allocate table.");
+  }
+
+  retval.handle = zend_objects_store_put(
+      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,
+      (zend_objects_free_object_storage_t)map_field_free, NULL TSRMLS_CC);
+  retval.handlers = map_field_handlers;
+
+  return retval;
+}
+
+void map_field_free(void *object TSRMLS_DC) {
+  Map *map = (Map *)object;
+
+  switch (map->value_type) {
+    case UPB_TYPE_MESSAGE:
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      MapIter it;
+      int len;
+      for (map_begin_internal(map, &it); !map_done(&it); map_next(&it)) {
+        upb_value value = map_iter_value(&it, &len);
+        void *mem = upb_value_memory(&value);
+        zval_ptr_dtor(mem);
+      }
+      break;
+    }
+    default:
+      break;
+  }
+
+  upb_strtable_uninit(&map->table);
+  zend_object_std_dtor(&map->std TSRMLS_CC);
+  efree(object);
+}
+
+void map_field_create_with_type(zend_class_entry *ce, const upb_fielddef *field,
+                                zval **map_field TSRMLS_DC) {
+  MAKE_STD_ZVAL(*map_field);
+  Z_TYPE_PP(map_field) = IS_OBJECT;
+  Z_OBJVAL_PP(map_field) =
+      map_field_type->create_object(map_field_type TSRMLS_CC);
+
+  Map* intern =
+      (Map*)zend_object_store_get_object(*map_field TSRMLS_CC);
+
+  const upb_fielddef *key_field = map_field_key(field);
+  const upb_fielddef *value_field = map_field_value(field);
+  intern->key_type = upb_fielddef_type(key_field);
+  intern->value_type = upb_fielddef_type(value_field);
+  intern->msg_ce = field_type_class(value_field);
+}
+
+static void map_field_free_element(void *object) {
+}
+
+// -----------------------------------------------------------------------------
+// MapField Handlers
+// -----------------------------------------------------------------------------
+
+static bool *map_field_read_dimension(zval *object, zval *key, int type,
+                                      zval **retval TSRMLS_DC) {
+  Map *intern =
+      (Map *)zend_object_store_get_object(object TSRMLS_CC);
+
+  char keybuf[TABLE_KEY_BUF_LENGTH];
+  const char* keyval = NULL;
+  size_t length = 0;
+  upb_value v;
+#ifndef NDEBUG
+  v.ctype = UPB_CTYPE_UINT64;
+#endif
+  if (!table_key(intern, key, keybuf, &keyval, &length)) {
+    return false;
+  }
+
+  if (upb_strtable_lookup2(&intern->table, keyval, length, &v)) {
+    void* mem = upb_value_memory(&v);
+    native_slot_get(intern->value_type, mem, retval TSRMLS_CC);
+    return true;
+  } else {
+    zend_error(E_USER_ERROR, "Given key doesn't exist.");
+    return false;
+  }
+}
+
+bool map_index_set(Map *intern, const char* keyval, int length, upb_value v) {
+  // Replace any existing value by issuing a 'remove' operation first.
+  upb_strtable_remove2(&intern->table, keyval, length, NULL);
+  if (!upb_strtable_insert2(&intern->table, keyval, length, v)) {
+    zend_error(E_USER_ERROR, "Could not insert into table");
+    return false;
+  }
+  return true;
+}
+
+static bool map_field_write_dimension(zval *object, zval *key,
+                                      zval *value TSRMLS_DC) {
+  Map *intern = (Map *)zend_object_store_get_object(object TSRMLS_CC);
+
+  char keybuf[TABLE_KEY_BUF_LENGTH];
+  const char* keyval = NULL;
+  size_t length = 0;
+  upb_value v;
+  void* mem;
+  if (!table_key(intern, key, keybuf, &keyval, &length)) {
+    return false;
+  }
+
+  mem = upb_value_memory(&v);
+  memset(mem, 0, native_slot_size(intern->value_type));
+  if(!native_slot_set(intern->value_type, intern->msg_ce, mem, value)) {
+    return false;
+  }
+#ifndef NDEBUG
+  v.ctype = UPB_CTYPE_UINT64;
+#endif
+
+  // Replace any existing value by issuing a 'remove' operation first.
+  upb_strtable_remove2(&intern->table, keyval, length, NULL);
+  if (!upb_strtable_insert2(&intern->table, keyval, length, v)) {
+    zend_error(E_USER_ERROR, "Could not insert into table");
+    return false;
+  }
+
+  return true;
+}
+
+static bool map_field_unset_dimension(zval *object, zval *key TSRMLS_DC) {
+  Map *intern = (Map *)zend_object_store_get_object(object TSRMLS_CC);
+
+  char keybuf[TABLE_KEY_BUF_LENGTH];
+  const char* keyval = NULL;
+  size_t length = 0;
+  upb_value v;
+  if (!table_key(intern, key, keybuf, &keyval, &length)) {
+    return false;
+  }
+#ifndef NDEBUG
+  v.ctype = UPB_CTYPE_UINT64;
+#endif
+
+  upb_strtable_remove2(&intern->table, keyval, length, &v);
+
+  return true;
+}
+
+// -----------------------------------------------------------------------------
+// PHP MapField Methods
+// -----------------------------------------------------------------------------
+
+PHP_METHOD(MapField, __construct) {
+  long key_type, value_type;
+  zend_class_entry* klass = NULL;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|C", &key_type,
+                            &value_type, &klass) == FAILURE) {
+    return;
+  }
+
+  Map* intern =
+      (Map*)zend_object_store_get_object(getThis() TSRMLS_CC);
+  intern->key_type = to_fieldtype(key_type);
+  intern->value_type = to_fieldtype(value_type);
+  intern->msg_ce = klass;
+
+  // Check that the key type is an allowed type.
+  switch (intern->key_type) {
+    case UPB_TYPE_INT32:
+    case UPB_TYPE_INT64:
+    case UPB_TYPE_UINT32:
+    case UPB_TYPE_UINT64:
+    case UPB_TYPE_BOOL:
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+      // These are OK.
+      break;
+    default:
+      zend_error(E_USER_ERROR, "Invalid key type for map.");
+  }
+}
+
+PHP_METHOD(MapField, offsetExists) {
+  zval *key;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &key) ==
+      FAILURE) {
+    return;
+  }
+
+  Map *intern = (Map *)zend_object_store_get_object(getThis() TSRMLS_CC);
+
+  char keybuf[TABLE_KEY_BUF_LENGTH];
+  const char* keyval = NULL;
+  size_t length = 0;
+  upb_value v;
+#ifndef NDEBUG
+  v.ctype = UPB_CTYPE_UINT64;
+#endif
+  if (!table_key(intern, key, keybuf, &keyval, &length)) {
+    return false;
+  }
+
+  RETURN_BOOL(upb_strtable_lookup2(&intern->table, keyval, length, &v));
+}
+
+PHP_METHOD(MapField, offsetGet) {
+  zval *index, *value;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &index) ==
+      FAILURE) {
+    return;
+  }
+  map_field_read_dimension(getThis(), index, BP_VAR_R,
+                           return_value_ptr TSRMLS_CC);
+}
+
+PHP_METHOD(MapField, offsetSet) {
+  zval *index, *value;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &index, &value) ==
+      FAILURE) {
+    return;
+  }
+  map_field_write_dimension(getThis(), index, value TSRMLS_CC);
+}
+
+PHP_METHOD(MapField, offsetUnset) {
+  zval *index;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &index) ==
+      FAILURE) {
+    return;
+  }
+  map_field_unset_dimension(getThis(), index TSRMLS_CC);
+}
+
+PHP_METHOD(MapField, count) {
+  Map *intern =
+      (MapField *)zend_object_store_get_object(getThis() TSRMLS_CC);
+
+  if (zend_parse_parameters_none() == FAILURE) {
+    return;
+  }
+
+  RETURN_LONG(upb_strtable_count(&intern->table));
+}
+
+// -----------------------------------------------------------------------------
+// Map Iterator
+// -----------------------------------------------------------------------------
+
+void map_begin(zval *map_php, MapIter *iter) {
+  Map *self = UNBOX(Map, map_php);
+  map_begin_internal(self, iter);
+}
+
+void map_next(MapIter *iter) {
+  upb_strtable_next(&iter->it);
+}
+
+bool map_done(MapIter *iter) {
+  return upb_strtable_done(&iter->it);
+}
+
+const char *map_iter_key(MapIter *iter, int *len) {
+  *len = upb_strtable_iter_keylength(&iter->it);
+  return upb_strtable_iter_key(&iter->it);
+}
+
+upb_value map_iter_value(MapIter *iter, int *len) {
+  *len = native_slot_size(iter->self->value_type);
+  return upb_strtable_iter_value(&iter->it);
+}

+ 150 - 176
php/ext/google/protobuf/message.c

@@ -29,245 +29,219 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <php.h>
+#include <stdlib.h>
 
 #include "protobuf.h"
 
-// -----------------------------------------------------------------------------
-// Class/module creation from msgdefs and enumdefs, respectively.
-// -----------------------------------------------------------------------------
-
-void* message_data(void* msg) {
-  return ((uint8_t *)msg) + sizeof(MessageHeader);
-}
-
-void message_set_property(zval* object, zval* field_name, zval* value,
-                          const zend_literal* key TSRMLS_DC) {
-  const upb_fielddef* field;
-
-  MessageHeader* self = zend_object_store_get_object(object TSRMLS_CC);
-
-  CHECK_TYPE(field_name, IS_STRING);
-  field = upb_msgdef_ntofz(self->descriptor->msgdef, Z_STRVAL_P(field_name));
-  if (field == NULL) {
-    zend_error(E_ERROR, "Unknown field: %s", Z_STRVAL_P(field_name));
-  }
-  layout_set(self->descriptor->layout, message_data(self), field, value);
-}
-
-zval* message_get_property(zval* object, zval* member, int type,
-                             const zend_literal* key TSRMLS_DC) {
-  MessageHeader* self =
-      (MessageHeader*)zend_object_store_get_object(object TSRMLS_CC);
-  CHECK_TYPE(member, IS_STRING);
-
-  const upb_fielddef* field;
-  field = upb_msgdef_ntofz(self->descriptor->msgdef, Z_STRVAL_P(member));
-  if (field == NULL) {
-    return EG(uninitialized_zval_ptr);
-  }
-  zval* retval = layout_get(self->descriptor->layout, message_data(self), field TSRMLS_CC);
-  return retval;
-}
+static zend_class_entry* message_type;
+zend_object_handlers* message_handlers;
 
 static  zend_function_entry message_methods[] = {
   PHP_ME(Message, encode, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Message, decode, NULL, ZEND_ACC_PUBLIC)
+  PHP_ME(Message, readOneof, NULL, ZEND_ACC_PROTECTED)
+  PHP_ME(Message, writeOneof, NULL, ZEND_ACC_PROTECTED)
   {NULL, NULL, NULL}
 };
 
-/* stringsink *****************************************************************/
+// Forward declare static functions.
 
-// This should probably be factored into a common upb component.
+static void message_set_property(zval* object, zval* member, zval* value,
+                                 const zend_literal* key TSRMLS_DC);
+static zval* message_get_property(zval* object, zval* member, int type,
+                                  const zend_literal* key TSRMLS_DC);
+static zval** message_get_property_ptr_ptr(zval* object, zval* member, int type,
+                                           const zend_literal* key TSRMLS_DC);
 
-typedef struct {
-  upb_byteshandler handler;
-  upb_bytessink sink;
-  char *ptr;
-  size_t len, size;
-} stringsink;
-
-static void *stringsink_start(void *_sink, const void *hd, size_t size_hint) {
-  stringsink *sink = _sink;
-  sink->len = 0;
-  return sink;
-}
+static zend_object_value message_create(zend_class_entry* ce TSRMLS_DC);
+static void message_free(void* object TSRMLS_DC);
 
-static size_t stringsink_string(void *_sink, const void *hd, const char *ptr,
-                                size_t len, const upb_bufhandle *handle) {
-  stringsink *sink = _sink;
-  size_t new_size = sink->size;
+// -----------------------------------------------------------------------------
+// PHP Message Handlers
+// -----------------------------------------------------------------------------
 
-  UPB_UNUSED(hd);
-  UPB_UNUSED(handle);
+void message_init(TSRMLS_D) {
+  zend_class_entry class_type;
+  INIT_CLASS_ENTRY(class_type, "Google\\Protobuf\\Internal\\Message",
+                   message_methods);
+  message_type = zend_register_internal_class(&class_type TSRMLS_CC);
+
+  message_handlers = PEMALLOC(zend_object_handlers);
+  memcpy(message_handlers, zend_get_std_object_handlers(),
+         sizeof(zend_object_handlers));
+  message_handlers->write_property = message_set_property;
+  message_handlers->read_property = message_get_property;
+  message_handlers->get_property_ptr_ptr = message_get_property_ptr_ptr;
+}
 
-  while (sink->len + len > new_size) {
-    new_size *= 2;
+static void message_set_property(zval* object, zval* member, zval* value,
+                                 const zend_literal* key TSRMLS_DC) {
+  if (Z_TYPE_P(member) != IS_STRING) {
+    zend_error(E_USER_ERROR, "Unexpected type for field name");
+    return;
   }
 
-  if (new_size != sink->size) {
-    sink->ptr = realloc(sink->ptr, new_size);
-    sink->size = new_size;
+  if (Z_OBJCE_P(object) != EG(scope)) {
+    // User cannot set property directly (e.g., $m->a = 1)
+    zend_error(E_USER_ERROR, "Cannot access private property.");
+    return;
   }
 
-  memcpy(sink->ptr + sink->len, ptr, len);
-  sink->len += len;
+  const upb_fielddef* field;
+
+  MessageHeader* self = zend_object_store_get_object(object TSRMLS_CC);
+
+  field = upb_msgdef_ntofz(self->descriptor->msgdef, Z_STRVAL_P(member));
+  if (field == NULL) {
+    zend_error(E_USER_ERROR, "Unknown field: %s", Z_STRVAL_P(member));
+  }
 
-  return len;
+  layout_set(self->descriptor->layout, self, field, value);
 }
 
-void stringsink_init(stringsink *sink) {
-  upb_byteshandler_init(&sink->handler);
-  upb_byteshandler_setstartstr(&sink->handler, stringsink_start, NULL);
-  upb_byteshandler_setstring(&sink->handler, stringsink_string, NULL);
+static zval* message_get_property(zval* object, zval* member, int type,
+                                  const zend_literal* key TSRMLS_DC) {
+  if (Z_TYPE_P(member) != IS_STRING) {
+    zend_error(E_USER_ERROR, "Property name has to be a string.");
+    return EG(uninitialized_zval_ptr);
+  }
 
-  upb_bytessink_reset(&sink->sink, &sink->handler, sink);
+  if (Z_OBJCE_P(object) != EG(scope)) {
+    // User cannot get property directly (e.g., $a = $m->a)
+    zend_error(E_USER_ERROR, "Cannot access private property.");
+    return EG(uninitialized_zval_ptr);
+  }
 
-  sink->size = 32;
-  sink->ptr = malloc(sink->size);
-  sink->len = 0;
-}
+  zend_property_info* property_info = NULL;
 
-void stringsink_uninit(stringsink *sink) { free(sink->ptr); }
-
-// Stack-allocated context during an encode/decode operation. Contains the upb
-// environment and its stack-based allocator, an initial buffer for allocations
-// to avoid malloc() when possible, and a template for PHP exception messages
-// if any error occurs.
-#define STACK_ENV_STACKBYTES 4096
-typedef struct {
-  upb_env env;
-  upb_seededalloc alloc;
-  const char *php_error_template;
-  char allocbuf[STACK_ENV_STACKBYTES];
-} stackenv;
-
-static void stackenv_init(stackenv* se, const char* errmsg);
-static void stackenv_uninit(stackenv* se);
-
-// Callback invoked by upb if any error occurs during parsing or serialization.
-static bool env_error_func(void* ud, const upb_status* status) {
-    stackenv* se = ud;
-    // Free the env -- rb_raise will longjmp up the stack past the encode/decode
-    // function so it would not otherwise have been freed.
-    stackenv_uninit(se);
-
-    // TODO(teboring): have a way to verify that this is actually a parse error,
-    // instead of just throwing "parse error" unconditionally.
-    zend_error(E_ERROR, se->php_error_template);
-    // Never reached.
-    return false;
-}
+  // All properties should have been declared in the generated code and have
+  // corresponding zvals in properties_table.
+  ulong h = zend_get_hash_value(Z_STRVAL_P(member), Z_STRLEN_P(member) + 1);
+  if (zend_hash_quick_find(&Z_OBJCE_P(object)->properties_info,
+                           Z_STRVAL_P(member), Z_STRLEN_P(member) + 1, h,
+                           (void**)&property_info) != SUCCESS) {
+    zend_error(E_USER_ERROR, "Property does not exist.");
+    return EG(uninitialized_zval_ptr);
+  }
+
+  MessageHeader* self =
+      (MessageHeader*)zend_object_store_get_object(object TSRMLS_CC);
 
-static void stackenv_init(stackenv* se, const char* errmsg) {
-  se->php_error_template = errmsg;
-  upb_env_init(&se->env);
-  upb_seededalloc_init(&se->alloc, &se->allocbuf, STACK_ENV_STACKBYTES);
-  upb_env_setallocfunc(&se->env, upb_seededalloc_getallocfunc(&se->alloc),
-                       &se->alloc);
-  upb_env_seterrorfunc(&se->env, env_error_func, se);
+  const upb_fielddef* field;
+  field = upb_msgdef_ntofz(self->descriptor->msgdef, Z_STRVAL_P(member));
+  if (field == NULL) {
+    return EG(uninitialized_zval_ptr);
+  }
+  return layout_get(
+      self->descriptor->layout, message_data(self), field,
+      &Z_OBJ_P(object)->properties_table[property_info->offset] TSRMLS_CC);
 }
 
-static void stackenv_uninit(stackenv* se) {
-  upb_env_uninit(&se->env);
-  upb_seededalloc_uninit(&se->alloc);
+static zval** message_get_property_ptr_ptr(zval* object, zval* member, int type,
+                                           const zend_literal* key TSRMLS_DC) {
+  return NULL;
 }
 
 // -----------------------------------------------------------------------------
-// Message
+// C Message Utilities
 // -----------------------------------------------------------------------------
 
-static const upb_handlers* msgdef_pb_serialize_handlers(Descriptor* desc) {
-  if (desc->pb_serialize_handlers == NULL) {
-    desc->pb_serialize_handlers =
-        upb_pb_encoder_newhandlers(desc->msgdef, &desc->pb_serialize_handlers);
-  }
-  return desc->pb_serialize_handlers;
+void* message_data(void* msg) {
+  return ((uint8_t*)msg) + sizeof(MessageHeader);
 }
 
-PHP_METHOD(Message, encode) {
-  Descriptor* desc = (Descriptor*)zend_object_store_get_object(
-      CE_STATIC_MEMBERS(Z_OBJCE_P(getThis()))[0] TSRMLS_CC);
-
-  stringsink sink;
-  stringsink_init(&sink);
-
-  {
-    const upb_handlers* serialize_handlers = msgdef_pb_serialize_handlers(desc);
+static void message_free(void* object TSRMLS_DC) {
+  MessageHeader* msg = (MessageHeader*)object;
+  int i;
 
-    stackenv se;
-    upb_pb_encoder* encoder;
-
-    stackenv_init(&se, "Error occurred during encoding: %s");
-    encoder = upb_pb_encoder_create(&se.env, serialize_handlers, &sink.sink);
-
-    putmsg(getThis(), desc, upb_pb_encoder_input(encoder), 0);
-
-    RETVAL_STRINGL(sink.ptr, sink.len, 1);
-
-    stackenv_uninit(&se);
-    stringsink_uninit(&sink);
+  for (i = 0; i < msg->std.ce->default_properties_count; i++) {
+    zval_ptr_dtor(&msg->std.properties_table[i]);
   }
+  efree(msg->std.properties_table);
+  efree(msg);
 }
 
-void message_free(void * object TSRMLS_DC) {
-  FREE(object);
-}
-
-zend_object_value message_create(zend_class_entry* ce TSRMLS_DC) {
+static zend_object_value message_create(zend_class_entry* ce TSRMLS_DC) {
   zend_object_value return_value;
 
-  zval* php_descriptor = get_def_obj(ce);
+  zval* php_descriptor = get_ce_obj(ce);
 
   Descriptor* desc = zend_object_store_get_object(php_descriptor TSRMLS_CC);
   MessageHeader* msg = (MessageHeader*)ALLOC_N(
       uint8_t, sizeof(MessageHeader) + desc->layout->size);
   memset(message_data(msg), 0, desc->layout->size);
 
-  // We wrap first so that everything in the message object is GC-rooted in case
-  // a collection happens during object creation in layout_init().
+  // We wrap first so that everything in the message object is GC-rooted in
+  // case a collection happens during object creation in layout_init().
   msg->descriptor = desc;
 
-  layout_init(desc->layout, message_data(msg));
   zend_object_std_init(&msg->std, ce TSRMLS_CC);
+  object_properties_init(&msg->std, ce);
+  layout_init(desc->layout, message_data(msg), msg->std.properties_table);
 
   return_value.handle = zend_objects_store_put(
-      msg, (zend_objects_store_dtor_t)zend_objects_destroy_object,
-      message_free, NULL TSRMLS_CC);
+      msg, (zend_objects_store_dtor_t)zend_objects_destroy_object, message_free,
+      NULL TSRMLS_CC);
 
-  return_value.handlers = PROTOBUF_G(message_handlers);
+  return_value.handlers = message_handlers;
   return return_value;
 }
 
-const zend_class_entry* build_class_from_descriptor(
-    zval* php_descriptor TSRMLS_DC) {
-  Descriptor* desc = php_to_descriptor(php_descriptor);
+void build_class_from_descriptor(zval* php_descriptor TSRMLS_DC) {
+  Descriptor* desc = UNBOX(Descriptor, php_descriptor);
+
+  // Map entries don't have existing php class.
+  if (upb_msgdef_mapentry(desc->msgdef)) {
+    return;
+  }
+
+  zend_class_entry* registered_ce = desc->klass;
+
   if (desc->layout == NULL) {
     MessageLayout* layout = create_layout(desc->msgdef);
     desc->layout = layout;
   }
-  // TODO(teboring): Add it back.
-  // if (desc->fill_method == NULL) {
-  //   desc->fill_method = new_fillmsg_decodermethod(desc, &desc->fill_method);
-  // }
-
-  const char* name = upb_msgdef_fullname(desc->msgdef);
-  if (name == NULL) {
-    zend_error(E_ERROR, "Descriptor does not have assigned name.");
+
+  registered_ce->create_object = message_create;
+}
+
+// -----------------------------------------------------------------------------
+// PHP Methods
+// -----------------------------------------------------------------------------
+
+PHP_METHOD(Message, readOneof) {
+  long index;
+
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
+      FAILURE) {
+    return;
   }
 
-  zend_class_entry class_entry;
-  INIT_CLASS_ENTRY_EX(class_entry, name, strlen(name), message_methods);
-  zend_class_entry* registered_ce =
-      zend_register_internal_class(&class_entry TSRMLS_CC);
+  MessageHeader* msg =
+      (MessageHeader*)zend_object_store_get_object(getThis() TSRMLS_CC);
 
-  add_def_obj(registered_ce, php_descriptor);
+  const upb_fielddef* field = upb_msgdef_itof(msg->descriptor->msgdef, index);
 
-  if (PROTOBUF_G(message_handlers) == NULL) {
-    PROTOBUF_G(message_handlers) = ALLOC(zend_object_handlers);
-    memcpy(PROTOBUF_G(message_handlers), zend_get_std_object_handlers(),
-           sizeof(zend_object_handlers));
-    PROTOBUF_G(message_handlers)->write_property = message_set_property;
-    PROTOBUF_G(message_handlers)->read_property = message_get_property;
+  int property_cache_index =
+      msg->descriptor->layout->fields[upb_fielddef_index(field)].cache_index;
+  zval** cache_ptr = &(msg->std.properties_table)[property_cache_index];
+
+  layout_get(msg->descriptor->layout, message_data(msg), field,
+             return_value_ptr TSRMLS_CC);
+}
+
+PHP_METHOD(Message, writeOneof) {
+  long index;
+  zval* value;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &index, &value) ==
+      FAILURE) {
+    return;
   }
 
-  registered_ce->create_object = message_create;
+  MessageHeader* msg =
+      (MessageHeader*)zend_object_store_get_object(getThis() TSRMLS_CC);
+
+  const upb_fielddef* field = upb_msgdef_itof(msg->descriptor->msgdef, index);
+
+  layout_set(msg->descriptor->layout, msg, field, value TSRMLS_CC);
 }

+ 74 - 0
php/ext/google/protobuf/package.xml

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.9.5" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
+ <name>protobuf</name>
+ <channel>pecl.php.net</channel>
+ <summary>Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data.</summary>
+ <description>https://developers.google.com/protocol-buffers/</description>
+ <lead>
+  <name>Bo Yang</name>
+  <user>teboring</user>
+  <email>protobuf-opensource@google.com</email>
+  <active>yes</active>
+ </lead>
+ <date>2016-09-02</date>
+ <time>16:06:07</time>
+ <version>
+  <release>3.1.0</release>
+  <api>3.1.0</api>
+ </version>
+ <stability>
+  <release>alpha</release>
+  <api>alpha</api>
+ </stability>
+ <license uri="http://www.opensource.org/licenses/bsd-license.php">New BSD License</license>
+ <notes>
+First alpha release.
+ </notes>
+ <contents>
+  <dir baseinstalldir="/" name="/">
+    <file baseinstalldir="/" name="config.m4" role="src" />
+    <file baseinstalldir="/" name="array.c" role="src" />
+    <file baseinstalldir="/" name="def.c" role="src" />
+    <file baseinstalldir="/" name="encode_decode.c" role="src" />
+    <file baseinstalldir="/" name="map.c" role="src" />
+    <file baseinstalldir="/" name="message.c" role="src" />
+    <file baseinstalldir="/" name="protobuf.c" role="src" />
+    <file baseinstalldir="/" name="protobuf.h" role="src" />
+    <file baseinstalldir="/" name="storage.c" role="src" />
+    <file baseinstalldir="/" name="type_check.c" role="src" />
+    <file baseinstalldir="/" name="upb.c" role="src" />
+    <file baseinstalldir="/" name="upb.h" role="src" />
+    <file baseinstalldir="/" name="utf8.c" role="src" />
+    <file baseinstalldir="/" name="utf8.h" role="src" />
+  </dir>
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.6.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.4.0</min>
+   </pearinstaller>
+  </required>
+ </dependencies>
+ <providesextension>protobuf</providesextension>
+ <extsrcrelease />
+ <changelog>
+  <release>
+   <version>
+    <release>3.1.0</release>
+    <api>3.1.0</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2016-09-02</date>
+   <license uri="http://www.opensource.org/licenses/bsd-license.php">New BSD License</license>
+   <notes>
+First alpha release
+   </notes>
+  </release>
+ </changelog>
+</package>

+ 114 - 36
php/ext/google/protobuf/protobuf.c

@@ -1,3 +1,33 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 #include "protobuf.h"
 
 #include <zend_hash.h>
@@ -5,56 +35,81 @@
 ZEND_DECLARE_MODULE_GLOBALS(protobuf)
 static PHP_GINIT_FUNCTION(protobuf);
 static PHP_GSHUTDOWN_FUNCTION(protobuf);
+static PHP_RINIT_FUNCTION(protobuf);
+static PHP_RSHUTDOWN_FUNCTION(protobuf);
+static PHP_MINIT_FUNCTION(protobuf);
+static PHP_MSHUTDOWN_FUNCTION(protobuf);
 
-// -----------------------------------------------------------------------------
 // Global map from upb {msg,enum}defs to wrapper Descriptor/EnumDescriptor
 // instances.
+static HashTable* upb_def_to_php_obj_map;
+// Global map from message/enum's php class entry to corresponding wrapper
+// Descriptor/EnumDescriptor instances.
+static HashTable* ce_to_php_obj_map;
+
+// -----------------------------------------------------------------------------
+// Global maps.
 // -----------------------------------------------------------------------------
 
-void add_def_obj(const void* def, zval* value) {
-  uint nIndex = (ulong)def & PROTOBUF_G(upb_def_to_php_obj_map).nTableMask;
+static void add_to_table(HashTable* t, const void* def, void* value) {
+  uint nIndex = (ulong)def & t->nTableMask;
 
   zval* pDest = NULL;
-  Z_ADDREF_P(value);
-  zend_hash_index_update(&PROTOBUF_G(upb_def_to_php_obj_map), (zend_ulong)def,
-                         &value, sizeof(zval*), &pDest);
+  zend_hash_index_update(t, (zend_ulong)def, &value, sizeof(zval*), &pDest);
 }
 
-zval* get_def_obj(const void* def) {
-  zval** value;
-  if (zend_hash_index_find(&PROTOBUF_G(upb_def_to_php_obj_map), (zend_ulong)def,
-                           &value) == FAILURE) {
+static void* get_from_table(const HashTable* t, const void* def) {
+  void** value;
+  if (zend_hash_index_find(t, (zend_ulong)def, (void**)&value) == FAILURE) {
     zend_error(E_ERROR, "PHP object not found for given definition.\n");
     return NULL;
   }
   return *value;
 }
 
+static void add_to_list(HashTable* t, void* value) {
+  zval* pDest = NULL;
+  zend_hash_next_index_insert(t, &value, sizeof(void*), &pDest);
+}
+
+void add_def_obj(const void* def, zval* value) {
+  Z_ADDREF_P(value);
+  add_to_table(upb_def_to_php_obj_map, def, value);
+}
+
+zval* get_def_obj(const void* def) {
+  return (zval*)get_from_table(upb_def_to_php_obj_map, def);
+}
+
+void add_ce_obj(const void* ce, zval* value) {
+  Z_ADDREF_P(value);
+  add_to_table(ce_to_php_obj_map, ce, value);
+}
+
+zval* get_ce_obj(const void* ce) {
+  return (zval*)get_from_table(ce_to_php_obj_map, ce);
+}
+
 // -----------------------------------------------------------------------------
 // Utilities.
 // -----------------------------------------------------------------------------
 
-// define the function(s) we want to add
 zend_function_entry protobuf_functions[] = {
-  ZEND_FE(get_generated_pool, NULL)
   ZEND_FE_END
 };
 
-// "protobuf_functions" refers to the struct defined above
-// we'll be filling in more of this later: you can use this to specify
-// globals, php.ini info, startup and teardown functions, etc.
 zend_module_entry protobuf_module_entry = {
   STANDARD_MODULE_HEADER,
-  PHP_PROTOBUF_EXTNAME, // extension name
-  protobuf_functions,   // function list
-  PHP_MINIT(protobuf),  // process startup
-  NULL,  // process shutdown
-  NULL,  // request startup
-  NULL,  // request shutdown
-  NULL,  // extension info
+  PHP_PROTOBUF_EXTNAME,     // extension name
+  protobuf_functions,       // function list
+  PHP_MINIT(protobuf),      // process startup
+  PHP_MSHUTDOWN(protobuf),  // process shutdown
+  PHP_RINIT(protobuf),      // request shutdown
+  PHP_RSHUTDOWN(protobuf),  // request shutdown
+  NULL,                 // extension info
   PHP_PROTOBUF_VERSION, // extension version
   PHP_MODULE_GLOBALS(protobuf),  // globals descriptor
-  PHP_GINIT(protobuf), // globals ctor
+  PHP_GINIT(protobuf),  // globals ctor
   PHP_GSHUTDOWN(protobuf),  // globals dtor
   NULL,  // post deactivate
   STANDARD_MODULE_PROPERTIES_EX
@@ -65,25 +120,48 @@ ZEND_GET_MODULE(protobuf)
 
 // global variables
 static PHP_GINIT_FUNCTION(protobuf) {
-  protobuf_globals->generated_pool = NULL;
-  generated_pool = NULL;
-  protobuf_globals->message_handlers = NULL;
-  zend_hash_init(&protobuf_globals->upb_def_to_php_obj_map, 16, NULL,
-                 ZVAL_PTR_DTOR, 0);
 }
 
 static PHP_GSHUTDOWN_FUNCTION(protobuf) {
-  if (protobuf_globals->generated_pool != NULL) {
-    FREE_ZVAL(protobuf_globals->generated_pool);
-  }
-  if (protobuf_globals->message_handlers != NULL) {
-    FREE(protobuf_globals->message_handlers);
+}
+
+static PHP_RINIT_FUNCTION(protobuf) {
+  ALLOC_HASHTABLE(upb_def_to_php_obj_map);
+  zend_hash_init(upb_def_to_php_obj_map, 16, NULL, ZVAL_PTR_DTOR, 0);
+
+  ALLOC_HASHTABLE(ce_to_php_obj_map);
+  zend_hash_init(ce_to_php_obj_map, 16, NULL, ZVAL_PTR_DTOR, 0);
+
+  generated_pool = NULL;
+  generated_pool_php = NULL;
+}
+
+static PHP_RSHUTDOWN_FUNCTION(protobuf) {
+  zend_hash_destroy(upb_def_to_php_obj_map);
+  FREE_HASHTABLE(upb_def_to_php_obj_map);
+
+  zend_hash_destroy(ce_to_php_obj_map);
+  FREE_HASHTABLE(ce_to_php_obj_map);
+
+  if (generated_pool_php != NULL) {
+    zval_dtor(generated_pool_php);
+    FREE_ZVAL(generated_pool_php);
   }
-  zend_hash_destroy(&protobuf_globals->upb_def_to_php_obj_map);
 }
 
-PHP_MINIT_FUNCTION(protobuf) {
+static PHP_MINIT_FUNCTION(protobuf) {
+  map_field_init(TSRMLS_C);
+  repeated_field_init(TSRMLS_C);
+  gpb_type_init(TSRMLS_C);
+  message_init(TSRMLS_C);
   descriptor_pool_init(TSRMLS_C);
   descriptor_init(TSRMLS_C);
-  message_builder_context_init(TSRMLS_C);
+  enum_descriptor_init(TSRMLS_C);
+  util_init(TSRMLS_C);
+}
+
+static PHP_MSHUTDOWN_FUNCTION(protobuf) {
+  PEFREE(message_handlers);
+  PEFREE(repeated_field_handlers);
+  PEFREE(map_field_handlers);
 }

+ 339 - 166
php/ext/google/protobuf/protobuf.h

@@ -1,46 +1,73 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 #ifndef __GOOGLE_PROTOBUF_PHP_PROTOBUF_H__
 #define __GOOGLE_PROTOBUF_PHP_PROTOBUF_H__
 
 #include <php.h>
 
+// ubp.h has to be placed after php.h. Othwise, php.h will introduce NDEBUG.
 #include "upb.h"
 
 #define PHP_PROTOBUF_EXTNAME "protobuf"
 #define PHP_PROTOBUF_VERSION "0.01"
 
-// Forward decls.
+// -----------------------------------------------------------------------------
+// Forward Declaration
+// ----------------------------------------------------------------------------
+
 struct DescriptorPool;
 struct Descriptor;
-struct FieldDescriptor;
 struct EnumDescriptor;
-struct MessageLayout;
+struct FieldDescriptor;
 struct MessageField;
 struct MessageHeader;
-struct MessageBuilderContext;
-struct EnumBuilderContext;
+struct MessageLayout;
+struct RepeatedField;
+struct MapField;
 
 typedef struct DescriptorPool DescriptorPool;
 typedef struct Descriptor Descriptor;
-typedef struct FieldDescriptor FieldDescriptor;
-typedef struct OneofDescriptor OneofDescriptor;
 typedef struct EnumDescriptor EnumDescriptor;
-typedef struct MessageLayout MessageLayout;
+typedef struct FieldDescriptor FieldDescriptor;
 typedef struct MessageField MessageField;
 typedef struct MessageHeader MessageHeader;
-typedef struct MessageBuilderContext MessageBuilderContext;
-typedef struct OneofBuilderContext OneofBuilderContext;
-typedef struct EnumBuilderContext EnumBuilderContext;
-
-extern zend_class_entry* builder_type;
-extern zend_class_entry* descriptor_type;
-extern zend_class_entry* message_builder_context_type;
+typedef struct MessageLayout MessageLayout;
+typedef struct RepeatedField RepeatedField;
+typedef struct MapField MapField;
 
-extern DescriptorPool* generated_pool;  // The actual generated pool
+// -----------------------------------------------------------------------------
+// Globals.
+// -----------------------------------------------------------------------------
 
 ZEND_BEGIN_MODULE_GLOBALS(protobuf)
-  zval* generated_pool;
-  zend_object_handlers* message_handlers;
-  HashTable upb_def_to_php_obj_map;
 ZEND_END_MODULE_GLOBALS(protobuf)
 
 ZEND_DECLARE_MODULE_GLOBALS(protobuf)
@@ -51,14 +78,31 @@ ZEND_DECLARE_MODULE_GLOBALS(protobuf)
 #define PROTOBUF_G(v) (protobuf_globals.v)
 #endif
 
-// -----------------------------------------------------------------------------
-// PHP functions and global variables.
-// -----------------------------------------------------------------------------
+// Init module and PHP classes.
+void descriptor_init(TSRMLS_D);
+void enum_descriptor_init(TSRMLS_D);
+void descriptor_pool_init(TSRMLS_D);
+void gpb_type_init(TSRMLS_D);
+void map_field_init(TSRMLS_D);
+void repeated_field_init(TSRMLS_D);
+void util_init(TSRMLS_D);
+void message_init(TSRMLS_D);
+
+// Global map from upb {msg,enum}defs to wrapper Descriptor/EnumDescriptor
+// instances.
+void add_def_obj(const void* def, zval* value);
+zval* get_def_obj(const void* def);
 
-PHP_MINIT_FUNCTION(protobuf);
+// Global map from PHP class entries to wrapper Descriptor/EnumDescriptor
+// instances.
+void add_ce_obj(const void* ce, zval* value);
+zval* get_ce_obj(const void* ce);
+
+extern zend_class_entry* map_field_type;
+extern zend_class_entry* repeated_field_type;
 
 // -----------------------------------------------------------------------------
-// PHP class structure.
+// Descriptor.
 // -----------------------------------------------------------------------------
 
 struct DescriptorPool {
@@ -67,72 +111,112 @@ struct DescriptorPool {
   HashTable* pending_list;
 };
 
+PHP_METHOD(DescriptorPool, getGeneratedPool);
+PHP_METHOD(DescriptorPool, internalAddGeneratedFile);
+
+extern zval* generated_pool_php;  // wrapper of generated pool
+extern DescriptorPool* generated_pool;  // The actual generated pool
+
 struct Descriptor {
   zend_object std;
   const upb_msgdef* msgdef;
   MessageLayout* layout;
-  // zval* klass;  // begins as NULL
-  // const upb_handlers* fill_handlers;
-  // const upb_pbdecodermethod* fill_method;
+  zend_class_entry* klass;  // begins as NULL
+  const upb_handlers* fill_handlers;
+  const upb_pbdecodermethod* fill_method;
   const upb_handlers* pb_serialize_handlers;
-  // const upb_handlers* json_serialize_handlers;
-  // Handlers hold type class references for sub-message fields directly in some
-  // cases. We need to keep these rooted because they might otherwise be
-  // collected.
-  // zval_array typeclass_references;
 };
 
+extern zend_class_entry* descriptor_type;
+
+void descriptor_name_set(Descriptor *desc, const char *name);
+
 struct FieldDescriptor {
   zend_object std;
   const upb_fielddef* fielddef;
 };
 
-struct OneofDescriptor {
-  zend_object std;
-  const upb_oneofdef* oneofdef;
-};
-
 struct EnumDescriptor {
   zend_object std;
   const upb_enumdef* enumdef;
-  // zval* module;  // begins as NULL
+  zend_class_entry* klass;  // begins as NULL
+  // VALUE module;  // begins as nil
 };
 
+extern zend_class_entry* enum_descriptor_type;
+
 // -----------------------------------------------------------------------------
-// Native slot storage abstraction.
+// Message class creation.
 // -----------------------------------------------------------------------------
 
-#define NATIVE_SLOT_MAX_SIZE sizeof(uint64_t)
-
-size_t native_slot_size(upb_fieldtype_t type);
+void* message_data(void* msg);
 
-#define MAP_KEY_FIELD 1
-#define MAP_VALUE_FIELD 2
+// Build PHP class for given descriptor. Instead of building from scratch, this
+// function modifies existing class which has been partially defined in PHP
+// code.
+void build_class_from_descriptor(zval* php_descriptor TSRMLS_DC);
 
-// Oneof case slot value to indicate that no oneof case is set. The value `0` is
-// safe because field numbers are used as case identifiers, and no field can
-// have a number of 0.
-#define ONEOF_CASE_NONE 0
-
-// These operate on a map field (i.e., a repeated field of submessages whose
-// submessage type is a map-entry msgdef).
-bool is_map_field(const upb_fielddef* field);
-const upb_fielddef* map_field_key(const upb_fielddef* field);
-const upb_fielddef* map_field_value(const upb_fielddef* field);
-
-// These operate on a map-entry msgdef.
-const upb_fielddef* map_entry_key(const upb_msgdef* msgdef);
-const upb_fielddef* map_entry_value(const upb_msgdef* msgdef);
+extern zend_object_handlers* message_handlers;
 
 // -----------------------------------------------------------------------------
 // Message layout / storage.
 // -----------------------------------------------------------------------------
 
+/*
+ * In c extension, each protobuf message is a zval instance. The zval instance
+ * is like union, which can be used to store int, string, zend_object_value and
+ * etc. For protobuf message, the zval instance is used to store the
+ * zend_object_value.
+ *
+ * The zend_object_value is composed of handlers and a handle to look up the
+ * actual stored data. The handlers are pointers to functions, e.g., read,
+ * write, and etc, to access properties.
+ *
+ * The actual data of protobuf messages is stored as MessageHeader in zend
+ * engine's central repository. Each MessageHeader instance is composed of a
+ * zend_object, a Descriptor instance and the real message data.
+ *
+ * For the reason that PHP's native types may not be large enough to store
+ * protobuf message's field (e.g., int64), all message's data is stored in
+ * custom memory layout and is indexed by the Descriptor instance.
+ *
+ * The zend_object contains the zend class entry and the properties table. The
+ * zend class entry contains all information about protobuf message's
+ * corresponding PHP class. The most useful information is the offset table of
+ * properties. Because read access to properties requires returning zval
+ * instance, we need to convert data from the custom layout to zval instance.
+ * Instead of creating zval instance for every read access, we use the zval
+ * instances in the properties table in the zend_object as cache.  When
+ * accessing properties, the offset is needed to find the zval property in
+ * zend_object's properties table. These properties will be updated using the
+ * data from custom memory layout only when reading these properties.
+ *
+ * zval
+ * |-zend_object_value obj
+ *   |-zend_object_handlers* handlers -> |-read_property_handler
+ *   |                                   |-write_property_handler
+ *   |                              ++++++++++++++++++++++
+ *   |-zend_object_handle handle -> + central repository +
+ *                                  ++++++++++++++++++++++
+ *  MessageHeader <-----------------|
+ *  |-zend_object std
+ *  | |-class_entry* ce -> class_entry
+ *  | |                    |-HashTable properties_table (name->offset)
+ *  | |-zval** properties_table <------------------------------|
+ *  |                         |------> zval* property(cache)
+ *  |-Descriptor* desc (name->offset)
+ *  |-void** data <-----------|
+ *           |-----------------------> void* property(data)
+ *
+ */
+
 #define MESSAGE_FIELD_NO_CASE ((size_t)-1)
 
 struct MessageField {
   size_t offset;
-  size_t case_offset;  // for oneofs, a uint32. Else, MESSAGE_FIELD_NO_CASE.
+  int cache_index;  // Each field except oneof field has a zval cache to avoid
+                    // multiple creation when being accessed.
+  size_t case_offset;   // for oneofs, a uint32. Else, MESSAGE_FIELD_NO_CASE.
 };
 
 struct MessageLayout {
@@ -141,141 +225,230 @@ struct MessageLayout {
   size_t size;
 };
 
-void layout_init(MessageLayout* layout, void* storage);
-zval* layout_get(MessageLayout* layout, const void* storage,
-                 const upb_fielddef* field TSRMLS_DC);
+struct MessageHeader {
+  zend_object std;  // Stores properties table and class info of PHP instance.
+                    // This is needed for MessageHeader to be accessed via PHP.
+  Descriptor* descriptor;  // Kept alive by self.class.descriptor reference.
+  // The real message data is appended after MessageHeader.
+};
+
 MessageLayout* create_layout(const upb_msgdef* msgdef);
+void layout_init(MessageLayout* layout, void* storage, zval** properties_table);
+zval* layout_get(MessageLayout* layout, const void* storage,
+                 const upb_fielddef* field, zval** cache TSRMLS_DC);
+void layout_set(MessageLayout* layout, MessageHeader* header,
+                const upb_fielddef* field, zval* val);
 void free_layout(MessageLayout* layout);
-zval* native_slot_get(upb_fieldtype_t type, /*VALUE type_class,*/
-                      const void* memory TSRMLS_DC);
+
+PHP_METHOD(Message, readOneof);
+PHP_METHOD(Message, writeOneof);
 
 // -----------------------------------------------------------------------------
-// Message class creation.
+// Encode / Decode.
 // -----------------------------------------------------------------------------
 
-struct MessageHeader {
-  zend_object std;
-  Descriptor* descriptor;  // kept alive by self.class.descriptor reference.
-                           // Data comes after this.
-};
+// Maximum depth allowed during encoding, to avoid stack overflows due to
+// cycles.
+#define ENCODE_MAX_NESTING 63
 
-struct MessageBuilderContext {
-  zend_object std;
-  zval* descriptor;
-  zval* pool;
-};
+// Constructs the upb decoder method for parsing messages of this type.
+// This is called from the message class creation code.
+const upb_pbdecodermethod *new_fillmsg_decodermethod(Descriptor *desc,
+                                                     const void *owner);
+
+PHP_METHOD(Message, encode);
+PHP_METHOD(Message, decode);
+
+// -----------------------------------------------------------------------------
+// Type check / conversion.
+// -----------------------------------------------------------------------------
+
+bool protobuf_convert_to_int32(zval* from, int32_t* to);
+bool protobuf_convert_to_uint32(zval* from, uint32_t* to);
+bool protobuf_convert_to_int64(zval* from, int64_t* to);
+bool protobuf_convert_to_uint64(zval* from, uint64_t* to);
+bool protobuf_convert_to_float(zval* from, float* to);
+bool protobuf_convert_to_double(zval* from, double* to);
+bool protobuf_convert_to_bool(zval* from, int8_t* to);
+bool protobuf_convert_to_string(zval* from);
+
+PHP_METHOD(Util, checkInt32);
+PHP_METHOD(Util, checkUint32);
+PHP_METHOD(Util, checkInt64);
+PHP_METHOD(Util, checkUint64);
+PHP_METHOD(Util, checkEnum);
+PHP_METHOD(Util, checkFloat);
+PHP_METHOD(Util, checkDouble);
+PHP_METHOD(Util, checkBool);
+PHP_METHOD(Util, checkString);
+PHP_METHOD(Util, checkBytes);
+PHP_METHOD(Util, checkMessage);
+PHP_METHOD(Util, checkRepeatedField);
+
+// -----------------------------------------------------------------------------
+// Native slot storage abstraction.
+// -----------------------------------------------------------------------------
+
+#define NATIVE_SLOT_MAX_SIZE sizeof(uint64_t)
+
+size_t native_slot_size(upb_fieldtype_t type);
+bool native_slot_set(upb_fieldtype_t type, const zend_class_entry* klass,
+                     void* memory, zval* value);
+void native_slot_init(upb_fieldtype_t type, void* memory, zval** cache);
+// For each property, in order to avoid conversion between the zval object and
+// the actual data type during parsing/serialization, the containing message
+// object use the custom memory layout to store the actual data type for each
+// property inside of it.  To access a property from php code, the property
+// needs to be converted to a zval object. The message object is not responsible
+// for providing such a zval object. Instead the caller needs to provide one
+// (cache) and update it with the actual data (memory).
+void native_slot_get(upb_fieldtype_t type, const void* memory,
+                     zval** cache TSRMLS_DC);
+void native_slot_get_default(upb_fieldtype_t type, zval** cache TSRMLS_DC);
+
+// -----------------------------------------------------------------------------
+// Map Field.
+// -----------------------------------------------------------------------------
 
-struct OneofBuilderContext {
+extern zend_object_handlers* map_field_handlers;
+
+typedef struct {
   zend_object std;
-  // VALUE descriptor;
-  // VALUE builder;
-};
+  upb_fieldtype_t key_type;
+  upb_fieldtype_t value_type;
+  const zend_class_entry* msg_ce;  // class entry for value message
+  upb_strtable table;
+} Map;
+
+typedef struct {
+  Map* self;
+  upb_strtable_iter it;
+} MapIter;
+
+void map_begin(zval* self, MapIter* iter);
+void map_next(MapIter* iter);
+bool map_done(MapIter* iter);
+const char* map_iter_key(MapIter* iter, int* len);
+upb_value map_iter_value(MapIter* iter, int* len);
+
+// These operate on a map-entry msgdef.
+const upb_fielddef* map_entry_key(const upb_msgdef* msgdef);
+const upb_fielddef* map_entry_value(const upb_msgdef* msgdef);
 
-struct EnumBuilderContext {
+zend_object_value map_field_create(zend_class_entry *ce TSRMLS_DC);
+void map_field_create_with_type(zend_class_entry *ce, const upb_fielddef *field,
+                                zval **map_field TSRMLS_DC);
+void map_field_free(void* object TSRMLS_DC);
+void* upb_value_memory(upb_value* v);
+
+#define MAP_KEY_FIELD 1
+#define MAP_VALUE_FIELD 2
+
+// These operate on a map field (i.e., a repeated field of submessages whose
+// submessage type is a map-entry msgdef).
+const upb_fielddef* map_field_key(const upb_fielddef* field);
+const upb_fielddef* map_field_value(const upb_fielddef* field);
+
+bool map_index_set(Map *intern, const char* keyval, int length, upb_value v);
+
+PHP_METHOD(MapField, __construct);
+PHP_METHOD(MapField, offsetExists);
+PHP_METHOD(MapField, offsetGet);
+PHP_METHOD(MapField, offsetSet);
+PHP_METHOD(MapField, offsetUnset);
+PHP_METHOD(MapField, count);
+
+// -----------------------------------------------------------------------------
+// Repeated Field.
+// -----------------------------------------------------------------------------
+
+extern zend_object_handlers* repeated_field_handlers;
+
+struct RepeatedField {
   zend_object std;
-  // VALUE enumdesc;
+  zval* array;
+  upb_fieldtype_t type;
+  const zend_class_entry* msg_ce;  // class entry for containing message
+                                   // (for message field only).
 };
 
-// Forward-declare all of the PHP method implementations.
-
-DescriptorPool* php_to_descriptor_pool(zval* value TSRMLS_DC);
-zend_object_value descriptor_pool_create(zend_class_entry *ce TSRMLS_DC);
-void descriptor_pool_free_c(DescriptorPool* object TSRMLS_DC);
-void descriptor_pool_free(void* object TSRMLS_DC);
-void descriptor_pool_init_c_instance(DescriptorPool* pool TSRMLS_DC);
-PHP_METHOD(DescriptorPool, addMessage);
-PHP_METHOD(DescriptorPool, finalize);
-
-Descriptor* php_to_descriptor(zval* value TSRMLS_DC);
-zend_object_value descriptor_create(zend_class_entry *ce TSRMLS_DC);
-void descriptor_init_c_instance(Descriptor* intern TSRMLS_DC);
-void descriptor_free_c(Descriptor* object TSRMLS_DC);
-void descriptor_free(void* object TSRMLS_DC);
-void descriptor_name_set(Descriptor *desc, const char *name);
+void repeated_field_create_with_type(zend_class_entry* ce,
+                                     const upb_fielddef* field,
+                                     zval** repeated_field TSRMLS_DC);
+// Return the element at the index position from the repeated field. There is
+// not restriction on the type of stored elements.
+void *repeated_field_index_native(RepeatedField *intern, int index);
+// Add the element to the end of the repeated field. There is not restriction on
+// the type of stored elements.
+void repeated_field_push_native(RepeatedField *intern, void *value);
+
+PHP_METHOD(RepeatedField, __construct);
+PHP_METHOD(RepeatedField, append);
+PHP_METHOD(RepeatedField, offsetExists);
+PHP_METHOD(RepeatedField, offsetGet);
+PHP_METHOD(RepeatedField, offsetSet);
+PHP_METHOD(RepeatedField, offsetUnset);
+PHP_METHOD(RepeatedField, count);
 
-MessageBuilderContext* php_to_message_builder_context(zval* value TSRMLS_DC);
-zend_object_value message_builder_context_create(
-    zend_class_entry* ce TSRMLS_DC);
-void message_builder_context_init_c_instance(
-    MessageBuilderContext* intern TSRMLS_DC);
-void message_builder_context_free_c(MessageBuilderContext* object TSRMLS_DC);
-void message_builder_context_free(void* object TSRMLS_DC);
-PHP_METHOD(MessageBuilderContext, optional);
-PHP_METHOD(MessageBuilderContext, finalizeToPool);
+// -----------------------------------------------------------------------------
+// Oneof Field.
+// -----------------------------------------------------------------------------
 
-PHP_METHOD(Message, encode);
-const zend_class_entry* build_class_from_descriptor(
-    zval* php_descriptor TSRMLS_DC);
+typedef struct {
+  zend_object std;
+  upb_oneofdef* oneofdef;
+  int index;    // Index of field in oneof. -1 if not set.
+  char value[NATIVE_SLOT_MAX_SIZE];
+} Oneof;
 
-PHP_FUNCTION(get_generated_pool);
+// Oneof case slot value to indicate that no oneof case is set. The value `0` is
+// safe because field numbers are used as case identifiers, and no field can
+// have a number of 0.
+#define ONEOF_CASE_NONE 0
 
 // -----------------------------------------------------------------------------
-// Global map from upb {msg,enum}defs to wrapper Descriptor/EnumDescriptor
-// instances.
-// ----------------------------------------------------------------------------
+// Upb.
+// -----------------------------------------------------------------------------
 
-void add_def_obj(const void* def, zval* value);
-zval* get_def_obj(const void* def);
+upb_fieldtype_t to_fieldtype(upb_descriptortype_t type);
+const zend_class_entry *field_type_class(const upb_fielddef *field);
 
 // -----------------------------------------------------------------------------
 // Utilities.
 // -----------------------------------------------------------------------------
 
-// PHP Array utils.
-#define Z_ARRVAL_SIZE_P(zval_p) zend_hash_num_elements(Z_ARRVAL_P(zval_p))
-#define Z_ARRVAL_BEGIN_P(zval_p) Z_ARRVAL_P(zval_p)->pListHead
-#define Z_BUCKET_NEXT_PP(bucket_pp) *bucket_pp = (*bucket_pp)->pListNext
-
-#define DEFINE_PHP_OBJECT(class_name, class_name_lower, name) \
-  do {                                                        \
-    zval* name;                                               \
-    MAKE_STD_ZVAL(name);                                      \
-    object_init_ex(name, class_name_lower##_type);            \
-  } while (0)
-
-#define DEFINE_PHP_WRAPPER(class_name, class_name_lower, name, intern) \
-  zval* name;                                                          \
-  MAKE_STD_ZVAL(name);                                                 \
-  object_init_ex(name, class_name_lower##_type);                       \
-  Z_OBJVAL_P(name)                                                     \
-      .handle = zend_objects_store_put(                                \
-      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,  \
-      class_name_lower##_free, NULL TSRMLS_CC);
-
-#define DEFINE_PHP_ZVAL(name) \
-  do {                        \
-    zval* name;               \
-    MAKE_STD_ZVAL(name);      \
-  } while (0)
-
-#define DEFINE_PHP_STRING(name, value) \
-  do {                                 \
-    zval* name;                        \
-    MAKE_STD_ZVAL(name);               \
-    ZVAL_STRING(name, value, 1);       \
-  } while (0)
-
-// Upb Utilities
-
-void check_upb_status(const upb_status* status, const char* msg);
-
-#define CHECK_UPB(code, msg)             \
-  do {                                   \
-    upb_status status = UPB_STATUS_INIT; \
-    code;                                \
-    check_upb_status(&status, msg);      \
-  } while (0)
+// PHP <-> C conversion.
+#define UNBOX(class_name, val) \
+  (class_name*)zend_object_store_get_object(val TSRMLS_CC);
 
-// Memory management
+#define BOX(class_name, wrapper, intern, free_func)                    \
+  MAKE_STD_ZVAL(wrapper);                                              \
+  Z_TYPE_P(wrapper) = IS_OBJECT;                                       \
+  Z_OBJVAL_P(wrapper)                                                  \
+      .handle =                                                        \
+      zend_objects_store_put(intern, NULL, free_func, NULL TSRMLS_CC); \
+  Z_OBJVAL_P(wrapper).handlers = zend_get_std_object_handlers();
 
+// Memory management
 #define ALLOC(class_name) (class_name*) emalloc(sizeof(class_name))
+#define PEMALLOC(class_name) (class_name*) pemalloc(sizeof(class_name), 1)
 #define ALLOC_N(class_name, n) (class_name*) emalloc(sizeof(class_name) * n)
 #define FREE(object) efree(object)
-
-// Type Checking
-#define CHECK_TYPE(field, type)             \
-  if (Z_TYPE_P(field) != type) {            \
-    zend_error(E_ERROR, "Unexpected type"); \
-  }
+#define PEFREE(object) pefree(object, 1)
+
+// Create PHP internal instance.
+#define CREATE(class_name, intern, init_func) \
+  intern = ALLOC(class_name);                 \
+  memset(intern, 0, sizeof(class_name));      \
+  init_func(intern TSRMLS_CC);
+
+// String argument.
+#define STR(str) (str), strlen(str)
+
+// Zend Value
+#define Z_OBJ_P(zval_p)                                       \
+  ((zend_object*)(EG(objects_store)                           \
+                      .object_buckets[Z_OBJ_HANDLE_P(zval_p)] \
+                      .bucket.obj.object))
 
 #endif  // __GOOGLE_PROTOBUF_PHP_PROTOBUF_H__

+ 332 - 312
php/ext/google/protobuf/storage.c

@@ -1,19 +1,41 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 #include <stdint.h>
 #include <protobuf.h>
+#include <Zend/zend.h>
 
 // -----------------------------------------------------------------------------
-// PHP <-> native slot management.
+// Native slot storage.
 // -----------------------------------------------------------------------------
 
-static zval* int32_to_zval(int32_t value) {
-  zval* tmp;
-  MAKE_STD_ZVAL(tmp);
-  ZVAL_LONG(tmp, value);
-  php_printf("int32 to zval\n");
-  // ZVAL_LONG(tmp, 1);
-  return tmp;
-}
-
 #define DEREF(memory, type) *(type*)(memory)
 
 size_t native_slot_size(upb_fieldtype_t type) {
@@ -21,9 +43,9 @@ size_t native_slot_size(upb_fieldtype_t type) {
     case UPB_TYPE_FLOAT: return 4;
     case UPB_TYPE_DOUBLE: return 8;
     case UPB_TYPE_BOOL: return 1;
-    case UPB_TYPE_STRING: return sizeof(zval*);
-    case UPB_TYPE_BYTES: return sizeof(zval*);
-    case UPB_TYPE_MESSAGE: return sizeof(zval*);
+    case UPB_TYPE_STRING: return sizeof(void*);
+    case UPB_TYPE_BYTES: return sizeof(void*);
+    case UPB_TYPE_MESSAGE: return sizeof(void*);
     case UPB_TYPE_ENUM: return 4;
     case UPB_TYPE_INT32: return 4;
     case UPB_TYPE_INT64: return 8;
@@ -33,72 +55,77 @@ size_t native_slot_size(upb_fieldtype_t type) {
   }
 }
 
-static bool is_php_num(zval* value) {
-  // Is numerial string also valid?
-  return (Z_TYPE_P(value) == IS_LONG ||
-          Z_TYPE_P(value) == IS_DOUBLE);
-}
+bool native_slot_set(upb_fieldtype_t type, const zend_class_entry* klass,
+                     void* memory, zval* value) {
+  switch (type) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      if (!protobuf_convert_to_string(value)) {
+        return false;
+      }
+      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 false;
+      }
+      if (*(zval**)memory != NULL) {
+        REPLACE_ZVAL_VALUE((zval**)memory, value, 1);
+      } else {
+        // Handles repeated/map string field. Memory provided by
+        // RepeatedField/Map is not initialized.
+        MAKE_STD_ZVAL(DEREF(memory, zval*));
+        ZVAL_STRINGL(DEREF(memory, zval*), Z_STRVAL_P(value), Z_STRLEN_P(value),
+                     1);
+      }
+      break;
+    }
+    case UPB_TYPE_MESSAGE: {
+      if (Z_TYPE_P(value) != IS_OBJECT && Z_TYPE_P(value) != IS_NULL) {
+        zend_error(E_USER_ERROR, "Given value is not message.");
+        return false;
+      }
+      if (Z_TYPE_P(value) == IS_OBJECT && klass != Z_OBJCE_P(value)) {
+        zend_error(E_USER_ERROR, "Given message does not have correct class.");
+        return false;
+      }
+      if (EXPECTED(DEREF(memory, zval*) != value)) {
+        if (DEREF(memory, zval*) != NULL) {
+          zval_ptr_dtor((zval**)memory);
+        }
+        DEREF(memory, zval*) = value;
+        Z_ADDREF_P(value);
+      }
+      break;
+    }
 
-void native_slot_check_int_range_precision(upb_fieldtype_t type, zval* val) {
-  // TODO(teboring): Add it back.
-  // if (!is_php_num(val)) {
-  //   zend_error(E_ERROR, "Expected number type for integral field.");
-  // }
-
-  // if (Z_TYPE_P(val) == IS_DOUBLE) {
-  //   double dbl_val = NUM2DBL(val);
-  //   if (floor(dbl_val) != dbl_val) {
-  //     zend_error(E_ERROR,
-  //              "Non-integral floating point value assigned to integer field.");
-  //   }
-  // }
-  // if (type == UPB_TYPE_UINT32 || type == UPB_TYPE_UINT64) {
-  //   if (NUM2DBL(val) < 0) {
-  //     zend_error(E_ERROR,
-  //              "Assigning negative value to unsigned integer field.");
-  //   }
-  // }
-}
+#define CASE_TYPE(upb_type, type, c_type, php_type)              \
+  case UPB_TYPE_##upb_type: {                                    \
+    c_type type##_value;                                         \
+    if (protobuf_convert_to_##type(value, &type##_value)) {      \
+      DEREF(memory, c_type) = type##_value;                      \
+    }                                                            \
+    break;                                                       \
+  }
+      CASE_TYPE(INT32,  int32,  int32_t,  LONG)
+      CASE_TYPE(UINT32, uint32, uint32_t, LONG)
+      CASE_TYPE(ENUM,   int32,  int32_t,  LONG)
+      CASE_TYPE(INT64,  int64,  int64_t,  LONG)
+      CASE_TYPE(UINT64, uint64, uint64_t, LONG)
+      CASE_TYPE(FLOAT,  float,  float,    DOUBLE)
+      CASE_TYPE(DOUBLE, double, double,   DOUBLE)
+      CASE_TYPE(BOOL,   bool,   int8_t,   BOOL)
+
+#undef CASE_TYPE
 
-zval* native_slot_get(upb_fieldtype_t type, /*VALUE type_class,*/
-                      const void* memory TSRMLS_DC) {
-  zval* retval = NULL;
-  switch (type) {
-    // TODO(teboring): Add it back.
-    // case UPB_TYPE_FLOAT:
-    //   return DBL2NUM(DEREF(memory, float));
-    // case UPB_TYPE_DOUBLE:
-    //   return DBL2NUM(DEREF(memory, double));
-    // case UPB_TYPE_BOOL:
-    //   return DEREF(memory, int8_t) ? Qtrue : Qfalse;
-    // case UPB_TYPE_STRING:
-    // case UPB_TYPE_BYTES:
-    // case UPB_TYPE_MESSAGE:
-    //   return DEREF(memory, VALUE);
-    // case UPB_TYPE_ENUM: {
-    //   int32_t val = DEREF(memory, int32_t);
-    //   VALUE symbol = enum_lookup(type_class, INT2NUM(val));
-    //   if (symbol == Qnil) {
-    //     return INT2NUM(val);
-    //   } else {
-    //     return symbol;
-    //   }
-    // }
-    case UPB_TYPE_INT32:
-      return int32_to_zval(DEREF(memory, int32_t));
-    // TODO(teboring): Add it back.
-    // case UPB_TYPE_INT64:
-    //   return LL2NUM(DEREF(memory, int64_t));
-    // case UPB_TYPE_UINT32:
-    //   return UINT2NUM(DEREF(memory, uint32_t));
-    // case UPB_TYPE_UINT64:
-    //   return ULL2NUM(DEREF(memory, uint64_t));
     default:
-      return EG(uninitialized_zval_ptr);
+      break;
   }
+
+  return true;
 }
 
-void native_slot_init(upb_fieldtype_t type, void* memory) {
+void native_slot_init(upb_fieldtype_t type, void* memory, zval** cache) {
+  zval* tmp = NULL;
   switch (type) {
     case UPB_TYPE_FLOAT:
       DEREF(memory, float) = 0.0;
@@ -109,17 +136,11 @@ void native_slot_init(upb_fieldtype_t type, void* memory) {
     case UPB_TYPE_BOOL:
       DEREF(memory, int8_t) = 0;
       break;
-    // TODO(teboring): Add it back.
-    // case UPB_TYPE_STRING:
-    // case UPB_TYPE_BYTES:
-    //   DEREF(memory, VALUE) = php_str_new2("");
-    //   php_enc_associate(DEREF(memory, VALUE), (type == UPB_TYPE_BYTES)
-    //                                              ? kRubyString8bitEncoding
-    //                                              : kRubyStringUtf8Encoding);
-    //   break;
-    // case UPB_TYPE_MESSAGE:
-    //   DEREF(memory, VALUE) = Qnil;
-    //   break;
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+    case UPB_TYPE_MESSAGE:
+      DEREF(memory, zval**) = cache;
+      break;
     case UPB_TYPE_ENUM:
     case UPB_TYPE_INT32:
       DEREF(memory, int32_t) = 0;
@@ -138,122 +159,93 @@ void native_slot_init(upb_fieldtype_t type, void* memory) {
   }
 }
 
-void native_slot_set(upb_fieldtype_t type, /*VALUE type_class,*/ void* memory,
-                     zval* value) {
-  native_slot_set_value_and_case(type, /*type_class,*/ memory, value, NULL, 0);
-}
-
-void native_slot_set_value_and_case(upb_fieldtype_t type, /*VALUE type_class,*/
-                                    void* memory, zval* value,
-                                    uint32_t* case_memory,
-                                    uint32_t case_number) {
+void native_slot_get(upb_fieldtype_t type, const void* memory,
+                     zval** cache TSRMLS_DC) {
   switch (type) {
-    case UPB_TYPE_FLOAT:
-      if (!Z_TYPE_P(value) == IS_LONG) {
-        zend_error(E_ERROR, "Expected number type for float field.");
+#define CASE(upb_type, php_type, c_type) \
+    case UPB_TYPE_##upb_type: \
+      SEPARATE_ZVAL_IF_NOT_REF(cache); \
+      ZVAL_##php_type(*cache, DEREF(memory, c_type)); \
+      return;
+
+CASE(FLOAT,  DOUBLE, float)
+CASE(DOUBLE, DOUBLE, double)
+CASE(BOOL,   BOOL,   int8_t)
+CASE(INT32,  LONG,   int32_t)
+CASE(INT64,  LONG,   int64_t)
+CASE(UINT64, LONG,   uint64_t)
+CASE(ENUM,   LONG,   uint32_t)
+
+#undef CASE
+    case UPB_TYPE_UINT32: {
+      // Prepend bit-1 for negative numbers, so that uint32 value will be
+      // consistent on both 32-bit and 64-bit architectures.
+      SEPARATE_ZVAL_IF_NOT_REF(cache);
+      int value = DEREF(memory, int32_t);
+      if (sizeof(int) == 8) {
+        value |= (-((value >> 31) & 0x1) & 0xFFFFFFFF00000000);
       }
-      DEREF(memory, float) = Z_DVAL_P(value);
-      break;
-    case UPB_TYPE_DOUBLE:
-      // TODO(teboring): Add it back.
-      // if (!is_php_num(value)) {
-      //   zend_error(E_ERROR, "Expected number type for double field.");
-      // }
-      // DEREF(memory, double) = Z_DVAL_P(value);
-      break;
-    case UPB_TYPE_BOOL: {
-      int8_t val = -1;
-      if (zval_is_true(value)) {
-        val = 1;
-      } else {
-        val = 0;
-      }
-      // TODO(teboring): Add it back.
-      // else if (value == Qfalse) {
-      //   val = 0;
-      // }
-      // else {
-      //   php_raise(php_eTypeError, "Invalid argument for boolean field.");
-      // }
-      DEREF(memory, int8_t) = val;
-      break;
+      ZVAL_LONG(*cache, value);
+      return;
     }
+
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
-      // TODO(teboring): Add it back.
-      // if (Z_TYPE_P(value) != IS_STRING) {
-      //   zend_error(E_ERROR, "Invalid argument for string field.");
-      // }
-      // native_slot_validate_string_encoding(type, value);
-      // DEREF(memory, zval*) = value;
+      // For optional string/bytes fields, the cache is owned by the containing
+      // message and should have been updated during setting/decoding. However,
+      // for repeated string/bytes fields, the cache is provided by zend engine
+      // and has not been updated.
+      zval* value = DEREF(memory, zval*);
+      if (*cache != value) {
+        ZVAL_STRINGL(*cache, Z_STRVAL_P(value), Z_STRLEN_P(value), 1);
+      }
       break;
     }
     case UPB_TYPE_MESSAGE: {
-      // TODO(teboring): Add it back.
-      // if (CLASS_OF(value) == CLASS_OF(Qnil)) {
-      //   value = Qnil;
-      // } else if (CLASS_OF(value) != type_class) {
-      //   php_raise(php_eTypeError,
-      //            "Invalid type %s to assign to submessage field.",
-      //            php_class2name(CLASS_OF(value)));
-      // }
-      // DEREF(memory, VALUE) = value;
-      break;
-    }
-    case UPB_TYPE_ENUM: {
-      // TODO(teboring): Add it back.
-      // int32_t int_val = 0;
-      // if (!is_php_num(value) && TYPE(value) != T_SYMBOL) {
-      //   php_raise(php_eTypeError,
-      //            "Expected number or symbol type for enum field.");
-      // }
-      // if (TYPE(value) == T_SYMBOL) {
-      //   // Ensure that the given symbol exists in the enum module.
-      //   VALUE lookup = php_funcall(type_class, php_intern("resolve"), 1, value);
-      //   if (lookup == Qnil) {
-      //     php_raise(php_eRangeError, "Unknown symbol value for enum field.");
-      //   } else {
-      //     int_val = NUM2INT(lookup);
-      //   }
-      // } else {
-      //   native_slot_check_int_range_precision(UPB_TYPE_INT32, value);
-      //   int_val = NUM2INT(value);
-      // }
-      // DEREF(memory, int32_t) = int_val;
-      // break;
-    }
-    case UPB_TYPE_INT32:
-    case UPB_TYPE_INT64:
-    case UPB_TYPE_UINT32:
-    case UPB_TYPE_UINT64:
-      native_slot_check_int_range_precision(type, value);
-      switch (type) {
-        case UPB_TYPE_INT32:
-          php_printf("Setting INT32 field\n");
-          DEREF(memory, int32_t) = Z_LVAL_P(value);
-          break;
-        case UPB_TYPE_INT64:
-          // TODO(teboring): Add it back.
-          // DEREF(memory, int64_t) = NUM2LL(value);
-          break;
-        case UPB_TYPE_UINT32:
-          // TODO(teboring): Add it back.
-          // DEREF(memory, uint32_t) = NUM2UINT(value);
-          break;
-        case UPB_TYPE_UINT64:
-          // TODO(teboring): Add it back.
-          // DEREF(memory, uint64_t) = NUM2ULL(value);
-          break;
-        default:
-          break;
+      // Same as above for string/bytes fields.
+      zval* value = DEREF(memory, zval*);
+      if (*cache != value) {
+        ZVAL_ZVAL(*cache, value, 1, 0);
       }
-      break;
+      return;
+    }
     default:
-      break;
+      return EG(uninitialized_zval_ptr);
   }
+}
 
-  if (case_memory != NULL) {
-    *case_memory = case_number;
+void native_slot_get_default(upb_fieldtype_t type, zval** cache TSRMLS_DC) {
+  switch (type) {
+#define CASE(upb_type, php_type)     \
+  case UPB_TYPE_##upb_type:          \
+    SEPARATE_ZVAL_IF_NOT_REF(cache); \
+    ZVAL_##php_type(*cache, 0);      \
+    return;
+
+    CASE(FLOAT, DOUBLE)
+    CASE(DOUBLE, DOUBLE)
+    CASE(BOOL, BOOL)
+    CASE(INT32, LONG)
+    CASE(INT64, LONG)
+    CASE(UINT32, LONG)
+    CASE(UINT64, LONG)
+    CASE(ENUM, LONG)
+
+#undef CASE
+
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      SEPARATE_ZVAL_IF_NOT_REF(cache);
+      ZVAL_STRINGL(*cache, "", 0, 1);
+      break;
+    }
+    case UPB_TYPE_MESSAGE: {
+      SEPARATE_ZVAL_IF_NOT_REF(cache);
+      ZVAL_NULL(*cache);
+      return;
+    }
+    default:
+      return EG(uninitialized_zval_ptr);
   }
 }
 
@@ -281,6 +273,40 @@ bool is_map_field(const upb_fielddef* field) {
   return tryget_map_entry_msgdef(field) != NULL;
 }
 
+const upb_fielddef* map_field_key(const upb_fielddef* field) {
+  const upb_msgdef* subdef = map_entry_msgdef(field);
+  return map_entry_key(subdef);
+}
+
+const upb_fielddef* map_field_value(const upb_fielddef* field) {
+  const upb_msgdef* subdef = map_entry_msgdef(field);
+  return map_entry_value(subdef);
+}
+
+const upb_fielddef* map_entry_key(const upb_msgdef* msgdef) {
+  const upb_fielddef* key_field = upb_msgdef_itof(msgdef, MAP_KEY_FIELD);
+  assert(key_field != NULL);
+  return key_field;
+}
+
+const upb_fielddef* map_entry_value(const upb_msgdef* msgdef) {
+  const upb_fielddef* value_field = upb_msgdef_itof(msgdef, MAP_VALUE_FIELD);
+  assert(value_field != NULL);
+  return value_field;
+}
+
+const zend_class_entry* field_type_class(const upb_fielddef* field) {
+  if (upb_fielddef_type(field) == UPB_TYPE_MESSAGE) {
+    zval* desc_php = get_def_obj(upb_fielddef_subdef(field));
+    Descriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+    return desc->klass;
+  } else if (upb_fielddef_type(field) == UPB_TYPE_ENUM) {
+    zval* desc_php = get_def_obj(upb_fielddef_subdef(field));
+    EnumDescriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+    return desc->klass;
+  }
+}
+
 // -----------------------------------------------------------------------------
 // Memory layout management.
 // -----------------------------------------------------------------------------
@@ -290,12 +316,29 @@ static size_t align_up_to(size_t offset, size_t granularity) {
   return (offset + granularity - 1) & ~(granularity - 1);
 }
 
+static void* slot_memory(MessageLayout* layout, const void* storage,
+                         const upb_fielddef* field) {
+  return ((uint8_t*)storage) + layout->fields[upb_fielddef_index(field)].offset;
+}
+
+static uint32_t* slot_oneof_case(MessageLayout* layout, const void* storage,
+                                 const upb_fielddef* field) {
+  return (uint32_t*)(((uint8_t*)storage) +
+                     layout->fields[upb_fielddef_index(field)].case_offset);
+}
+
+static int slot_property_cache(MessageLayout* layout, const void* storage,
+                               const upb_fielddef* field) {
+  return layout->fields[upb_fielddef_index(field)].cache_index;
+}
+
 MessageLayout* create_layout(const upb_msgdef* msgdef) {
   MessageLayout* layout = ALLOC(MessageLayout);
   int nfields = upb_msgdef_numfields(msgdef);
   upb_msg_field_iter it;
   upb_msg_oneof_iter oit;
   size_t off = 0;
+  int i = 0;
 
   layout->fields = ALLOC_N(MessageField, nfields);
 
@@ -322,6 +365,7 @@ MessageLayout* create_layout(const upb_msgdef* msgdef) {
     layout->fields[upb_fielddef_index(field)].offset = off;
     layout->fields[upb_fielddef_index(field)].case_offset =
         MESSAGE_FIELD_NO_CASE;
+    layout->fields[upb_fielddef_index(field)].cache_index = i++;
     off += field_size;
   }
 
@@ -353,11 +397,13 @@ MessageLayout* create_layout(const upb_msgdef* msgdef) {
          upb_oneof_next(&fit)) {
       const upb_fielddef* field = upb_oneof_iter_field(&fit);
       layout->fields[upb_fielddef_index(field)].offset = off;
+      layout->fields[upb_fielddef_index(field)].cache_index = i;
     }
+    i++;
     off += field_size;
   }
 
-  // Now the case fields.
+  // Now the case offset.
   for (upb_msg_oneof_begin(&oit, msgdef); !upb_msg_oneof_done(&oit);
        upb_msg_oneof_next(&oit)) {
     const upb_oneofdef* oneof = upb_msg_iter_oneof(&oit);
@@ -389,151 +435,125 @@ void free_layout(MessageLayout* layout) {
   FREE(layout);
 }
 
-// TODO(teboring): Add it back.
-// VALUE field_type_class(const upb_fielddef* field) {
-//   VALUE type_class = Qnil;
-//   if (upb_fielddef_type(field) == UPB_TYPE_MESSAGE) {
-//     VALUE submsgdesc = get_def_obj(upb_fielddef_subdef(field));
-//     type_class = Descriptor_msgclass(submsgdesc);
-//   } else if (upb_fielddef_type(field) == UPB_TYPE_ENUM) {
-//     VALUE subenumdesc = get_def_obj(upb_fielddef_subdef(field));
-//     type_class = EnumDescriptor_enummodule(subenumdesc);
-//   }
-//   return type_class;
-// }
+void layout_init(MessageLayout* layout, void* storage, zval** properties_table) {
+  int i;
+  upb_msg_field_iter it;
+  for (upb_msg_field_begin(&it, layout->msgdef), i = 0; !upb_msg_field_done(&it);
+       upb_msg_field_next(&it), i++) {
+    const upb_fielddef* field = upb_msg_iter_field(&it);
+    void* memory = slot_memory(layout, storage, field);
+    uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
+    int cache_index = slot_property_cache(layout, storage, field);
+    zval** property_ptr = &properties_table[cache_index];
 
-static void* slot_memory(MessageLayout* layout, const void* storage,
-                         const upb_fielddef* field) {
-  return ((uint8_t*)storage) + layout->fields[upb_fielddef_index(field)].offset;
+    if (upb_fielddef_containingoneof(field)) {
+      memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
+      *oneof_case = ONEOF_CASE_NONE;
+    } else if (is_map_field(field)) {
+      zval_ptr_dtor(property_ptr);
+      map_field_create_with_type(map_field_type, field, property_ptr);
+      DEREF(memory, zval**) = property_ptr;
+    } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
+      zval_ptr_dtor(property_ptr);
+      repeated_field_create_with_type(repeated_field_type, field, property_ptr);
+      DEREF(memory, zval**) = property_ptr;
+    } else {
+      native_slot_init(upb_fielddef_type(field), memory, property_ptr);
+    }
+  }
 }
 
-static uint32_t* slot_oneof_case(MessageLayout* layout, const void* storage,
-                                 const upb_fielddef* field) {
-  return (uint32_t*)(((uint8_t*)storage) +
-                     layout->fields[upb_fielddef_index(field)].case_offset);
+// For non-singular fields, the related memory needs to point to the actual
+// zval in properties table first.
+static void* value_memory(const upb_fielddef* field, void* memory) {
+  switch (upb_fielddef_type(field)) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+    case UPB_TYPE_MESSAGE:
+      memory = DEREF(memory, zval**);
+      break;
+    default:
+      // No operation
+      break;
+  }
+  return memory;
 }
 
-void layout_set(MessageLayout* layout, void* storage, const upb_fielddef* field,
-                zval* val) {
+zval* layout_get(MessageLayout* layout, const void* storage,
+                 const upb_fielddef* field, zval** cache TSRMLS_DC) {
   void* memory = slot_memory(layout, storage, field);
   uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
 
   if (upb_fielddef_containingoneof(field)) {
-    if (Z_TYPE_P(val) == IS_NULL) {
-      // Assigning nil to a oneof field clears the oneof completely.
-      *oneof_case = ONEOF_CASE_NONE;
-      memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
+    if (*oneof_case != upb_fielddef_number(field)) {
+      native_slot_get_default(upb_fielddef_type(field), cache TSRMLS_CC);
     } else {
-      // The transition between field types for a single oneof (union) slot is
-      // somewhat complex because we need to ensure that a GC triggered at any
-      // point by a call into the Ruby VM sees a valid state for this field and
-      // does not either go off into the weeds (following what it thinks is a
-      // VALUE but is actually a different field type) or miss an object (seeing
-      // what it thinks is a primitive field but is actually a VALUE for the new
-      // field type).
-      //
-      // In order for the transition to be safe, the oneof case slot must be in
-      // sync with the value slot whenever the Ruby VM has been called. Thus, we
-      // use native_slot_set_value_and_case(), which ensures that both the value
-      // and case number are altered atomically (w.r.t. the Ruby VM).
-      native_slot_set_value_and_case(upb_fielddef_type(field),
-                                     /*field_type_class(field),*/ memory, val,
-                                     oneof_case, upb_fielddef_number(field));
+      native_slot_get(upb_fielddef_type(field), value_memory(field, memory),
+                      cache TSRMLS_CC);
     }
-  } else if (is_map_field(field)) {
-    // TODO(teboring): Add it back.
-    // check_map_field_type(val, field);
-    // DEREF(memory, zval*) = val;
+    return *cache;
   } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
-    // TODO(teboring): Add it back.
-    // check_repeated_field_type(val, field);
-    // DEREF(memory, zval*) = val;
+    return *cache;
   } else {
-    native_slot_set(upb_fielddef_type(field), /*field_type_class(field),*/ memory,
-                    val);
-  }
-}
-
-void layout_init(MessageLayout* layout, void* storage) {
-  upb_msg_field_iter it;
-  for (upb_msg_field_begin(&it, layout->msgdef); !upb_msg_field_done(&it);
-       upb_msg_field_next(&it)) {
-    const upb_fielddef* field = upb_msg_iter_field(&it);
-    void* memory = slot_memory(layout, storage, field);
-    uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
-
-    if (upb_fielddef_containingoneof(field)) {
-      // TODO(teboring): Add it back.
-      // memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
-      // *oneof_case = ONEOF_CASE_NONE;
-    } else if (is_map_field(field)) {
-      // TODO(teboring): Add it back.
-      // VALUE map = Qnil;
-
-      // const upb_fielddef* key_field = map_field_key(field);
-      // const upb_fielddef* value_field = map_field_value(field);
-      // VALUE type_class = field_type_class(value_field);
-
-      // if (type_class != Qnil) {
-      //   VALUE args[3] = {
-      //       fieldtype_to_php(upb_fielddef_type(key_field)),
-      //       fieldtype_to_php(upb_fielddef_type(value_field)), type_class,
-      //   };
-      //   map = php_class_new_instance(3, args, cMap);
-      // } else {
-      //   VALUE args[2] = {
-      //       fieldtype_to_php(upb_fielddef_type(key_field)),
-      //       fieldtype_to_php(upb_fielddef_type(value_field)),
-      //   };
-      //   map = php_class_new_instance(2, args, cMap);
-      // }
-
-      // DEREF(memory, VALUE) = map;
-    } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
-      // TODO(teboring): Add it back.
-      // VALUE ary = Qnil;
-
-      // VALUE type_class = field_type_class(field);
-
-      // if (type_class != Qnil) {
-      //   VALUE args[2] = {
-      //       fieldtype_to_php(upb_fielddef_type(field)), type_class,
-      //   };
-      //   ary = php_class_new_instance(2, args, cRepeatedField);
-      // } else {
-      //   VALUE args[1] = {fieldtype_to_php(upb_fielddef_type(field))};
-      //   ary = php_class_new_instance(1, args, cRepeatedField);
-      // }
-
-      // DEREF(memory, VALUE) = ary;
-    } else {
-      native_slot_init(upb_fielddef_type(field), memory);
-    }
+    native_slot_get(upb_fielddef_type(field), value_memory(field, memory),
+                    cache TSRMLS_CC);
+    return *cache;
   }
 }
 
-zval* layout_get(MessageLayout* layout, const void* storage,
-                 const upb_fielddef* field TSRMLS_DC) {
+void layout_set(MessageLayout* layout, MessageHeader* header, const upb_fielddef* field,
+                zval* val) {
+  void* storage = message_data(header);
   void* memory = slot_memory(layout, storage, field);
   uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
 
   if (upb_fielddef_containingoneof(field)) {
-    if (*oneof_case != upb_fielddef_number(field)) {
-      return NULL;
-      // TODO(teboring): Add it back.
-      // return Qnil;
+    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: {
+        upb_msgdef* msg = upb_fielddef_msgsubdef(field);
+        zval* desc_php = get_def_obj(msg);
+        Descriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+        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, zval**) =
+            &(header->std.properties_table)[property_cache_index];
+        memory = DEREF(memory, zval**);
+        break;
+      }
+      default:
+        break;
     }
-      return NULL;
-    // TODO(teboring): Add it back.
-    // return native_slot_get(upb_fielddef_type(field), field_type_class(field),
-    //                        memory);
+
+    native_slot_set(type, ce, memory, val);
+    *oneof_case = upb_fielddef_number(field);
   } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
-      return NULL;
-    // TODO(teboring): Add it back.
-    // return *((VALUE*)memory);
+    // Works for both repeated and map fields
+    memory = DEREF(memory, zval**);
+    if (EXPECTED(DEREF(memory, zval*) != val)) {
+        zval_ptr_dtor(memory);
+        DEREF(memory, zval*) = val;
+        Z_ADDREF_P(val);
+    }
   } else {
-    return native_slot_get(
-        upb_fielddef_type(field), /*field_type_class(field), */
-        memory TSRMLS_CC);
+    upb_fieldtype_t type = upb_fielddef_type(field);
+    zend_class_entry *ce = NULL;
+    if (type == UPB_TYPE_MESSAGE) {
+      upb_msgdef* msg = upb_fielddef_msgsubdef(field);
+      zval* desc_php = get_def_obj(msg);
+      Descriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+      ce = desc->klass;
+    }
+    native_slot_set(type, ce, value_memory(field, memory), val);
   }
 }

+ 0 - 15
php/ext/google/protobuf/test.php

@@ -1,15 +0,0 @@
-<?php
-
-
-namespace Google\Protobuf;
-
-$pool = get_generated_pool();
-$pool->addMessage("TestMessage")
-    ->optional("optional_int32_a", "int32", 1)
-    ->optional("optional_int32_b", "int32", 2)
-    ->finalizeToPool()
-    ->finalize();
-
-$test_message = new \TestMessage();
-
-?>

+ 310 - 0
php/ext/google/protobuf/type_check.c

@@ -0,0 +1,310 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <Zend/zend_operators.h>
+
+#include "protobuf.h"
+#include "utf8.h"
+
+static zend_class_entry* util_type;
+
+ZEND_BEGIN_ARG_INFO_EX(arg_check_optional, 0, 0, 1)
+  ZEND_ARG_INFO(1, val)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arg_check_message, 0, 0, 2)
+  ZEND_ARG_INFO(1, val)
+  ZEND_ARG_INFO(0, klass)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arg_check_repeated, 0, 0, 2)
+  ZEND_ARG_INFO(1, val)
+  ZEND_ARG_INFO(0, type)
+  ZEND_ARG_INFO(0, klass)
+ZEND_END_ARG_INFO()
+
+static zend_function_entry util_methods[] = {
+  PHP_ME(Util, checkInt32,  arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkUint32, arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkInt64,  arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkUint64, arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkEnum,   arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkFloat,  arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkDouble, arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkBool,   arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkString, arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkBytes,  arg_check_optional, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkMessage, arg_check_message, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  PHP_ME(Util, checkRepeatedField, arg_check_repeated,
+         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+  ZEND_FE_END
+};
+
+void util_init(TSRMLS_D) {
+  zend_class_entry class_type;
+  INIT_CLASS_ENTRY(class_type, "Google\\Protobuf\\Internal\\GPBUtil",
+                   util_methods);
+  util_type = zend_register_internal_class(&class_type TSRMLS_CC);
+}
+
+// -----------------------------------------------------------------------------
+// Type checking/conversion.
+// -----------------------------------------------------------------------------
+
+#define CONVERT_TO_INTEGER(type)                                             \
+  static bool convert_long_to_##type(long val, type##_t* type##_value) {     \
+    *type##_value = (type##_t)val;                                           \
+    return true;                                                             \
+  }                                                                          \
+                                                                             \
+  static bool convert_double_to_##type(double val, type##_t* type##_value) { \
+    *type##_value = (type##_t)zend_dval_to_lval(val);                        \
+    return true;                                                             \
+  }                                                                          \
+                                                                             \
+  static bool convert_string_to_##type(const char* val, int len,             \
+                                       type##_t* type##_value) {             \
+    long lval;                                                               \
+    double dval;                                                             \
+                                                                             \
+    switch (is_numeric_string(val, len, &lval, &dval, 0)) {                  \
+      case IS_DOUBLE: {                                                      \
+        return convert_double_to_##type(dval, type##_value);                 \
+      }                                                                      \
+      case IS_LONG: {                                                        \
+        return convert_long_to_##type(lval, type##_value);                   \
+      }                                                                      \
+      default:                                                               \
+        zend_error(E_USER_ERROR,                                             \
+                   "Given string value cannot be converted to integer.");    \
+        return false;                                                        \
+    }                                                                        \
+  }                                                                          \
+                                                                             \
+  bool protobuf_convert_to_##type(zval* from, type##_t* to) {                \
+    switch (Z_TYPE_P(from)) {                                                \
+      case IS_LONG: {                                                        \
+        return convert_long_to_##type(Z_LVAL_P(from), to);                   \
+      }                                                                      \
+      case IS_DOUBLE: {                                                      \
+        return convert_double_to_##type(Z_DVAL_P(from), to);                 \
+      }                                                                      \
+      case IS_STRING: {                                                      \
+        return convert_string_to_##type(Z_STRVAL_P(from), Z_STRLEN_P(from),  \
+                                        to);                                 \
+      }                                                                      \
+      default: {                                                             \
+        zend_error(E_USER_ERROR,                                             \
+                   "Given value cannot be converted to integer.");           \
+        return false;                                                        \
+      }                                                                      \
+    }                                                                        \
+    return false;                                                            \
+  }
+
+CONVERT_TO_INTEGER(int32);
+CONVERT_TO_INTEGER(uint32);
+CONVERT_TO_INTEGER(int64);
+CONVERT_TO_INTEGER(uint64);
+
+#undef CONVERT_TO_INTEGER
+
+#define CONVERT_TO_FLOAT(type)                                              \
+  static bool convert_long_to_##type(long val, type* type##_value) {        \
+    *type##_value = (type)val;                                              \
+    return true;                                                            \
+  }                                                                         \
+                                                                            \
+  static bool convert_double_to_##type(double val, type* type##_value) {    \
+    *type##_value = (type)val;                                              \
+    return true;                                                            \
+  }                                                                         \
+                                                                            \
+  static bool convert_string_to_##type(const char* val, int len,            \
+                                       type* type##_value) {                \
+    long lval;                                                              \
+    double dval;                                                            \
+                                                                            \
+    switch (is_numeric_string(val, len, &lval, &dval, 0)) {                 \
+      case IS_DOUBLE: {                                                     \
+        *type##_value = (type)dval;                                         \
+        return true;                                                        \
+      }                                                                     \
+      case IS_LONG: {                                                       \
+        *type##_value = (type)lval;                                         \
+        return true;                                                        \
+      }                                                                     \
+      default:                                                              \
+        zend_error(E_USER_ERROR,                                            \
+                   "Given string value cannot be converted to integer.");   \
+        return false;                                                       \
+    }                                                                       \
+  }                                                                         \
+                                                                            \
+  bool protobuf_convert_to_##type(zval* from, type* to) {                   \
+    switch (Z_TYPE_P(from)) {                                               \
+      case IS_LONG: {                                                       \
+        return convert_long_to_##type(Z_LVAL_P(from), to);                  \
+      }                                                                     \
+      case IS_DOUBLE: {                                                     \
+        return convert_double_to_##type(Z_DVAL_P(from), to);                \
+      }                                                                     \
+      case IS_STRING: {                                                     \
+        return convert_string_to_##type(Z_STRVAL_P(from), Z_STRLEN_P(from), \
+                                        to);                                \
+      }                                                                     \
+      default: {                                                            \
+        zend_error(E_USER_ERROR,                                            \
+                   "Given value cannot be converted to integer.");          \
+        return false;                                                       \
+      }                                                                     \
+    }                                                                       \
+    return false;                                                           \
+  }
+
+CONVERT_TO_FLOAT(float);
+CONVERT_TO_FLOAT(double);
+
+#undef CONVERT_TO_FLOAT
+
+bool protobuf_convert_to_bool(zval* from, int8_t* to) {
+  switch (Z_TYPE_P(from)) {
+    case IS_BOOL:
+      *to = (int8_t)Z_BVAL_P(from);
+      break;
+    case IS_LONG:
+      *to = (int8_t)(Z_LVAL_P(from) != 0);
+      break;
+    case IS_DOUBLE:
+      *to = (int8_t)(Z_LVAL_P(from) != 0);
+      break;
+    case IS_STRING: {
+      char* strval = Z_STRVAL_P(from);
+
+      if (Z_STRLEN_P(from) == 0 ||
+          (Z_STRLEN_P(from) == 1 && Z_STRVAL_P(from)[0] == '0')) {
+        *to = 0;
+      } else {
+        *to = 1;
+      }
+      STR_FREE(strval);
+    } break;
+    default: {
+      zend_error(E_USER_ERROR, "Given value cannot be converted to bool.");
+      return false;
+    }
+  }
+  return true;
+}
+
+bool protobuf_convert_to_string(zval* from) {
+  switch (Z_TYPE_P(from)) {
+    case IS_STRING: {
+      return true;
+    }
+    case IS_BOOL:
+    case IS_LONG:
+    case IS_DOUBLE: {
+      int use_copy;
+      zval tmp;
+      zend_make_printable_zval(from, &tmp, &use_copy);
+      ZVAL_COPY_VALUE(from, &tmp);
+      return true;
+    }
+    default:
+      zend_error(E_USER_ERROR, "Given value cannot be converted to string.");
+      return false;
+  }
+}
+
+// -----------------------------------------------------------------------------
+// PHP Functions.
+// -----------------------------------------------------------------------------
+
+// The implementation of type checking for primitive fields is empty. This is
+// because type checking is done when direct assigning message fields (e.g.,
+// foo->a = 1). Functions defined here are place holders in generated code for
+// pure PHP implementation (c extension and pure PHP share the same generated
+// code).
+#define PHP_TYPE_CHECK(type) \
+  PHP_METHOD(Util, check##type) {}
+
+PHP_TYPE_CHECK(Int32)
+PHP_TYPE_CHECK(Uint32)
+PHP_TYPE_CHECK(Int64)
+PHP_TYPE_CHECK(Uint64)
+PHP_TYPE_CHECK(Enum)
+PHP_TYPE_CHECK(Float)
+PHP_TYPE_CHECK(Double)
+PHP_TYPE_CHECK(Bool)
+PHP_TYPE_CHECK(String)
+PHP_TYPE_CHECK(Bytes)
+
+#undef PHP_TYPE_CHECK
+
+PHP_METHOD(Util, checkMessage) {
+  zval* val;
+  zend_class_entry* klass = NULL;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!C", &val, &klass) ==
+      FAILURE) {
+    return;
+  }
+  if (val == NULL) {
+    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);
+    return;
+  }
+  RETURN_ZVAL(val, 1, 0);
+}
+
+PHP_METHOD(Util, checkRepeatedField) {
+  zval* val;
+  long type;
+  const zend_class_entry* klass = NULL;
+  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ol|C", &val,
+                            repeated_field_type, &type, &klass) == FAILURE) {
+    return;
+  }
+
+  RepeatedField *intern =
+      (RepeatedField *)zend_object_store_get_object(val TSRMLS_CC);
+  if (to_fieldtype(type) != intern->type) {
+    zend_error(E_USER_ERROR, "Incorrect repeated field type.");
+    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);
+    return;
+  }
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 365 - 123
php/ext/google/protobuf/upb.c


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 612 - 207
php/ext/google/protobuf/upb.h


+ 68 - 0
php/ext/google/protobuf/utf8.c

@@ -0,0 +1,68 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "utf8.h"
+
+static const uint8_t utf8_offset[] = {
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+    2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+    4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+bool is_structurally_valid_utf8(const char* buf, int len) {
+  int i, j;
+  uint8_t offset;
+
+  i = 0;
+  while (i < len) {
+    offset = utf8_offset[(uint8_t)buf[i]];
+    if (offset == 0 || i + offset > len) {
+      return false;
+    }
+    for (j = i + 1; j < i + offset; j++) {
+      if (buf[j] & 0xc0 != 0x80) {
+        return false;
+      }
+    }
+    i += offset;
+  }
+  return i == len;
+}

+ 36 - 0
php/ext/google/protobuf/utf8.h

@@ -0,0 +1,36 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef GOOGLE_PROTOBUF_UTF8_H_
+#define GOOGLE_PROTOBUF_UTF8_H_
+
+bool is_structurally_valid_utf8(const char* buf, int len);
+
+#endif  // GOOGLE_PROTOBUF_UTF8_H_

+ 162 - 0
php/src/Google/Protobuf/Internal/DescriptorPool.php

@@ -0,0 +1,162 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\Descriptor;
+use Google\Protobuf\Internal\FileDescriptor;
+use Google\Protobuf\Internal\FileDescriptorSet;
+use Google\Protobuf\Internal\MessageBuilderContext;
+use Google\Protobuf\Internal\EnumBuilderContext;
+
+class DescriptorPool
+{
+    private static $pool;
+    // Map from message names to sub-maps, which are maps from field numbers to
+    // field descriptors.
+    private $class_to_desc = [];
+    private $class_to_enum_desc = [];
+    private $proto_to_class = [];
+
+    public static function getGeneratedPool()
+    {
+        if (!isset(self::$pool)) {
+            self::$pool = new DescriptorPool();
+        }
+        return self::$pool;
+    }
+
+    public function internalAddGeneratedFile($data)
+    {
+        $files = new FileDescriptorSet();
+        $files->decode($data);
+        $file = FileDescriptor::buildFromProto($files->getFile()[0]);
+
+        foreach ($file->getMessageType() as &$desc) {
+            $this->addDescriptor($desc);
+        }
+        unset($desc);
+
+        foreach ($file->getEnumType() as &$desc) {
+            $this->addEnumDescriptor($desc);
+        }
+        unset($desc);
+
+        foreach ($file->getMessageType() as &$desc) {
+            $this->crossLink($desc);
+        }
+        unset($desc);
+    }
+
+    public function addMessage($name, $klass)
+    {
+        return new MessageBuilderContext($name, $klass, $this);
+    }
+
+    public function addEnum($name, $klass)
+    {
+        return new EnumBuilderContext($name, $klass, $this);
+    }
+
+    public function addDescriptor($descriptor)
+    {
+        $this->proto_to_class[$descriptor->getFullName()] =
+            $descriptor->getClass();
+        $this->class_to_desc[$descriptor->getClass()] = $descriptor;
+        foreach ($descriptor->getNestedType() as $nested_type) {
+            $this->addDescriptor($nested_type);
+        }
+    }
+
+    public function addEnumDescriptor($descriptor)
+    {
+        $this->proto_to_class[$descriptor->getFullName()] =
+            $descriptor->getClass();
+        $this->class_to_enum_desc[$descriptor->getClass()] = $descriptor;
+    }
+
+    public function getDescriptorByClassName($klass)
+    {
+        return $this->class_to_desc[$klass];
+    }
+
+    public function getEnumDescriptorByClassName($klass)
+    {
+        return $this->class_to_enum_desc[$klass];
+    }
+
+    public function getDescriptorByProtoName($proto)
+    {
+        $klass = $this->proto_to_class[$proto];
+        return $this->class_to_desc[$klass];
+    }
+
+    public function getEnumDescriptorByProtoName($proto)
+    {
+        $klass = $this->proto_to_class[$proto];
+        return $this->class_to_enum_desc[$klass];
+    }
+
+    private function crossLink(&$desc)
+    {
+        foreach ($desc->getField() as &$field) {
+            switch ($field->getType()) {
+                case GPBType::MESSAGE:
+                    $proto = $field->getMessageType();
+                    $field->setMessageType(
+                        $this->getDescriptorByProtoName($proto));
+                    break;
+                case GPBType::ENUM:
+                    $proto = $field->getEnumType();
+                    $field->setEnumType(
+                        $this->getEnumDescriptorByProtoName($proto));
+                    break;
+                default:
+                    break;
+            }
+        }
+        unset($field);
+
+        foreach ($desc->getNestedType() as &$nested_type) {
+            $this->crossLink($nested_type);
+        }
+        unset($nested_type);
+    }
+
+    public function finish()
+    {
+        foreach ($this->class_to_desc as $klass => &$desc) {
+            $this->crossLink($desc);
+        }
+        unset($desc);
+    }
+}

+ 63 - 0
php/src/Google/Protobuf/Internal/EnumBuilderContext.php

@@ -0,0 +1,63 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\EnumDescriptor;
+use Google\Protobuf\Internal\EnumValueDescriptor;
+
+class EnumBuilderContext
+{
+
+    private $descriptor;
+    private $pool;
+
+    public function __construct($full_name, $klass, $pool)
+    {
+        $this->descriptor = new EnumDescriptor();
+        $this->descriptor->setFullName($full_name);
+        $this->descriptor->setClass($klass);
+        $this->pool = $pool;
+    }
+
+    public function value($name, $number)
+    {
+        $value = new EnumValueDescriptor();
+        $this->descriptor->addValue($number, $value);
+        return $this;
+    }
+
+    public function finalizeToPool()
+    {
+        $this->pool->addEnumDescriptor($this->descriptor);
+    }
+}

+ 40 - 0
php/src/Google/Protobuf/Internal/GPBLabel.php

@@ -0,0 +1,40 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+class GPBLabel
+{
+    const OPTIONAL = 1;
+    const REQUIRED = 2;
+    const REPEATED = 3;
+}

+ 55 - 0
php/src/Google/Protobuf/Internal/GPBType.php

@@ -0,0 +1,55 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+class GPBType
+{
+    const DOUBLE   =  1;
+    const FLOAT    =  2;
+    const INT64    =  3;
+    const UINT64   =  4;
+    const INT32    =  5;
+    const FIXED64  =  6;
+    const FIXED32  =  7;
+    const BOOL     =  8;
+    const STRING   =  9;
+    const GROUP    = 10;
+    const MESSAGE  = 11;
+    const BYTES    = 12;
+    const UINT32   = 13;
+    const ENUM     = 14;
+    const SFIXED32 = 15;
+    const SFIXED64 = 16;
+    const SINT32   = 17;
+    const SINT64   = 18;
+}

+ 161 - 0
php/src/Google/Protobuf/Internal/GPBUtil.php

@@ -0,0 +1,161 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\RepeatedField;
+
+class GPBUtil
+{
+
+    public static function checkString(&$var, $check_utf8)
+    {
+        if (is_array($var) || is_object($var)) {
+            trigger_error("Expect string.", E_USER_ERROR);
+            return;
+        }
+        if (!is_string($var)) {
+            $var = strval($var);
+        }
+        if ($check_utf8 && !preg_match('//u', $var)) {
+            trigger_error("Expect utf-8 encoding.", E_USER_ERROR);
+            return;
+        }
+    }
+
+    public static function checkEnum(&$var)
+    {
+      static::checkInt32($var);
+    }
+
+    public static function checkInt32(&$var)
+    {
+        if (is_numeric($var)) {
+            $var = intval($var);
+        } else {
+            trigger_error("Expect integer.", E_USER_ERROR);
+        }
+    }
+
+    public static function checkUint32(&$var)
+    {
+        if (is_numeric($var)) {
+            $var = intval($var);
+            if (PHP_INT_SIZE === 8) {
+                $var |= ((-(($var >> 31) & 0x1)) & ~0xFFFFFFFF);
+            }
+        } else {
+            trigger_error("Expect integer.", E_USER_ERROR);
+        }
+    }
+
+    public static function checkInt64(&$var)
+    {
+        if (is_numeric($var)) {
+            $var = intval($var);
+        } else {
+            trigger_error("Expect integer.", E_USER_ERROR);
+        }
+    }
+
+    public static function checkUint64(&$var)
+    {
+        if (is_numeric($var)) {
+            $var = intval($var);
+        } else {
+            trigger_error("Expect integer.", E_USER_ERROR);
+        }
+    }
+
+    public static function checkFloat(&$var)
+    {
+        if (is_float($var) || is_numeric($var)) {
+            $var = floatval($var);
+        } else {
+            trigger_error("Expect float.", E_USER_ERROR);
+        }
+    }
+
+    public static function checkDouble(&$var)
+    {
+        if (is_float($var) || is_numeric($var)) {
+            $var = floatval($var);
+        } else {
+            trigger_error("Expect float.", E_USER_ERROR);
+        }
+    }
+
+    public static function checkBool(&$var)
+    {
+        if (is_array($var) || is_object($var)) {
+            trigger_error("Expect boolean.", E_USER_ERROR);
+            return;
+        }
+        $var = boolval($var);
+    }
+
+    public static function checkMessage(&$var, $klass)
+    {
+        if (!$var instanceof $klass && !is_null($var)) {
+            trigger_error("Expect message.", E_USER_ERROR);
+        }
+    }
+
+    public static function checkRepeatedField(&$var, $type, $klass = null)
+    {
+        if (!$var instanceof RepeatedField) {
+            trigger_error("Expect repeated field.", E_USER_ERROR);
+        }
+        if ($var->getType() != $type) {
+            trigger_error(
+                "Expect repeated field of different type.",
+                E_USER_ERROR);
+        }
+        if ($var->getType() === GPBType::MESSAGE &&
+            $var->getClass() !== $klass) {
+            trigger_error(
+                "Expect repeated field of different message.",
+                E_USER_ERROR);
+        }
+    }
+
+    public static function Int64($value)
+    {
+        return new Int64($value);
+    }
+
+    public static function Uint64($value)
+    {
+        return new Uint64($value);
+    }
+}

+ 583 - 0
php/src/Google/Protobuf/Internal/GPBWire.php

@@ -0,0 +1,583 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\GPBUtil;
+use Google\Protobuf\Internal\Int64;
+use Google\Protobuf\Internal\Uint64;
+
+class GPBWire
+{
+
+    const TAG_TYPE_BITS = 3;
+
+    const WIRETYPE_VARINT  = 0;
+    const WIRETYPE_FIXED64 = 1;
+    const WIRETYPE_LENGTH_DELIMITED = 2;
+    const WIRETYPE_START_GROUP = 3;
+    const WIRETYPE_END_GROUP = 4;
+    const WIRETYPE_FIXED32 = 5;
+
+    const UNKNOWN = 0;
+    const NORMAL_FORMAT = 1;
+    const PACKED_FORMAT = 2;
+
+    public static function getTagFieldNumber($tag)
+    {
+        return ($tag >> self::TAG_TYPE_BITS) &
+            (1 << ((PHP_INT_SIZE * 8) - self::TAG_TYPE_BITS)) - 1;
+    }
+
+    public static function getTagWireType($tag)
+    {
+        return $tag & 0x7;
+    }
+
+    public static function getWireType($type)
+    {
+        switch ($type) {
+            case GPBType::FLOAT:
+            case GPBType::FIXED32:
+            case GPBType::SFIXED32:
+                return self::WIRETYPE_FIXED32;
+            case GPBType::DOUBLE:
+            case GPBType::FIXED64:
+            case GPBType::SFIXED64:
+                return self::WIRETYPE_FIXED64;
+            case GPBType::UINT32:
+            case GPBType::UINT64:
+            case GPBType::INT32:
+            case GPBType::INT64:
+            case GPBType::SINT32:
+            case GPBType::SINT64:
+            case GPBType::ENUM:
+            case GPBType::BOOL:
+                return self::WIRETYPE_VARINT;
+            case GPBType::STRING:
+            case GPBType::BYTES:
+            case GPBType::MESSAGE:
+                return self::WIRETYPE_LENGTH_DELIMITED;
+            case GPBType::GROUP:
+                user_error("Unsupported type.");
+                return 0;
+            default:
+                user_error("Unsupported type.");
+                return 0;
+        }
+    }
+
+  // ZigZag Transform:  Encodes signed integers so that they can be effectively
+  // used with varint encoding.
+  //
+  // varint operates on unsigned integers, encoding smaller numbers into fewer
+  // bytes.  If you try to use it on a signed integer, it will treat this
+  // number as a very large unsigned integer, which means that even small
+  // signed numbers like -1 will take the maximum number of bytes (10) to
+  // encode.  zigZagEncode() maps signed integers to unsigned in such a way
+  // that those with a small absolute value will have smaller encoded values,
+  // making them appropriate for encoding using varint.
+  //
+  // int32 ->     uint32
+  // -------------------------
+  //           0 ->          0
+  //          -1 ->          1
+  //           1 ->          2
+  //          -2 ->          3
+  //         ... ->        ...
+  //  2147483647 -> 4294967294
+  // -2147483648 -> 4294967295
+  //
+  //        >> encode >>
+  //        << decode <<
+  public static function zigZagEncode32($int32)
+  {
+      // Fill high 32 bits.
+      if (PHP_INT_SIZE === 8) {
+          $int32 |= ((($int32 << 32) >> 31) & (0xFFFFFFFF << 32));
+      }
+
+      $uint32 = ($int32 << 1) ^ ($int32 >> 31);
+
+      // Fill high 32 bits.
+      if (PHP_INT_SIZE === 8) {
+          $uint32 |= ((($uint32 << 32) >> 31) & (0xFFFFFFFF << 32));
+      }
+
+      return $uint32;
+  }
+
+    public static function zigZagDecode32($uint32)
+    {
+        // Fill high 32 bits.
+        if (PHP_INT_SIZE === 8) {
+            $uint32 |= ($uint32 & 0xFFFFFFFF);
+        }
+
+        $int32 = (($uint32 >> 1) & 0x7FFFFFFF) ^ (-($uint32 & 1));
+
+        return $int32;
+    }
+
+    public static function zigZagEncode64($int64)
+    {
+        $a = $int64->copy()->leftShift(1);
+        $b = $int64->copy()->rightShift(63);
+        $result = $a->bitXor($b);
+        $uint64 = Uint64::newValue($result->high, $result->low);
+        return $uint64;
+    }
+
+    public static function zigZagDecode64($uint64)
+    {
+        $a = $uint64->copy()->rightShift(1);
+        $b = $uint64->oddMask();
+        $result = $a->bitXor($b);
+        $int64 = Int64::newValue($result->high, $result->low);
+        return $int64;
+    }
+
+    public static function readInt32(&$input, &$value)
+    {
+        return $input->readVarint32($value);
+    }
+
+    public static function readInt64(&$input, &$value)
+    {
+        return $input->readVarint64($value);
+    }
+
+    public static function readUint32(&$input, &$value)
+    {
+        return self::readInt32($input, $value);
+    }
+
+    public static function readUint64(&$input, &$value)
+    {
+        return self::readInt64($input, $value);
+    }
+
+    public static function readSint32(&$input, &$value)
+    {
+        if (!$input->readVarint32($value)) {
+            return false;
+        }
+        $value = GPBWire::zigZagDecode32($value);
+        return true;
+    }
+
+    public static function readSint64(&$input, &$value)
+    {
+        if (!$input->readVarint64($value)) {
+            return false;
+        }
+        $value = GPBWire::zigZagDecode64($value);
+        return true;
+    }
+
+    public static function readFixed32(&$input, &$value)
+    {
+        return $input->readLittleEndian32($value);
+    }
+
+    public static function readFixed64(&$input, &$value)
+    {
+        return $input->readLittleEndian64($value);
+    }
+
+    public static function readSfixed32(&$input, &$value)
+    {
+        if (!self::readFixed32($input, $value)) {
+            return false;
+        }
+        if (PHP_INT_SIZE === 8) {
+            $value |= (-($value >> 31) << 32);
+        }
+        return true;
+    }
+
+    public static function readSfixed64(&$input, &$value)
+    {
+        if (!self::readFixed64($input, $value)) {
+            return false;
+        }
+        $value = Int64::newValue($value->high, $value->low);
+        return true;
+    }
+
+    public static function readFloat(&$input, &$value)
+    {
+        $data = null;
+        if (!$input->readRaw(4, $data)) {
+            return false;
+        }
+        $value = unpack('f', $data)[1];
+        return true;
+    }
+
+    public static function readDouble(&$input, &$value)
+    {
+        $data = null;
+        if (!$input->readRaw(8, $data)) {
+            return false;
+        }
+        $value = unpack('d', $data)[1];
+        return true;
+    }
+
+    public static function readBool(&$input, &$value)
+    {
+        if (!$input->readVarint64($value)) {
+            return false;
+        }
+        if ($value->high === 0 && $value->low === 0) {
+            $value = false;
+        } else {
+            $value = true;
+        }
+        return true;
+    }
+
+    public static function readString(&$input, &$value)
+    {
+        $length = 0;
+        return $input->readVarintSizeAsInt($length) && $input->readRaw($length, $value);
+    }
+
+    public static function readMessage(&$input, &$message)
+    {
+        $length = 0;
+        if (!$input->readVarintSizeAsInt($length)) {
+            return false;
+        }
+        $old_limit = 0;
+        $recursion_limit = 0;
+        $input->incrementRecursionDepthAndPushLimit(
+            $length,
+            $old_limit,
+            $recursion_limit);
+        if ($recursion_limit < 0 || !$message->parseFromStream($input)) {
+            return false;
+        }
+        return $input->decrementRecursionDepthAndPopLimit($old_limit);
+    }
+
+    public static function writeTag(&$output, $tag)
+    {
+        return $output->writeTag($tag);
+    }
+
+    public static function writeInt32(&$output, $value)
+    {
+        return $output->writeVarint32($value);
+    }
+
+    public static function writeInt64(&$output, $value)
+    {
+        return $output->writeVarint64($value);
+    }
+
+    public static function writeUint32(&$output, $value)
+    {
+        return $output->writeVarint32($value);
+    }
+
+    public static function writeUint64(&$output, $value)
+    {
+        return $output->writeVarint64($value);
+    }
+
+    public static function writeSint32(&$output, $value)
+    {
+        $value = GPBWire::zigZagEncode32($value);
+        return $output->writeVarint64($value);
+    }
+
+    public static function writeSint64(&$output, $value)
+    {
+        $value = GPBWire::zigZagEncode64(GPBUtil::Int64($value));
+        return $output->writeVarint64($value->toInteger());
+    }
+
+    public static function writeFixed32(&$output, $value)
+    {
+        return $output->writeLittleEndian32($value);
+    }
+
+    public static function writeFixed64(&$output, $value)
+    {
+        return $output->writeLittleEndian64($value);
+    }
+
+    public static function writeSfixed32(&$output, $value)
+    {
+        return $output->writeLittleEndian32($value);
+    }
+
+    public static function writeSfixed64(&$output, $value)
+    {
+        return $output->writeLittleEndian64($value);
+    }
+
+    public static function writeBool(&$output, $value)
+    {
+        if ($value) {
+            return $output->writeVarint32(1);
+        } else {
+            return $output->writeVarint32(0);
+        }
+    }
+
+    public static function writeFloat(&$output, $value)
+    {
+        $data = pack("f", $value);
+        return $output->writeRaw($data, 4);
+    }
+
+    public static function writeDouble(&$output, $value)
+    {
+        $data = pack("d", $value);
+        return $output->writeRaw($data, 8);
+    }
+
+    public static function writeString(&$output, $value)
+    {
+        return self::writeBytes($output, $value);
+    }
+
+    public static function writeBytes(&$output, $value)
+    {
+        $size = strlen($value);
+        if (!$output->writeVarint32($size)) {
+            return false;
+        }
+        return $output->writeRaw($value, $size);
+    }
+
+    public static function writeMessage(&$output, $value)
+    {
+        $size = $value->byteSize();
+        if (!$output->writeVarint32($size)) {
+            return false;
+        }
+        return $value->serializeToStream($output);
+    }
+
+    public static function makeTag($number, $type)
+    {
+        return ($number << 3) | self::getWireType($type);
+    }
+
+    public static function tagSize($field)
+    {
+        $tag = self::makeTag($field->getNumber(), $field->getType());
+        return self::varint32Size($tag);
+    }
+
+    public static function varint32Size($value)
+    {
+        if ($value < 0) {
+            return 5;
+        }
+        if ($value < (1 <<  7)) {
+            return 1;
+        }
+        if ($value < (1 << 14)) {
+            return 2;
+        }
+        if ($value < (1 << 21)) {
+            return 3;
+        }
+        if ($value < (1 << 28)) {
+            return 4;
+        }
+        return 5;
+    }
+
+    public static function sint32Size($value)
+    {
+        $value = self::zigZagEncode32($value);
+        return self::varint32Size($value);
+    }
+
+    public static function sint64Size($value)
+    {
+        $value = GPBUtil::Int64($value);
+        $value = self::zigZagEncode64($value);
+        return self::varint64Size($value->toInteger());
+    }
+
+    public static function varint64Size($value)
+    {
+        if ($value < 0) {
+            return 10;
+        }
+        if ($value < (1 <<  7)) {
+            return 1;
+        }
+        if ($value < (1 << 14)) {
+            return 2;
+        }
+        if ($value < (1 << 21)) {
+            return 3;
+        }
+        if ($value < (1 << 28)) {
+            return 4;
+        }
+        if ($value < (1 << 35)) {
+            return 5;
+        }
+        if ($value < (1 << 42)) {
+            return 6;
+        }
+        if ($value < (1 << 49)) {
+            return 7;
+        }
+        if ($value < (1 << 56)) {
+            return 8;
+        }
+        return 9;
+    }
+
+    public static function serializeFieldToStream(
+        $value,
+        $field,
+        $need_tag,
+        &$output)
+    {
+        if ($need_tag) {
+            if (!GPBWire::writeTag(
+                $output,
+                self::makeTag(
+                    $field->getNumber(),
+                    $field->getType()))) {
+                return false;
+            }
+        }
+        switch ($field->getType()) {
+            case GPBType::DOUBLE:
+                if (!GPBWire::writeDouble($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::FLOAT:
+                if (!GPBWire::writeFloat($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::INT64:
+                if (!GPBWire::writeInt64($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::UINT64:
+                if (!GPBWire::writeUint64($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::INT32:
+                if (!GPBWire::writeInt32($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::FIXED32:
+                if (!GPBWire::writeFixed32($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::FIXED64:
+                if (!GPBWire::writeFixed64($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::BOOL:
+                if (!GPBWire::writeBool($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::STRING:
+                if (!GPBWire::writeString($output, $value)) {
+                    return false;
+                }
+                break;
+            //    case GPBType::GROUP:
+            //      echo "GROUP\xA";
+            //      trigger_error("Not implemented.", E_ERROR);
+            //      break;
+            case GPBType::MESSAGE:
+                if (!GPBWire::writeMessage($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::BYTES:
+                if (!GPBWire::writeBytes($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::UINT32:
+                if (!GPBWire::writeUint32($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::ENUM:
+                if (!GPBWire::writeInt32($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::SFIXED32:
+                if (!GPBWire::writeSfixed32($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::SFIXED64:
+                if (!GPBWire::writeSfixed64($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::SINT32:
+                if (!GPBWire::writeSint32($output, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::SINT64:
+                if (!GPBWire::writeSint64($output, $value)) {
+                    return false;
+                }
+                break;
+            default:
+                user_error("Unsupported type.");
+                return false;
+        }
+
+        return true;
+    }
+}

+ 323 - 0
php/src/Google/Protobuf/Internal/InputStream.php

@@ -0,0 +1,323 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\Uint64;
+
+class InputStream
+{
+
+    private $buffer;
+    private $buffer_size_after_limit;
+    private $buffer_end;
+    private $current;
+    private $current_limit;
+    private $legitimate_message_end;
+    private $recursion_budget;
+    private $recursion_limit;
+    private $total_bytes_limit;
+    private $total_bytes_read;
+
+    const MAX_VARINT_BYTES = 10;
+    const MAX_VARINT32_BYTES = 5;
+    const DEFAULT_RECURSION_LIMIT = 100;
+    const DEFAULT_TOTAL_BYTES_LIMIT = 33554432; // 32 << 20, 32MB
+
+    public function __construct($buffer)
+    {
+        $start = 0;
+        $end = strlen($buffer);
+        $this->buffer = $buffer;
+        $this->buffer_size_after_limit = 0;
+        $this->buffer_end = $end;
+        $this->current = $start;
+        $this->current_limit = $end;
+        $this->legitimate_message_end = false;
+        $this->recursion_budget = self::DEFAULT_RECURSION_LIMIT;
+        $this->recursion_limit = self::DEFAULT_RECURSION_LIMIT;
+        $this->total_bytes_limit = self::DEFAULT_TOTAL_BYTES_LIMIT;
+        $this->total_bytes_read = $end - $start;
+    }
+
+    private function advance($amount)
+    {
+        $this->current += $amount;
+    }
+
+    private function bufferSize()
+    {
+        return $this->buffer_end - $this->current;
+    }
+
+    private function current()
+    {
+        return $this->total_bytes_read -
+            ($this->buffer_end - $this->current +
+            $this->buffer_size_after_limit);
+    }
+
+    private function recomputeBufferLimits()
+    {
+        $this->buffer_end += $this->buffer_size_after_limit;
+        $closest_limit = min($this->current_limit, $this->total_bytes_limit);
+        if ($closest_limit < $this->total_bytes_read) {
+            // The limit position is in the current buffer.  We must adjust the
+            // buffer size accordingly.
+            $this->buffer_size_after_limit = $this->total_bytes_read -
+                $closest_limit;
+            $this->buffer_end -= $this->buffer_size_after_limit;
+        } else {
+            $this->buffer_size_after_limit = 0;
+        }
+    }
+
+    private function consumedEntireMessage()
+    {
+        return $this->legitimate_message_end;
+    }
+
+    /**
+     * Read uint32 into $var. Advance buffer with consumed bytes. If the
+     * contained varint is larger than 32 bits, discard the high order bits.
+     * @param $var.
+     */
+    public function readVarint32(&$var)
+    {
+        if (!$this->readVarint64($var)) {
+            return false;
+        }
+        $var = $var->toInteger() & 0xFFFFFFFF;
+        // Convert large uint32 to int32.
+        if (PHP_INT_SIZE === 8 && ($var > 0x7FFFFFFF)) {
+            $var = $var | (0xFFFFFFFF << 32);
+        }
+        return true;
+    }
+
+    /**
+     * Read Uint64 into $var. Advance buffer with consumed bytes.
+     * @param $var.
+     */
+    public function readVarint64(&$var)
+    {
+        $result = new Uint64(0);
+        $count = 0;
+        $b = 0;
+
+        do {
+            if ($this->current === $this->buffer_end) {
+                return false;
+            }
+            if ($count === self::MAX_VARINT_BYTES) {
+                return false;
+            }
+            $b = ord($this->buffer[$this->current]);
+            $result->bitOr((new Uint64($b & 0x7F))->leftShift(7 * $count));
+            $this->advance(1);
+            $count += 1;
+        } while ($b & 0x80);
+
+        $var = $result;
+        return true;
+    }
+
+    /**
+     * Read int into $var. If the result is larger than the largest integer, $var
+     * will be -1. Advance buffer with consumed bytes.
+     * @param $var.
+     */
+    public function readVarintSizeAsInt(&$var)
+    {
+        if (!$this->readVarint64($var)) {
+            return false;
+        }
+        $var = $var->toInteger();
+        return true;
+    }
+
+    /**
+     * Read 32-bit unsiged integer to $var. If the buffer has less than 4 bytes,
+     * return false. Advance buffer with consumed bytes.
+     * @param $var.
+     */
+    public function readLittleEndian32(&$var)
+    {
+        $data = null;
+        if (!$this->readRaw(4, $data)) {
+            return false;
+        }
+        $var = unpack('V', $data);
+        $var = $var[1];
+        return true;
+    }
+
+    /**
+     * Read 64-bit unsiged integer to $var. If the buffer has less than 8 bytes,
+     * return false. Advance buffer with consumed bytes.
+     * @param $var.
+     */
+    public function readLittleEndian64(&$var)
+    {
+        $data = null;
+        if (!$this->readRaw(4, $data)) {
+            return false;
+        }
+        $low = unpack('V', $data)[1];
+        if (!$this->readRaw(4, $data)) {
+            return false;
+        }
+        $high = unpack('V', $data)[1];
+        $var = Uint64::newValue($high, $low);
+        return true;
+    }
+
+    /**
+     * Read tag into $var. Advance buffer with consumed bytes.
+     * @param $var.
+     */
+    public function readTag()
+    {
+        if ($this->current === $this->buffer_end) {
+            // Make sure that it failed due to EOF, not because we hit
+            // total_bytes_limit, which, unlike normal limits, is not a valid
+            // place to end a message.
+            $current_position = $this->total_bytes_read -
+                $this->buffer_size_after_limit;
+            if ($current_position >= $this->total_bytes_limit) {
+                // Hit total_bytes_limit_.  But if we also hit the normal limit,
+                // we're still OK.
+                $this->legitimate_message_end =
+                    ($this->current_limit === $this->total_bytes_limit);
+            } else {
+                $this->legitimate_message_end = true;
+            }
+            return 0;
+        }
+
+        $result = 0;
+        // The larget tag is 2^29 - 1, which can be represented by int32.
+        $success = $this->readVarint32($result);
+        if ($success) {
+            return $result;
+        } else {
+            return 0;
+        }
+    }
+
+    public function readRaw($size, &$buffer)
+    {
+        $current_buffer_size = 0;
+        if ($this->bufferSize() < $size) {
+            return false;
+        }
+
+        $buffer = substr($this->buffer, $this->current, $size);
+        $this->advance($size);
+
+        return true;
+    }
+
+    /* Places a limit on the number of bytes that the stream may read, starting
+     * from the current position.  Once the stream hits this limit, it will act
+     * like the end of the input has been reached until popLimit() is called.
+     *
+     * As the names imply, the stream conceptually has a stack of limits.  The
+     * shortest limit on the stack is always enforced, even if it is not the top
+     * limit.
+     *
+     * The value returned by pushLimit() is opaque to the caller, and must be
+     * passed unchanged to the corresponding call to popLimit().
+     *
+     * @param integer $byte_limit
+     */
+    public function pushLimit($byte_limit)
+    {
+        // Current position relative to the beginning of the stream.
+        $current_position = $this->current();
+        $old_limit = $this->current_limit;
+
+        // security: byte_limit is possibly evil, so check for negative values
+        // and overflow.
+        if ($byte_limit >= 0 && $byte_limit <= PHP_INT_MAX - $current_position) {
+            $this->current_limit = $current_position + $byte_limit;
+        } else {
+            // Negative or overflow.
+            $this->current_limit = PHP_INT_MAX;
+        }
+
+        // We need to enforce all limits, not just the new one, so if the previous
+        // limit was before the new requested limit, we continue to enforce the
+        // previous limit.
+        $this->current_limit = min($this->current_limit, $old_limit);
+
+        $this->recomputeBufferLimits();
+        return $old_limit;
+    }
+
+    /* The limit passed in is actually the *old* limit, which we returned from
+     * PushLimit().
+     *
+     * @param integer $byte_limit
+     */
+    public function popLimit($byte_limit)
+    {
+        $this->current_limit = $byte_limit;
+        $this->recomputeBufferLimits();
+        // We may no longer be at a legitimate message end.  ReadTag() needs to
+        // be called again to find out.
+        $this->legitimate_message_end = false;
+    }
+
+    public function incrementRecursionDepthAndPushLimit(
+    $byte_limit, &$old_limit, &$recursion_budget)
+    {
+        $old_limit = $this->pushLimit($byte_limit);
+        $recursion_limit = --$this->recursion_limit;
+    }
+
+    public function decrementRecursionDepthAndPopLimit($byte_limit)
+    {
+        $result = $this->consumedEntireMessage();
+        $this->popLimit($byte_limit);
+        ++$this->recursion_budget;
+        return $result;
+    }
+
+    public function bytesUntilLimit()
+    {
+        if ($this->current_limit === PHP_INT_MAX) {
+            return -1;
+        }
+        return $this->current_limit - $this->current;
+    }
+}

+ 57 - 0
php/src/Google/Protobuf/Internal/MapEntry.php

@@ -0,0 +1,57 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\Message;
+
+class MapEntry extends Message
+{
+    public $key;
+    public $value;
+
+    public function setKey(&$key) {
+      $this->key = $key;
+    }
+
+    public function getKey() {
+      return $this->key;
+    }
+
+    public function setValue(&$value) {
+      $this->value = $value;
+    }
+
+    public function getValue() {
+      return $this->value;
+    }
+}

+ 321 - 0
php/src/Google/Protobuf/Internal/MapField.php

@@ -0,0 +1,321 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * MapField and MapFieldIter are used by generated protocol message classes to
+ * manipulate map fields.
+ */
+
+namespace Google\Protobuf\Internal;
+
+/**
+ * MapFieldIter is used to iterate MapField. It is also need for the foreach
+ * syntax.
+ */
+class MapFieldIter implements \Iterator
+{
+
+    /**
+     * @ignore
+     */
+    private $container;
+
+    /**
+     * Create iterator instance for MapField.
+     *
+     * @param MapField The MapField instance for which this iterator is
+     * created.
+     * @ignore
+     */
+    public function __construct($container)
+    {
+        $this->container = $container;
+    }
+
+    /**
+     * Reset the status of the iterator
+     *
+     * @return void
+     */
+    public function rewind()
+    {
+        return reset($this->container);
+    }
+
+    /**
+     * Return the element at the current position.
+     *
+     * @return object The element at the current position.
+     */
+    public function current()
+    {
+        return current($this->container);
+    }
+
+    /**
+     * Return the current key.
+     *
+     * @return object The current key.
+     */
+    public function key()
+    {
+        return key($this->container);
+    }
+
+    /**
+     * Move to the next position.
+     *
+     * @return void
+     */
+    public function next()
+    {
+        return next($this->container);
+    }
+
+    /**
+     * Check whether there are more elements to iterate.
+     *
+     * @return bool True if there are more elements to iterate.
+     */
+    public function valid()
+    {
+        return key($this->container) !== null;
+    }
+}
+
+/**
+ * @ignore
+ */
+function checkKey($key_type, &$key)
+{
+    switch ($key_type) {
+        case GPBType::INT32:
+            GPBUtil::checkInt32($key);
+            break;
+        case GPBType::UINT32:
+            GPBUtil::checkUint32($key);
+            break;
+        case GPBType::INT64:
+            GPBUtil::checkInt64($key);
+            break;
+        case GPBType::UINT64:
+            GPBUtil::checkUint64($key);
+            break;
+        case GPBType::FIXED64:
+            GPBUtil::checkUint64($key);
+            break;
+        case GPBType::FIXED32:
+            GPBUtil::checkUint32($key);
+            break;
+        case GPBType::SFIXED64:
+            GPBUtil::checkInt64($key);
+            break;
+        case GPBType::SFIXED32:
+            GPBUtil::checkInt32($key);
+            break;
+        case GPBType::SINT64:
+            GPBUtil::checkInt64($key);
+            break;
+        case GPBType::SINT32:
+            GPBUtil::checkInt32($key);
+            break;
+        case GPBType::BOOL:
+            GPBUtil::checkBool($key);
+            break;
+        case GPBType::STRING:
+            GPBUtil::checkString($key, true);
+            break;
+        default:
+            var_dump($key_type);
+            trigger_error(
+                "Given type cannot be map key.",
+                E_USER_ERROR);
+            break;
+    }
+}
+
+/**
+ * MapField is used by generated protocol message classes to manipulate map
+ * fields. It can be used like native PHP array.
+ */
+class MapField implements \ArrayAccess, \IteratorAggregate, \Countable
+{
+    /**
+     * @ignore
+     */
+    private $container;
+    /**
+     * @ignore
+     */
+    private $key_type;
+    /**
+     * @ignore
+     */
+    private $value_type;
+    /**
+     * @ignore
+     */
+    private $value_klass;
+
+    /**
+     * Constructs an instance of MapField.
+     *
+     * @param long $key_type Type of the stored key element.
+     * @param long $value_type Type of the stored value element.
+     * @param string $klass Message/Enum class name of value instance
+     * (message/enum fields only).
+     * @ignore
+     */
+    public function __construct($key_type, $value_type, $klass = null)
+    {
+        $this->container = [];
+        $this->key_type = $key_type;
+        $this->value_type = $value_type;
+        $this->klass = $klass;
+    }
+
+    /**
+     * Return the element at the given key.
+     *
+     * This will also be called for: $ele = $arr[$key]
+     *
+     * @param object $key The key of the element to be fetched.
+     * @return object The stored element at given key.
+     * @throws ErrorException Invalid type for index.
+     * @throws ErrorException Non-existing index.
+     */
+    public function offsetGet($key)
+    {
+        return $this->container[$key];
+    }
+
+    /**
+     * Assign the element at the given key.
+     *
+     * This will also be called for: $arr[$key] = $value
+     *
+     * @param object $key The key of the element to be fetched.
+     * @param object $value The element to be assigned.
+     * @return void
+     * @throws ErrorException Invalid type for key.
+     * @throws ErrorException Invalid type for value.
+     * @throws ErrorException Non-existing key.
+     */
+    public function offsetSet($key, $value)
+    {
+        checkKey($this->key_type, $key);
+
+        switch ($this->value_type) {
+            case GPBType::INT32:
+                GPBUtil::checkInt32($value);
+                break;
+            case GPBType::UINT32:
+                GPBUtil::checkUint32($value);
+                break;
+            case GPBType::INT64:
+                GPBUtil::checkInt64($value);
+                break;
+            case GPBType::UINT64:
+                GPBUtil::checkUint64($value);
+                break;
+            case GPBType::FLOAT:
+                GPBUtil::checkFloat($value);
+                break;
+            case GPBType::DOUBLE:
+                GPBUtil::checkDouble($value);
+                break;
+            case GPBType::BOOL:
+                GPBUtil::checkBool($value);
+                break;
+            case GPBType::STRING:
+                GPBUtil::checkString($value, true);
+                break;
+            case GPBType::MESSAGE:
+                GPBUtil::checkMessage($value, $this->klass);
+                break;
+            default:
+                break;
+        }
+
+        $this->container[$key] = $value;
+    }
+
+    /**
+     * Remove the element at the given key.
+     *
+     * This will also be called for: unset($arr)
+     *
+     * @param object $key The key of the element to be removed.
+     * @return void
+     * @throws ErrorException Invalid type for key.
+     */
+    public function offsetUnset($key)
+    {
+        checkKey($this->key_type, $key);
+        unset($this->container[$key]);
+    }
+
+    /**
+     * Check the existence of the element at the given key.
+     *
+     * This will also be called for: isset($arr)
+     *
+     * @param object $key The key of the element to be removed.
+     * @return bool True if the element at the given key exists.
+     * @throws ErrorException Invalid type for key.
+     */
+    public function offsetExists($key)
+    {
+        checkKey($this->key_type, $key);
+        return isset($this->container[$key]);
+    }
+
+    /**
+     * @ignore
+     */
+    public function getIterator()
+    {
+        return new MapFieldIter($this->container);
+    }
+
+    /**
+     * Return the number of stored elements.
+     *
+     * This will also be called for: count($arr)
+     *
+     * @return integer The number of stored elements.
+     */
+    public function count()
+    {
+        return count($this->container);
+    }
+}

+ 671 - 0
php/src/Google/Protobuf/Internal/Message.php

@@ -0,0 +1,671 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * Defines Message, the parent class extended by all protocol message classes.
+ */
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\InputStream;
+use Google\Protobuf\Internal\OutputStream;
+use Google\Protobuf\Internal\DescriptorPool;
+use Google\Protobuf\Internal\GPBLabel;
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\GPBWire;
+use Google\Protobuf\Internal\MapEntry;
+use Google\Protobuf\Internal\RepeatedField;
+
+/**
+ * Parent class of all proto messages. Users should not instantiate this class
+ * or extend this class or its child classes by their own.  See the comment of
+ * specific functions for more details.
+ */
+class Message
+{
+
+    /**
+     * @ignore
+     */
+    private $desc;
+
+    /**
+     * @ignore
+     */
+    public function __construct($desc = NULL)
+    {
+        // MapEntry message is shared by all types of map fields, whose
+        // descriptors are different from each other. Thus, we cannot find a
+        // specific descriptor from the descriptor pool.
+        if (get_class($this) === 'Google\Protobuf\Internal\MapEntry') {
+            $this->desc = $desc;
+            return;
+        }
+        $pool = DescriptorPool::getGeneratedPool();
+        $this->desc = $pool->getDescriptorByClassName(get_class($this));
+        foreach ($this->desc->getField() as $field) {
+            $setter = $field->getSetter();
+            if ($field->isMap()) {
+                $message_type = $field->getMessageType();
+                $key_field = $message_type->getFieldByNumber(1);
+                $value_field = $message_type->getFieldByNumber(2);
+                switch ($value_field->getType()) {
+                    case GPBType::MESSAGE:
+                    case GPBType::GROUP:
+                        $this->$setter(
+                            new MapField(
+                                $key_field->getType(),
+                                $value_field->getType(),
+                                $value_field->getMessageType()->getClass()));
+                        break;
+                    case GPBType::ENUM:
+                        $this->$setter(
+                            new MapField(
+                                $key_field->getType(),
+                                $value_field->getType(),
+                                $value_field->getEnumType()->getClass()));
+                        break;
+                    default:
+                        $this->$setter(new MapField($key_field->getType(),
+                                                    $value_field->getType()));
+                        break;
+                }
+            } else if ($field->getLabel() === GPBLabel::REPEATED) {
+                switch ($field->getType()) {
+                    case GPBType::MESSAGE:
+                    case GPBType::GROUP:
+                        $this->$setter(
+                            new RepeatedField(
+                                $field->getType(),
+                                $field->getMessageType()->getClass()));
+                        break;
+                    case GPBType::ENUM:
+                        $this->$setter(
+                            new RepeatedField(
+                                $field->getType(),
+                                $field->getEnumType()->getClass()));
+                        break;
+                    default:
+                        $this->$setter(new RepeatedField($field->getType()));
+                        break;
+                }
+            } else if ($field->getOneofIndex() !== -1) {
+                $oneof = $this->desc->getOneofDecl()[$field->getOneofIndex()];
+                $oneof_name = $oneof->getName();
+                $this->$oneof_name = new OneofField($oneof);
+            }
+        }
+    }
+
+    protected function readOneof($number)
+    {
+        $field = $this->desc->getFieldByNumber($number);
+        $oneof = $this->desc->getOneofDecl()[$field->getOneofIndex()];
+        $oneof_name = $oneof->getName();
+        $oneof_field = $this->$oneof_name;
+        if ($number === $oneof_field->getNumber()) {
+            return $oneof_field->getValue();
+        } else {
+            return $this->defaultValue($field);
+        }
+    }
+
+    protected function writeOneof($number, $value)
+    {
+        $field = $this->desc->getFieldByNumber($number);
+        $oneof = $this->desc->getOneofDecl()[$field->getOneofIndex()];
+        $oneof_name = $oneof->getName();
+        $oneof_field = $this->$oneof_name;
+        $oneof_field->setValue($value);
+        $oneof_field->setFieldName($field->getName());
+        $oneof_field->setNumber($number);
+    }
+
+    /**
+     * @ignore
+     */
+    private function defaultValue($field)
+    {
+        $value = null;
+
+        switch ($field->getType()) {
+            case GPBType::DOUBLE:
+            case GPBType::FLOAT:
+                return 0.0;
+            case GPBType::UINT32:
+            case GPBType::UINT64:
+            case GPBType::INT32:
+            case GPBType::INT64:
+            case GPBType::FIXED32:
+            case GPBType::FIXED64:
+            case GPBType::SFIXED32:
+            case GPBType::SFIXED64:
+            case GPBType::SINT32:
+            case GPBType::SINT64:
+            case GPBType::ENUM:
+                return 0;
+            case GPBType::BOOL:
+                return false;
+            case GPBType::STRING:
+            case GPBType::BYTES:
+                return "";
+            case GPBType::GROUP:
+            case GPBType::MESSAGE:
+                return null;
+            default:
+                user_error("Unsupported type.");
+                return false;
+        }
+    }
+
+    /**
+     * @ignore
+     */
+    private static function parseFieldFromStreamNoTag($input, $field, &$value)
+    {
+        switch ($field->getType()) {
+            case GPBType::DOUBLE:
+                if (!GPBWire::readDouble($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::FLOAT:
+                if (!GPBWire::readFloat($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::INT64:
+                if (!GPBWire::readInt64($input, $value)) {
+                    return false;
+                }
+                $value = $value->toInteger();
+                break;
+            case GPBType::UINT64:
+                if (!GPBWire::readUint64($input, $value)) {
+                    return false;
+                }
+                $value = $value->toInteger();
+                break;
+            case GPBType::INT32:
+                if (!GPBWire::readInt32($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::FIXED64:
+                if (!GPBWire::readFixed64($input, $value)) {
+                    return false;
+                }
+                $value = $value->toInteger();
+                break;
+            case GPBType::FIXED32:
+                if (!GPBWire::readFixed32($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::BOOL:
+                if (!GPBWire::readBool($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::STRING:
+                // TODO(teboring): Add utf-8 check.
+                if (!GPBWire::readString($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::GROUP:
+                echo "GROUP\xA";
+                trigger_error("Not implemented.", E_ERROR);
+                break;
+            case GPBType::MESSAGE:
+                if ($field->isMap()) {
+                    $value = new MapEntry($field->getMessageType());
+                } else {
+                    $klass = $field->getMessageType()->getClass();
+                    $value = new $klass;
+                }
+                if (!GPBWire::readMessage($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::BYTES:
+                if (!GPBWire::readString($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::UINT32:
+                if (!GPBWire::readUint32($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::ENUM:
+                // TODO(teboring): Check unknown enum value.
+                if (!GPBWire::readInt32($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::SFIXED32:
+                if (!GPBWire::readSfixed32($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::SFIXED64:
+                if (!GPBWire::readSfixed64($input, $value)) {
+                    return false;
+                }
+                $value = $value->toInteger();
+                break;
+            case GPBType::SINT32:
+                if (!GPBWire::readSint32($input, $value)) {
+                    return false;
+                }
+                break;
+            case GPBType::SINT64:
+                if (!GPBWire::readSint64($input, $value)) {
+                    return false;
+                }
+                $value = $value->toInteger();
+                break;
+            default:
+                user_error("Unsupported type.");
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * @ignore
+     */
+    private function parseFieldFromStream($tag, $input, $field)
+    {
+        $value = null;
+        $field_type = $field->getType();
+
+        $value_format = GPBWire::UNKNOWN;
+        if (GPBWire::getTagWireType($tag) ===
+            GPBWire::getWireType($field_type)) {
+            $value_format = GPBWire::NORMAL_FORMAT;
+        } elseif ($field->isPackable() &&
+            GPBWire::getTagWireType($tag) ===
+            GPBWire::WIRETYPE_LENGTH_DELIMITED) {
+            $value_format = GPBWire::PACKED_FORMAT;
+        }
+
+        if ($value_format === GPBWire::NORMAL_FORMAT) {
+            if (!self::parseFieldFromStreamNoTag($input, $field, $value)) {
+                return false;
+            }
+        } elseif ($value_format === GPBWire::PACKED_FORMAT) {
+            $length = 0;
+            if (!GPBWire::readInt32($input, $length)) {
+                return false;
+            }
+            $limit = $input->pushLimit($length);
+            $getter = $field->getGetter();
+            while ($input->bytesUntilLimit() > 0) {
+                if (!self::parseFieldFromStreamNoTag($input, $field, $value)) {
+                    return false;
+                }
+                $this->$getter()[] = $value;
+            }
+            $input->popLimit($limit);
+            return true;
+        } else {
+            return false;
+        }
+
+        if ($field->isMap()) {
+            $getter = $field->getGetter();
+            $this->$getter()[$value->getKey()] = $value->getValue();
+        } else if ($field->isRepeated()) {
+            $getter = $field->getGetter();
+            $this->$getter()[] = $value;
+        } else {
+            $setter = $field->getSetter();
+            $this->$setter($value);
+        }
+
+        return true;
+    }
+
+    /**
+     * Parses a protocol buffer contained in a string.
+     *
+     * This function takes a string in the (non-human-readable) binary wire
+     * format, matching the encoding output by encode().
+     *
+     * @param string $data Binary protobuf data.
+     * @return bool Return true on success.
+     */
+    public function decode($data)
+    {
+        $input = new InputStream($data);
+        $this->parseFromStream($input);
+    }
+
+    /**
+     * @ignore
+     */
+    public function parseFromStream($input)
+    {
+        while (true) {
+            $tag = $input->readTag();
+            // End of input.  This is a valid place to end, so return true.
+            if ($tag === 0) {
+                return true;
+            }
+
+            $number = GPBWire::getTagFieldNumber($tag);
+            $field = $this->desc->getFieldByNumber($number);
+
+            if (!$this->parseFieldFromStream($tag, $input, $field)) {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @ignore
+     */
+    private function serializeSingularFieldToStream($field, &$output)
+    {
+        if (!$this->existField($field)) {
+            return true;
+        }
+        $getter = $field->getGetter();
+        $value = $this->$getter();
+        if (!GPBWire::serializeFieldToStream($value, $field, true, $output)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @ignore
+     */
+    private function serializeRepeatedFieldToStream($field, &$output)
+    {
+        $getter = $field->getGetter();
+        $values = $this->$getter();
+        $count = count($values);
+        if ($count === 0) {
+            return true;
+        }
+
+        $packed = $field->getPacked();
+        if ($packed) {
+            if (!GPBWire::writeTag(
+                $output,
+                GPBWire::makeTag($field->getNumber(), GPBType::STRING))) {
+                return false;
+            }
+            $size = 0;
+            foreach ($values as $value) {
+                $size += $this->fieldDataOnlyByteSize($field, $value);
+            }
+            if (!$output->writeVarint32($size)) {
+                return false;
+            }
+        }
+
+        foreach ($values as $value) {
+            if (!GPBWire::serializeFieldToStream(
+                $value,
+                $field,
+                !$packed,
+                $output)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @ignore
+     */
+    private function serializeMapFieldToStream($field, $output)
+    {
+        $getter = $field->getGetter();
+        $values = $this->$getter();
+        $count = count($values);
+        if ($count === 0) {
+            return true;
+        }
+
+        foreach ($values as $key => $value) {
+            $map_entry = new MapEntry($field->getMessageType());
+            $map_entry->setKey($key);
+            $map_entry->setValue($value);
+            if (!GPBWire::serializeFieldToStream(
+                $map_entry,
+                $field,
+                true,
+                $output)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @ignore
+     */
+    private function serializeFieldToStream(&$output, $field)
+    {
+        if ($field->isMap()) {
+            return $this->serializeMapFieldToStream($field, $output);
+        } elseif ($field->isRepeated()) {
+            return $this->serializeRepeatedFieldToStream($field, $output);
+        } else {
+            return $this->serializeSingularFieldToStream($field, $output);
+        }
+    }
+
+    /**
+     * @ignore
+     */
+    public function serializeToStream(&$output)
+    {
+        $fields = $this->desc->getField();
+        foreach ($fields as $field) {
+            if (!$this->serializeFieldToStream($output, $field)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Serialize the message to string.
+     * @return string Serialized binary protobuf data.
+     */
+    public function encode()
+    {
+        $output = new OutputStream($this->byteSize());
+        $this->serializeToStream($output);
+        return $output->getData();
+    }
+
+    /**
+     * @ignore
+     */
+    private function existField($field)
+    {
+        $getter = $field->getGetter();
+        $value = $this->$getter();
+        return $value !== $this->defaultValue($field);
+    }
+
+    /**
+     * @ignore
+     */
+    private function repeatedFieldDataOnlyByteSize($field)
+    {
+        $size = 0;
+
+        $getter = $field->getGetter();
+        $values = $this->$getter();
+        $count = count($values);
+        if ($count !== 0) {
+            $size += $count * GPBWire::tagSize($field);
+            foreach ($values as $value) {
+                $size += $this->singularFieldDataOnlyByteSize($field);
+            }
+        }
+    }
+
+    /**
+     * @ignore
+     */
+    private function fieldDataOnlyByteSize($field, $value)
+    {
+        $size = 0;
+
+        switch ($field->getType()) {
+            case GPBType::BOOL:
+                $size += 1;
+                break;
+            case GPBType::FLOAT:
+            case GPBType::FIXED32:
+            case GPBType::SFIXED32:
+                $size += 4;
+                break;
+            case GPBType::DOUBLE:
+            case GPBType::FIXED64:
+            case GPBType::SFIXED64:
+                $size += 8;
+                break;
+            case GPBType::UINT32:
+            case GPBType::INT32:
+            case GPBType::ENUM:
+                $size += GPBWire::varint32Size($value);
+                break;
+            case GPBType::UINT64:
+            case GPBType::INT64:
+                $size += GPBWire::varint64Size($value);
+                break;
+            case GPBType::SINT32:
+                $size += GPBWire::sint32Size($value);
+                break;
+            case GPBType::SINT64:
+                $size += GPBWire::sint64Size($value);
+                break;
+            case GPBType::STRING:
+            case GPBType::BYTES:
+                $size += strlen($value);
+                $size += GPBWire::varint32Size($size);
+                break;
+            case GPBType::MESSAGE:
+                $size += $value->byteSize();
+                $size += GPBWire::varint32Size($size);
+                break;
+            case GPBType::GROUP:
+                // TODO(teboring): Add support.
+                user_error("Unsupported type.");
+                break;
+            default:
+                user_error("Unsupported type.");
+                return 0;
+        }
+
+        return $size;
+    }
+
+    /**
+     * @ignore
+     */
+    private function fieldByteSize($field)
+    {
+        $size = 0;
+        if ($field->isMap()) {
+            $getter = $field->getGetter();
+            $values = $this->$getter();
+            $count = count($values);
+            if ($count !== 0) {
+                $size += $count * GPBWire::tagSize($field);
+                $message_type = $field->getMessageType();
+                $key_field = $message_type->getFieldByNumber(1);
+                $value_field = $message_type->getFieldByNumber(2);
+                foreach ($values as $key => $value) {
+                    $data_size = 0;
+                    $data_size += $this->fieldDataOnlyByteSize($key_field, $key);
+                    $data_size += $this->fieldDataOnlyByteSize(
+                        $value_field,
+                        $value);
+                    $data_size += GPBWire::tagSize($key_field);
+                    $data_size += GPBWire::tagSize($value_field);
+                    $size += GPBWire::varint32Size($data_size) + $data_size;
+                }
+            }
+        } elseif ($field->isRepeated()) {
+            $getter = $field->getGetter();
+            $values = $this->$getter();
+            $count = count($values);
+            if ($count !== 0) {
+                if ($field->getPacked()) {
+                    $data_size = 0;
+                    foreach ($values as $value) {
+                        $data_size += $this->fieldDataOnlyByteSize($field, $value);
+                    }
+                    $size += GPBWire::tagSize($field);
+                    $size += GPBWire::varint32Size($data_size);
+                    $size += $data_size;
+                } else {
+                    $size += $count * GPBWire::tagSize($field);
+                    foreach ($values as $value) {
+                        $size += $this->fieldDataOnlyByteSize($field, $value);
+                    }
+                }
+            }
+        } elseif ($this->existField($field)) {
+            $size += GPBWire::tagSize($field);
+            $getter = $field->getGetter();
+            $value = $this->$getter();
+            $size += $this->fieldDataOnlyByteSize($field, $value);
+        }
+        return $size;
+    }
+
+    /**
+     * @ignore
+     */
+    public function byteSize()
+    {
+        $size = 0;
+
+        $fields = $this->desc->getField();
+        foreach ($fields as $field) {
+            $size += $this->fieldByteSize($field);
+        }
+        return $size;
+    }
+}

+ 120 - 0
php/src/Google/Protobuf/Internal/MessageBuilderContext.php

@@ -0,0 +1,120 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\GPBLabel;
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\Descriptor;
+use Google\Protobuf\Internal\FieldDescriptor;
+
+class MessageBuilderContext
+{
+
+    private $descriptor;
+    private $pool;
+
+    public function __construct($full_name, $klass, $pool)
+    {
+        $this->descriptor = new Descriptor();
+        $this->descriptor->setFullName($full_name);
+        $this->descriptor->setClass($klass);
+        $this->pool = $pool;
+    }
+
+    private function getFieldDescriptor($name, $label, $type,
+                                      $number, $type_name = null)
+    {
+        $field = new FieldDescriptor();
+        $field->setName($name);
+        $camel_name = implode('', array_map('ucwords', explode('_', $name)));
+        $field->setGetter('get' . $camel_name);
+        $field->setSetter('set' . $camel_name);
+        $field->setType($type);
+        $field->setNumber($number);
+        $field->setLabel($label);
+
+        // At this time, the message/enum type may have not been added to pool.
+        // So we use the type name as place holder and will replace it with the
+        // actual descriptor in cross building.
+        switch ($type) {
+        case GPBType::MESSAGE:
+          $field->setMessageType($type_name);
+          break;
+        case GPBType::ENUM:
+          $field->setEnumType($type_name);
+          break;
+        default:
+          break;
+        }
+
+        return $field;
+    }
+
+    public function optional($name, $type, $number, $type_name = null)
+    {
+        $this->descriptor->addField($this->getFieldDescriptor(
+            $name,
+            GPBLabel::OPTIONAL,
+            $type,
+            $number,
+            $type_name));
+        return $this;
+    }
+
+    public function repeated($name, $type, $number, $type_name = null)
+    {
+        $this->descriptor->addField($this->getFieldDescriptor(
+            $name,
+            GPBLabel::REPEATED,
+            $type,
+            $number,
+            $type_name));
+        return $this;
+    }
+
+    public function required($name, $type, $number, $type_name = null)
+    {
+        $this->descriptor->addField($this->getFieldDescriptor(
+            $name,
+            GPBLabel::REQUIRED,
+            $type,
+            $number,
+            $type_name));
+        return $this;
+    }
+
+    public function finalizeToPool()
+    {
+        $this->pool->addDescriptor($this->descriptor);
+    }
+}

+ 77 - 0
php/src/Google/Protobuf/Internal/OneofField.php

@@ -0,0 +1,77 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+class OneofField
+{
+
+    private $desc;
+    private $field_name;
+    private $number = 0;
+    private $value;
+
+    public function __construct($desc)
+    {
+        $this->desc = $desc;
+    }
+
+    public function setValue($value)
+    {
+        $this->value = $value;
+    }
+
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    public function setFieldName($field_name)
+    {
+        $this->field_name = $field_name;
+    }
+
+    public function getFieldName()
+    {
+        return $this->field_name;
+    }
+
+    public function setNumber($number)
+    {
+        $this->number = $number;
+    }
+
+    public function getNumber()
+    {
+        return $this->number;
+    }
+}

+ 143 - 0
php/src/Google/Protobuf/Internal/OutputStream.php

@@ -0,0 +1,143 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+class OutputStream
+{
+
+    private $buffer;
+    private $buffer_size;
+    private $current;
+
+    const MAX_VARINT32_BYTES = 5;
+    const MAX_VARINT64_BYTES = 10;
+
+    public function __construct($size)
+    {
+        $this->current = 0;
+        $this->buffer_size = $size;
+        $this->buffer = str_repeat(chr(0), $this->buffer_size);
+    }
+
+    public function getData()
+    {
+        return $this->buffer;
+    }
+
+    public function writeVarint32($value)
+    {
+        $bytes = str_repeat(chr(0), self::MAX_VARINT32_BYTES);
+        $size = self::writeVarintToArray($value, $bytes, true);
+        return $this->writeRaw($bytes, $size);
+    }
+
+    public function writeVarint64($value)
+    {
+        $bytes = str_repeat(chr(0), self::MAX_VARINT64_BYTES);
+        $size = self::writeVarintToArray($value, $bytes);
+        return $this->writeRaw($bytes, $size);
+    }
+
+    public function writeLittleEndian32($value)
+    {
+        $bytes = str_repeat(chr(0), 4);
+        $size = self::writeLittleEndian32ToArray($value, $bytes);
+        return $this->writeRaw($bytes, $size);
+    }
+
+    public function writeLittleEndian64($value)
+    {
+        $bytes = str_repeat(chr(0), 8);
+        $size = self::writeLittleEndian64ToArray($value, $bytes);
+        return $this->writeRaw($bytes, $size);
+    }
+
+    public function writeTag($tag)
+    {
+        return $this->writeVarint32($tag);
+    }
+
+    public function writeRaw($data, $size)
+    {
+        if ($this->buffer_size < $size) {
+            var_dump($this->buffer_size);
+            var_dump($size);
+            trigger_error("Output stream doesn't have enough buffer.");
+            return false;
+        }
+
+        for ($i = 0; $i < $size; $i++) {
+            $this->buffer[$this->current] = $data[$i];
+            $this->current++;
+            $this->buffer_size--;
+        }
+        return true;
+    }
+
+    private static function writeVarintToArray($value, &$buffer, $trim = false)
+    {
+        $current = 0;
+        if ($trim) {
+            $value &= 0xFFFFFFFF;
+        }
+        while ($value >= 0x80 || $value < 0) {
+            $buffer[$current] = chr($value | 0x80);
+            $value = ($value >> 7) & ~(0x7F << ((PHP_INT_SIZE << 3) - 7));
+            $current++;
+        }
+        $buffer[$current] = chr($value);
+        return $current + 1;
+    }
+
+    private static function writeLittleEndian32ToArray($value, &$buffer)
+    {
+        $buffer[0] = chr($value & 0x000000FF);
+        $buffer[1] = chr(($value >> 8) & 0x000000FF);
+        $buffer[2] = chr(($value >> 16) & 0x000000FF);
+        $buffer[3] = chr(($value >> 24) & 0x000000FF);
+        return 4;
+    }
+
+    private static function writeLittleEndian64ToArray($value, &$buffer)
+    {
+        $buffer[0] = chr($value & 0x000000FF);
+        $buffer[1] = chr(($value >> 8) & 0x000000FF);
+        $buffer[2] = chr(($value >> 16) & 0x000000FF);
+        $buffer[3] = chr(($value >> 24) & 0x000000FF);
+        $buffer[4] = chr(($value >> 32) & 0x000000FF);
+        $buffer[5] = chr(($value >> 40) & 0x000000FF);
+        $buffer[6] = chr(($value >> 48) & 0x000000FF);
+        $buffer[7] = chr(($value >> 56) & 0x000000FF);
+        return 8;
+    }
+}

+ 303 - 0
php/src/Google/Protobuf/Internal/RepeatedField.php

@@ -0,0 +1,303 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * RepeatedField and RepeatedFieldIter are used by generated protocol message
+ * classes to manipulate repeated fields.
+ */
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\GPBUtil;
+
+/**
+ * RepeatedFieldIter is used to iterate RepeatedField. It is also need for the
+ * foreach syntax.
+ */
+class RepeatedFieldIter implements \Iterator
+{
+
+    /**
+     * @ignore
+     */
+    private $position;
+    /**
+     * @ignore
+     */
+    private $container;
+
+    /**
+     * Create iterator instance for RepeatedField.
+     *
+     * @param RepeatedField The RepeatedField instance for which this iterator
+     * is created.
+     * @ignore
+     */
+    public function __construct($container)
+    {
+        $this->position = 0;
+        $this->container = $container;
+    }
+
+    /**
+     * Reset the status of the iterator
+     *
+     * @return void
+     */
+    public function rewind()
+    {
+        $this->position = 0;
+    }
+
+    /**
+     * Return the element at the current position.
+     *
+     * @return object The element at the current position.
+     */
+    public function current()
+    {
+        return $this->container[$this->position];
+    }
+
+    /**
+     * Return the current position.
+     *
+     * @return integer The current position.
+     */
+    public function key()
+    {
+        return $this->position;
+    }
+
+    /**
+     * Move to the next position.
+     *
+     * @return void
+     */
+    public function next()
+    {
+        ++$this->position;
+    }
+
+    /**
+     * Check whether there are more elements to iterate.
+     *
+     * @return bool True if there are more elements to iterate.
+     */
+    public function valid()
+    {
+        return isset($this->container[$this->position]);
+    }
+}
+
+/**
+ * RepeatedField is used by generated protocol message classes to manipulate
+ * repeated fields. It can be used like native PHP array.
+ */
+class RepeatedField implements \ArrayAccess, \IteratorAggregate, \Countable
+{
+
+    /**
+     * @ignore
+     */
+    private $container;
+    /**
+     * @ignore
+     */
+    private $type;
+    /**
+     * @ignore
+     */
+    private $klass;
+
+    /**
+     * Constructs an instance of RepeatedField.
+     *
+     * @param long $type Type of the stored element.
+     * @param string $klass Message/Enum class name (message/enum fields only).
+     * @ignore
+     */
+    public function __construct($type, $klass = null)
+    {
+        $this->container = [];
+        $this->type = $type;
+        $this->klass = $klass;
+    }
+
+    /**
+     * @ignore
+     */
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    /**
+     * @ignore
+     */
+    public function getClass()
+    {
+        return $this->klass;
+    }
+
+    /**
+     * Return the element at the given index.
+     *
+     * This will also be called for: $ele = $arr[0]
+     *
+     * @param long $offset The index of the element to be fetched.
+     * @return object The stored element at given index.
+     * @throws ErrorException Invalid type for index.
+     * @throws ErrorException Non-existing index.
+     */
+    public function offsetGet($offset)
+    {
+        return $this->container[$offset];
+    }
+
+    /**
+     * Assign the element at the given index.
+     *
+     * This will also be called for: $arr []= $ele and $arr[0] = ele
+     *
+     * @param long $offset The index of the element to be assigned.
+     * @param object $value The element to be assigned.
+     * @return void
+     * @throws ErrorException Invalid type for index.
+     * @throws ErrorException Non-existing index.
+     * @throws ErrorException Incorrect type of the element.
+     */
+    public function offsetSet($offset, $value)
+    {
+        switch ($this->type) {
+            case GPBType::INT32:
+                GPBUtil::checkInt32($value);
+                break;
+            case GPBType::UINT32:
+                GPBUtil::checkUint32($value);
+                break;
+            case GPBType::INT64:
+                GPBUtil::checkInt64($value);
+                break;
+            case GPBType::UINT64:
+                GPBUtil::checkUint64($value);
+                break;
+            case GPBType::FLOAT:
+                GPBUtil::checkFloat($value);
+                break;
+            case GPBType::DOUBLE:
+                GPBUtil::checkDouble($value);
+                break;
+            case GPBType::BOOL:
+                GPBUtil::checkBool($value);
+                break;
+            case GPBType::STRING:
+                GPBUtil::checkString($value, true);
+                break;
+            case GPBType::MESSAGE:
+                GPBUtil::checkMessage($value, $this->klass);
+                break;
+            default:
+                break;
+        }
+        if (is_null($offset)) {
+            $this->container[] = $value;
+        } else {
+            $count = count($this->container);
+            if (!is_numeric($offset) || $offset < 0 || $offset >= $count) {
+                trigger_error(
+                    "Cannot modify element at the given index",
+                    E_USER_ERROR);
+                return;
+            }
+            $this->container[$offset] = $value;
+        }
+    }
+
+    /**
+     * Remove the element at the given index.
+     *
+     * This will also be called for: unset($arr)
+     *
+     * @param long $offset The index of the element to be removed.
+     * @return void
+     * @throws ErrorException Invalid type for index.
+     * @throws ErrorException The element to be removed is not at the end of the
+     * RepeatedField.
+     */
+    public function offsetUnset($offset)
+    {
+        $count = count($this->container);
+        if (!is_numeric($offset) || $count === 0 || $offset !== $count - 1) {
+            trigger_error(
+                "Cannot remove element at the given index",
+                E_USER_ERROR);
+            return;
+        }
+        array_pop($this->container);
+    }
+
+    /**
+     * Check the existence of the element at the given index.
+     *
+     * This will also be called for: isset($arr)
+     *
+     * @param long $offset The index of the element to be removed.
+     * @return bool True if the element at the given offset exists.
+     * @throws ErrorException Invalid type for index.
+     */
+    public function offsetExists($offset)
+    {
+        return isset($this->container[$offset]);
+    }
+
+    /**
+     * @ignore
+     */
+    public function getIterator()
+    {
+        return new RepeatedFieldIter($this->container);
+    }
+
+    /**
+     * Return the number of stored elements.
+     *
+     * This will also be called for: count($arr)
+     *
+     * @return integer The number of stored elements.
+     */
+    public function count()
+    {
+        return count($this->container);
+    }
+}

+ 175 - 0
php/src/Google/Protobuf/Internal/Type.php

@@ -0,0 +1,175 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+class GPBInteger
+{
+    public $high = 0;
+    public $low = 0;
+
+    public function __construct($value = 0)
+    {
+        $this->low = $value & 0xFFFFFFFF;
+        if (PHP_INT_SIZE === 8) {
+            $this->high = ($value >> 32) & 0xFFFFFFFF;
+        }
+    }
+
+    // Return 0 for unsigned integers and 1 for signed integers.
+    protected function sign()
+    {
+        trigger_error("Not implemented", E_ERROR);
+    }
+
+    public function leftShift($count)
+    {
+        if ($count > 63) {
+            $this->low = 0;
+            $this->high = 0;
+            return;
+        }
+        if ($count > 32) {
+            $this->high = $this->low;
+            $this->low = 0;
+            $count -= 32;
+        }
+        $mask = (1 << $count) - 1;
+        $this->high = (($this->high << $count) & 0xFFFFFFFF) |
+                  (($this->low >> (32 - $count)) & $mask);
+        $this->low = ($this->low << $count) & 0xFFFFFFFF;
+
+        $this->high &= 0xFFFFFFFF;
+        $this->low &= 0xFFFFFFFF;
+        return $this;
+    }
+
+    public function rightShift($count)
+    {
+        $sign = (($this->high & 0x80000000) >> 31) & $this->sign();
+        if ($count > 63) {
+            $this->low = -$sign;
+            $this->high = -$sign;
+            return;
+        }
+        if ($count > 32) {
+            $this->low = $this->high;
+            $this->high = -$sign;
+            $count -= 32;
+        }
+        $this->low = (($this->low >> $count) & 0xFFFFFFFF) |
+                 (($this->high << (32 - $count)) & 0xFFFFFFFF);
+        $this->high = (($this->high >> $count) | (-$sign << $count));
+
+        $this->high &= 0xFFFFFFFF;
+        $this->low &= 0xFFFFFFFF;
+
+        return $this;
+    }
+
+    public function bitOr($var)
+    {
+        $this->high |= $var->high;
+        $this->low |= $var->low;
+        return $this;
+    }
+
+    public function bitXor($var)
+    {
+        $this->high ^= $var->high;
+        $this->low ^= $var->low;
+        return $this;
+    }
+
+    public function bitAnd($var)
+    {
+        $this->high &= $var->high;
+        $this->low &= $var->low;
+        return $this;
+    }
+
+    // Even: all zero; Odd: all one.
+    public function oddMask()
+    {
+        $low = (-($this->low & 1)) & 0xFFFFFFFF;
+        $high = $low;
+        return UInt64::newValue($high, $low);
+    }
+
+    public function toInteger()
+    {
+        if (PHP_INT_SIZE === 8) {
+            return ($this->high << 32) | $this->low;
+        } else {
+            return $this->low;
+        }
+    }
+
+    public function copy()
+    {
+        return static::newValue($this->high, $this->low);
+    }
+}
+
+class Uint64 extends GPBInteger
+{
+
+    public static function newValue($high, $low)
+    {
+        $uint64 = new Uint64(0);
+        $uint64->high = $high;
+        $uint64->low = $low;
+        return $uint64;
+    }
+
+    protected function sign()
+    {
+        return 0;
+    }
+}
+
+class Int64 extends GPBInteger
+{
+
+    public static function newValue($high, $low)
+    {
+        $int64 = new Int64(0);
+        $int64->high = $high;
+        $int64->low = $low;
+        return $int64;
+    }
+
+    protected function sign()
+    {
+        return 1;
+    }
+}

+ 541 - 0
php/src/Google/Protobuf/descriptor.php

@@ -0,0 +1,541 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\MessageOptions;
+
+class FileDescriptor
+{
+
+    private $package;
+    private $message_type = [];
+    private $enum_type = [];
+
+    public function setPackage($package)
+    {
+        $this->package = $package;
+    }
+
+    public function getPackage()
+    {
+        return $this->package;
+    }
+
+    public function getMessageType()
+    {
+        return $this->message_type;
+    }
+
+    public function addMessageType($desc)
+    {
+        $this->message_type[] = $desc;
+    }
+
+    public function getEnumType()
+    {
+        return $this->enum_type;
+    }
+
+    public function addEnumType($desc)
+    {
+        $this->enum_type[]= $desc;
+    }
+
+    public static function buildFromProto($proto)
+    {
+        $file = new FileDescriptor();
+        $file->setPackage($proto->getPackage());
+        foreach ($proto->getMessageType() as $message_proto) {
+            $file->addMessageType(Descriptor::buildFromProto(
+                $message_proto, $file->getPackage(), ""));
+        }
+        foreach ($proto->getEnumType() as $enum_proto) {
+            $file->getEnumType()[] =
+                $file->addEnumType(
+                    EnumDescriptor::buildFromProto(
+                        $enum_proto,
+                        $file->getPackage(),
+                        ""));
+        }
+        return $file;
+    }
+}
+
+class Descriptor
+{
+
+    private $full_name;
+    private $field = [];
+    private $nested_type = [];
+    private $enum_type = [];
+    private $klass;
+    private $options;
+    private $oneof_decl = [];
+
+    public function addOneofDecl($oneof)
+    {
+        $this->oneof_decl[] = $oneof;
+    }
+
+    public function getOneofDecl()
+    {
+        return $this->oneof_decl;
+    }
+
+    public function setFullName($full_name)
+    {
+        $this->full_name = $full_name;
+    }
+
+    public function getFullName()
+    {
+        return $this->full_name;
+    }
+
+    public function addField($field)
+    {
+        $this->field[$field->getNumber()] = $field;
+    }
+
+    public function getField()
+    {
+        return $this->field;
+    }
+
+    public function addNestedType($desc)
+    {
+        $this->nested_type[] = $desc;
+    }
+
+    public function getNestedType()
+    {
+        return $this->nested_type;
+    }
+
+    public function addEnumType($desc)
+    {
+        $this->enum_type[] = $desc;
+    }
+
+    public function getEnumType()
+    {
+        return $this->enum_type;
+    }
+
+    public function getFieldByNumber($number)
+    {
+        return $this->field[$number];
+    }
+
+    public function setClass($klass)
+    {
+        $this->klass = $klass;
+    }
+
+    public function getClass()
+    {
+        return $this->klass;
+    }
+
+    public function setOptions($options)
+    {
+        $this->options = $options;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public static function buildFromProto($proto, $package, $containing)
+    {
+        $desc = new Descriptor();
+
+        $message_name_without_package  = "";
+        $classname = "";
+        $fullname = "";
+        getFullClassName(
+            $proto,
+            $containing,
+            $package,
+            $message_name_without_package,
+            $classname,
+            $fullname);
+        $desc->setFullName($fullname);
+        $desc->setClass($classname);
+        $desc->setOptions($proto->getOptions());
+
+        foreach ($proto->getField() as $field_proto) {
+            $desc->addField(FieldDescriptor::buildFromProto($field_proto));
+        }
+
+        // Handle nested types.
+        foreach ($proto->getNestedType() as $nested_proto) {
+            $desc->addNestedType(Descriptor::buildFromProto(
+              $nested_proto, $package, $message_name_without_package));
+        }
+
+        // Handle oneof fields.
+        foreach ($proto->getOneofDecl() as $oneof_proto) {
+            $desc->addOneofDecl(
+                OneofDescriptor::buildFromProto($oneof_proto, $desc));
+        }
+
+        return $desc;
+    }
+}
+function getFullClassName(
+    $proto,
+    $containing,
+    $package,
+    &$message_name_without_package,
+    &$classname,
+    &$fullname)
+{
+    // Full name needs to start with '.'.
+    $message_name_without_package = $proto->getName();
+    if ($containing !== "") {
+        $message_name_without_package =
+            $containing . "." . $message_name_without_package;
+    }
+    if ($package === "") {
+        $fullname = "." . $message_name_without_package;
+    } else {
+        $fullname = "." . $package . "." . $message_name_without_package;
+    }
+
+    // Nested message class names are seperated by '_', and package names are
+    // seperated by '\'.
+    $class_name_without_package =
+        implode('_', array_map('ucwords',
+                               explode('.', $message_name_without_package)));
+    $classname =
+        implode('\\', array_map('ucwords', explode('.', $package))).
+        "\\".$class_name_without_package;
+}
+
+class OneofDescriptor
+{
+
+    private $name;
+    private $fields;
+
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function addField(&$field)
+    {
+        $this->fields[] = $field;
+    }
+
+    public function getFields()
+    {
+        return $this->fields;
+    }
+
+    public static function buildFromProto($oneof_proto)
+    {
+        $oneof = new OneofDescriptor();
+        $oneof->setName($oneof_proto->getName());
+        return $oneof;
+    }
+}
+
+
+class EnumDescriptor
+{
+
+    private $klass;
+    private $full_name;
+    private $value;
+
+    public function setFullName($full_name)
+    {
+        $this->full_name = $full_name;
+    }
+
+    public function getFullName()
+    {
+        return $this->full_name;
+    }
+
+    public function addValue($number, $value)
+    {
+        $this->value[$number] = $value;
+    }
+
+    public function setClass($klass)
+    {
+        $this->klass = $klass;
+    }
+
+    public function getClass()
+    {
+        return $this->klass;
+    }
+
+    public static function buildFromProto($proto, $package, $containing)
+    {
+        $desc = new EnumDescriptor();
+
+        $enum_name_without_package  = "";
+        $classname = "";
+        $fullname = "";
+        getFullClassName(
+            $proto,
+            $containing,
+            $package,
+            $enum_name_without_package,
+            $classname,
+            $fullname);
+        $desc->setFullName($fullname);
+        $desc->setClass($classname);
+
+        return $desc;
+    }
+}
+
+class EnumValueDescriptor
+{
+}
+
+class FieldDescriptor
+{
+
+    private $name;
+    private $setter;
+    private $getter;
+    private $number;
+    private $label;
+    private $type;
+    private $message_type;
+    private $enum_type;
+    private $packed;
+    private $is_map;
+    private $oneof_index = -1;
+
+    public function setOneofIndex($index)
+    {
+        $this->oneof_index = $index;
+    }
+
+    public function getOneofIndex()
+    {
+        return $this->oneof_index;
+    }
+
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setSetter($setter)
+    {
+        $this->setter = $setter;
+    }
+
+    public function getSetter()
+    {
+        return $this->setter;
+    }
+
+    public function setGetter($getter)
+    {
+        $this->getter = $getter;
+    }
+
+    public function getGetter()
+    {
+        return $this->getter;
+    }
+
+    public function setNumber($number)
+    {
+        $this->number = $number;
+    }
+
+    public function getNumber()
+    {
+        return $this->number;
+    }
+
+    public function setLabel($label)
+    {
+        $this->label = $label;
+    }
+
+    public function getLabel()
+    {
+        return $this->label;
+    }
+
+    public function isRepeated()
+    {
+        return $this->label === GPBLabel::REPEATED;
+    }
+
+    public function setType($type)
+    {
+        $this->type = $type;
+    }
+
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    public function setMessageType($message_type)
+    {
+        $this->message_type = $message_type;
+    }
+
+    public function getMessageType()
+    {
+        return $this->message_type;
+    }
+
+    public function setEnumType($enum_type)
+    {
+        $this->enum_type = $enum_type;
+    }
+
+    public function getEnumType()
+    {
+        return $this->enum_type;
+    }
+
+    public function setPacked($packed)
+    {
+        $this->packed = $packed;
+    }
+
+    public function getPacked()
+    {
+        return $this->packed;
+    }
+
+    public function isPackable()
+    {
+        return $this->isRepeated() && self::isTypePackable($this->type);
+    }
+
+    public function isMap()
+    {
+        return $this->getType() == GPBType::MESSAGE &&
+               !is_null($this->getMessageType()->getOptions()) &&
+               $this->getMessageType()->getOptions()->getMapEntry();
+    }
+
+    private static function isTypePackable($field_type)
+    {
+        return ($field_type !== GPBType::STRING  &&
+            $field_type !== GPBType::GROUP   &&
+            $field_type !== GPBType::MESSAGE &&
+            $field_type !== GPBType::BYTES);
+    }
+
+    public static function getFieldDescriptor(
+        $name,
+        $label,
+        $type,
+        $number,
+        $oneof_index,
+        $packed,
+        $type_name = null)
+    {
+        $field = new FieldDescriptor();
+        $field->setName($name);
+        $camel_name = implode('', array_map('ucwords', explode('_', $name)));
+        $field->setGetter('get' . $camel_name);
+        $field->setSetter('set' . $camel_name);
+        $field->setType($type);
+        $field->setNumber($number);
+        $field->setLabel($label);
+        $field->setPacked($packed);
+        $field->setOneofIndex($oneof_index);
+
+        // At this time, the message/enum type may have not been added to pool.
+        // So we use the type name as place holder and will replace it with the
+        // actual descriptor in cross building.
+        switch ($type) {
+            case GPBType::MESSAGE:
+                $field->setMessageType($type_name);
+                break;
+            case GPBType::ENUM:
+                $field->setEnumType($type_name);
+                break;
+            default:
+                break;
+        }
+
+        return $field;
+    }
+
+    public static function buildFromProto($proto)
+    {
+        $type_name = null;
+        switch ($proto->getType()) {
+            case GPBType::MESSAGE:
+            case GPBType::GROUP:
+            case GPBType::ENUM:
+                $type_name = $proto->getTypeName();
+                break;
+            default:
+                break;
+        }
+
+        $oneof_index = $proto->hasOneofIndex() ? $proto->getOneofIndex() : -1;
+        $packed = false;
+        $options = $proto->getOptions();
+        if ($options !== null) {
+            $packed = $options->getPacked();
+        }
+
+        return FieldDescriptor::getFieldDescriptor(
+            $proto->getName(), $proto->getLabel(), $proto->getType(),
+            $proto->getNumber(), $oneof_index, $packed, $type_name);
+    }
+}

+ 2532 - 0
php/src/Google/Protobuf/descriptor_internal.pb.php

@@ -0,0 +1,2532 @@
+<?php
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: google/protobuf/descriptor.proto
+
+namespace Google\Protobuf\Internal;
+
+use Google\Protobuf\Internal\DescriptorPool;
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\GPBWire;
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\InputStream;
+
+use Google\Protobuf\Internal\GPBUtil;
+
+class FileDescriptorSet extends \Google\Protobuf\Internal\Message
+{
+    private $file;
+    private $has_file = false;
+
+    public function getFile()
+    {
+        return $this->file;
+    }
+
+    public function setFile(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\FileDescriptorProto::class);
+        $this->file = $var;
+        $this->has_file = true;
+    }
+
+    public function hasFile()
+    {
+        return $this->has_file;
+    }
+
+}
+
+class FileDescriptorProto extends \Google\Protobuf\Internal\Message
+{
+    private $name = '';
+    private $has_name = false;
+    private $package = '';
+    private $has_package = false;
+    private $dependency;
+    private $has_dependency = false;
+    private $public_dependency;
+    private $has_public_dependency = false;
+    private $weak_dependency;
+    private $has_weak_dependency = false;
+    private $message_type;
+    private $has_message_type = false;
+    private $enum_type;
+    private $has_enum_type = false;
+    private $service;
+    private $has_service = false;
+    private $extension;
+    private $has_extension = false;
+    private $options = null;
+    private $has_options = false;
+    private $source_code_info = null;
+    private $has_source_code_info = false;
+    private $syntax = '';
+    private $has_syntax = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getPackage()
+    {
+        return $this->package;
+    }
+
+    public function setPackage($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->package = $var;
+        $this->has_package = true;
+    }
+
+    public function hasPackage()
+    {
+        return $this->has_package;
+    }
+
+    public function getDependency()
+    {
+        return $this->dependency;
+    }
+
+    public function setDependency(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::STRING);
+        $this->dependency = $var;
+        $this->has_dependency = true;
+    }
+
+    public function hasDependency()
+    {
+        return $this->has_dependency;
+    }
+
+    public function getPublicDependency()
+    {
+        return $this->public_dependency;
+    }
+
+    public function setPublicDependency(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT32);
+        $this->public_dependency = $var;
+        $this->has_public_dependency = true;
+    }
+
+    public function hasPublicDependency()
+    {
+        return $this->has_public_dependency;
+    }
+
+    public function getWeakDependency()
+    {
+        return $this->weak_dependency;
+    }
+
+    public function setWeakDependency(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT32);
+        $this->weak_dependency = $var;
+        $this->has_weak_dependency = true;
+    }
+
+    public function hasWeakDependency()
+    {
+        return $this->has_weak_dependency;
+    }
+
+    public function getMessageType()
+    {
+        return $this->message_type;
+    }
+
+    public function setMessageType(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\DescriptorProto::class);
+        $this->message_type = $var;
+        $this->has_message_type = true;
+    }
+
+    public function hasMessageType()
+    {
+        return $this->has_message_type;
+    }
+
+    public function getEnumType()
+    {
+        return $this->enum_type;
+    }
+
+    public function setEnumType(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\EnumDescriptorProto::class);
+        $this->enum_type = $var;
+        $this->has_enum_type = true;
+    }
+
+    public function hasEnumType()
+    {
+        return $this->has_enum_type;
+    }
+
+    public function getService()
+    {
+        return $this->service;
+    }
+
+    public function setService(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\ServiceDescriptorProto::class);
+        $this->service = $var;
+        $this->has_service = true;
+    }
+
+    public function hasService()
+    {
+        return $this->has_service;
+    }
+
+    public function getExtension()
+    {
+        return $this->extension;
+    }
+
+    public function setExtension(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\FieldDescriptorProto::class);
+        $this->extension = $var;
+        $this->has_extension = true;
+    }
+
+    public function hasExtension()
+    {
+        return $this->has_extension;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public function setOptions(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\FileOptions::class);
+        $this->options = $var;
+        $this->has_options = true;
+    }
+
+    public function hasOptions()
+    {
+        return $this->has_options;
+    }
+
+    public function getSourceCodeInfo()
+    {
+        return $this->source_code_info;
+    }
+
+    public function setSourceCodeInfo(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\SourceCodeInfo::class);
+        $this->source_code_info = $var;
+        $this->has_source_code_info = true;
+    }
+
+    public function hasSourceCodeInfo()
+    {
+        return $this->has_source_code_info;
+    }
+
+    public function getSyntax()
+    {
+        return $this->syntax;
+    }
+
+    public function setSyntax($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->syntax = $var;
+        $this->has_syntax = true;
+    }
+
+    public function hasSyntax()
+    {
+        return $this->has_syntax;
+    }
+
+}
+
+class DescriptorProto extends \Google\Protobuf\Internal\Message
+{
+    private $name = '';
+    private $has_name = false;
+    private $field;
+    private $has_field = false;
+    private $extension;
+    private $has_extension = false;
+    private $nested_type;
+    private $has_nested_type = false;
+    private $enum_type;
+    private $has_enum_type = false;
+    private $extension_range;
+    private $has_extension_range = false;
+    private $oneof_decl;
+    private $has_oneof_decl = false;
+    private $options = null;
+    private $has_options = false;
+    private $reserved_range;
+    private $has_reserved_range = false;
+    private $reserved_name;
+    private $has_reserved_name = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getField()
+    {
+        return $this->field;
+    }
+
+    public function setField(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\FieldDescriptorProto::class);
+        $this->field = $var;
+        $this->has_field = true;
+    }
+
+    public function hasField()
+    {
+        return $this->has_field;
+    }
+
+    public function getExtension()
+    {
+        return $this->extension;
+    }
+
+    public function setExtension(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\FieldDescriptorProto::class);
+        $this->extension = $var;
+        $this->has_extension = true;
+    }
+
+    public function hasExtension()
+    {
+        return $this->has_extension;
+    }
+
+    public function getNestedType()
+    {
+        return $this->nested_type;
+    }
+
+    public function setNestedType(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\DescriptorProto::class);
+        $this->nested_type = $var;
+        $this->has_nested_type = true;
+    }
+
+    public function hasNestedType()
+    {
+        return $this->has_nested_type;
+    }
+
+    public function getEnumType()
+    {
+        return $this->enum_type;
+    }
+
+    public function setEnumType(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\EnumDescriptorProto::class);
+        $this->enum_type = $var;
+        $this->has_enum_type = true;
+    }
+
+    public function hasEnumType()
+    {
+        return $this->has_enum_type;
+    }
+
+    public function getExtensionRange()
+    {
+        return $this->extension_range;
+    }
+
+    public function setExtensionRange(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\DescriptorProto_ExtensionRange::class);
+        $this->extension_range = $var;
+        $this->has_extension_range = true;
+    }
+
+    public function hasExtensionRange()
+    {
+        return $this->has_extension_range;
+    }
+
+    public function getOneofDecl()
+    {
+        return $this->oneof_decl;
+    }
+
+    public function setOneofDecl(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\OneofDescriptorProto::class);
+        $this->oneof_decl = $var;
+        $this->has_oneof_decl = true;
+    }
+
+    public function hasOneofDecl()
+    {
+        return $this->has_oneof_decl;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public function setOptions(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\MessageOptions::class);
+        $this->options = $var;
+        $this->has_options = true;
+    }
+
+    public function hasOptions()
+    {
+        return $this->has_options;
+    }
+
+    public function getReservedRange()
+    {
+        return $this->reserved_range;
+    }
+
+    public function setReservedRange(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\DescriptorProto_ReservedRange::class);
+        $this->reserved_range = $var;
+        $this->has_reserved_range = true;
+    }
+
+    public function hasReservedRange()
+    {
+        return $this->has_reserved_range;
+    }
+
+    public function getReservedName()
+    {
+        return $this->reserved_name;
+    }
+
+    public function setReservedName(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::STRING);
+        $this->reserved_name = $var;
+        $this->has_reserved_name = true;
+    }
+
+    public function hasReservedName()
+    {
+        return $this->has_reserved_name;
+    }
+
+}
+
+class DescriptorProto_ExtensionRange extends \Google\Protobuf\Internal\Message
+{
+    private $start = 0;
+    private $has_start = false;
+    private $end = 0;
+    private $has_end = false;
+
+    public function getStart()
+    {
+        return $this->start;
+    }
+
+    public function setStart($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->start = $var;
+        $this->has_start = true;
+    }
+
+    public function hasStart()
+    {
+        return $this->has_start;
+    }
+
+    public function getEnd()
+    {
+        return $this->end;
+    }
+
+    public function setEnd($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->end = $var;
+        $this->has_end = true;
+    }
+
+    public function hasEnd()
+    {
+        return $this->has_end;
+    }
+
+}
+
+class DescriptorProto_ReservedRange extends \Google\Protobuf\Internal\Message
+{
+    private $start = 0;
+    private $has_start = false;
+    private $end = 0;
+    private $has_end = false;
+
+    public function getStart()
+    {
+        return $this->start;
+    }
+
+    public function setStart($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->start = $var;
+        $this->has_start = true;
+    }
+
+    public function hasStart()
+    {
+        return $this->has_start;
+    }
+
+    public function getEnd()
+    {
+        return $this->end;
+    }
+
+    public function setEnd($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->end = $var;
+        $this->has_end = true;
+    }
+
+    public function hasEnd()
+    {
+        return $this->has_end;
+    }
+
+}
+
+class FieldDescriptorProto extends \Google\Protobuf\Internal\Message
+{
+    private $name = '';
+    private $has_name = false;
+    private $number = 0;
+    private $has_number = false;
+    private $label = 0;
+    private $has_label = false;
+    private $type = 0;
+    private $has_type = false;
+    private $type_name = '';
+    private $has_type_name = false;
+    private $extendee = '';
+    private $has_extendee = false;
+    private $default_value = '';
+    private $has_default_value = false;
+    private $oneof_index = 0;
+    private $has_oneof_index = false;
+    private $json_name = '';
+    private $has_json_name = false;
+    private $options = null;
+    private $has_options = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getNumber()
+    {
+        return $this->number;
+    }
+
+    public function setNumber($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->number = $var;
+        $this->has_number = true;
+    }
+
+    public function hasNumber()
+    {
+        return $this->has_number;
+    }
+
+    public function getLabel()
+    {
+        return $this->label;
+    }
+
+    public function setLabel($var)
+    {
+        GPBUtil::checkEnum($var, \Google\Protobuf\Internal\FieldDescriptorProto_Label::class);
+        $this->label = $var;
+        $this->has_label = true;
+    }
+
+    public function hasLabel()
+    {
+        return $this->has_label;
+    }
+
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    public function setType($var)
+    {
+        GPBUtil::checkEnum($var, \Google\Protobuf\Internal\FieldDescriptorProto_Type::class);
+        $this->type = $var;
+        $this->has_type = true;
+    }
+
+    public function hasType()
+    {
+        return $this->has_type;
+    }
+
+    public function getTypeName()
+    {
+        return $this->type_name;
+    }
+
+    public function setTypeName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->type_name = $var;
+        $this->has_type_name = true;
+    }
+
+    public function hasTypeName()
+    {
+        return $this->has_type_name;
+    }
+
+    public function getExtendee()
+    {
+        return $this->extendee;
+    }
+
+    public function setExtendee($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->extendee = $var;
+        $this->has_extendee = true;
+    }
+
+    public function hasExtendee()
+    {
+        return $this->has_extendee;
+    }
+
+    public function getDefaultValue()
+    {
+        return $this->default_value;
+    }
+
+    public function setDefaultValue($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->default_value = $var;
+        $this->has_default_value = true;
+    }
+
+    public function hasDefaultValue()
+    {
+        return $this->has_default_value;
+    }
+
+    public function getOneofIndex()
+    {
+        return $this->oneof_index;
+    }
+
+    public function setOneofIndex($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->oneof_index = $var;
+        $this->has_oneof_index = true;
+    }
+
+    public function hasOneofIndex()
+    {
+        return $this->has_oneof_index;
+    }
+
+    public function getJsonName()
+    {
+        return $this->json_name;
+    }
+
+    public function setJsonName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->json_name = $var;
+        $this->has_json_name = true;
+    }
+
+    public function hasJsonName()
+    {
+        return $this->has_json_name;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public function setOptions(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\FieldOptions::class);
+        $this->options = $var;
+        $this->has_options = true;
+    }
+
+    public function hasOptions()
+    {
+        return $this->has_options;
+    }
+
+}
+
+class FieldDescriptorProto_Type
+{
+    const TYPE_DOUBLE = 1;
+    const TYPE_FLOAT = 2;
+    const TYPE_INT64 = 3;
+    const TYPE_UINT64 = 4;
+    const TYPE_INT32 = 5;
+    const TYPE_FIXED64 = 6;
+    const TYPE_FIXED32 = 7;
+    const TYPE_BOOL = 8;
+    const TYPE_STRING = 9;
+    const TYPE_GROUP = 10;
+    const TYPE_MESSAGE = 11;
+    const TYPE_BYTES = 12;
+    const TYPE_UINT32 = 13;
+    const TYPE_ENUM = 14;
+    const TYPE_SFIXED32 = 15;
+    const TYPE_SFIXED64 = 16;
+    const TYPE_SINT32 = 17;
+    const TYPE_SINT64 = 18;
+}
+
+class FieldDescriptorProto_Label
+{
+    const LABEL_OPTIONAL = 1;
+    const LABEL_REQUIRED = 2;
+    const LABEL_REPEATED = 3;
+}
+
+class OneofDescriptorProto extends \Google\Protobuf\Internal\Message
+{
+    private $name = '';
+    private $has_name = false;
+    private $options = null;
+    private $has_options = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public function setOptions(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\OneofOptions::class);
+        $this->options = $var;
+        $this->has_options = true;
+    }
+
+    public function hasOptions()
+    {
+        return $this->has_options;
+    }
+
+}
+
+class EnumDescriptorProto extends \Google\Protobuf\Internal\Message
+{
+    private $name = '';
+    private $has_name = false;
+    private $value;
+    private $has_value = false;
+    private $options = null;
+    private $has_options = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    public function setValue(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\EnumValueDescriptorProto::class);
+        $this->value = $var;
+        $this->has_value = true;
+    }
+
+    public function hasValue()
+    {
+        return $this->has_value;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public function setOptions(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\EnumOptions::class);
+        $this->options = $var;
+        $this->has_options = true;
+    }
+
+    public function hasOptions()
+    {
+        return $this->has_options;
+    }
+
+}
+
+class EnumValueDescriptorProto extends \Google\Protobuf\Internal\Message
+{
+    private $name = '';
+    private $has_name = false;
+    private $number = 0;
+    private $has_number = false;
+    private $options = null;
+    private $has_options = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getNumber()
+    {
+        return $this->number;
+    }
+
+    public function setNumber($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->number = $var;
+        $this->has_number = true;
+    }
+
+    public function hasNumber()
+    {
+        return $this->has_number;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public function setOptions(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\EnumValueOptions::class);
+        $this->options = $var;
+        $this->has_options = true;
+    }
+
+    public function hasOptions()
+    {
+        return $this->has_options;
+    }
+
+}
+
+class ServiceDescriptorProto extends \Google\Protobuf\Internal\Message
+{
+    private $name = '';
+    private $has_name = false;
+    private $method;
+    private $has_method = false;
+    private $options = null;
+    private $has_options = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getMethod()
+    {
+        return $this->method;
+    }
+
+    public function setMethod(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\MethodDescriptorProto::class);
+        $this->method = $var;
+        $this->has_method = true;
+    }
+
+    public function hasMethod()
+    {
+        return $this->has_method;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public function setOptions(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\ServiceOptions::class);
+        $this->options = $var;
+        $this->has_options = true;
+    }
+
+    public function hasOptions()
+    {
+        return $this->has_options;
+    }
+
+}
+
+class MethodDescriptorProto extends \Google\Protobuf\Internal\Message
+{
+    private $name = '';
+    private $has_name = false;
+    private $input_type = '';
+    private $has_input_type = false;
+    private $output_type = '';
+    private $has_output_type = false;
+    private $options = null;
+    private $has_options = false;
+    private $client_streaming = false;
+    private $has_client_streaming = false;
+    private $server_streaming = false;
+    private $has_server_streaming = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getInputType()
+    {
+        return $this->input_type;
+    }
+
+    public function setInputType($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->input_type = $var;
+        $this->has_input_type = true;
+    }
+
+    public function hasInputType()
+    {
+        return $this->has_input_type;
+    }
+
+    public function getOutputType()
+    {
+        return $this->output_type;
+    }
+
+    public function setOutputType($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->output_type = $var;
+        $this->has_output_type = true;
+    }
+
+    public function hasOutputType()
+    {
+        return $this->has_output_type;
+    }
+
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    public function setOptions(&$var)
+    {
+        GPBUtil::checkMessage($var, \Google\Protobuf\Internal\MethodOptions::class);
+        $this->options = $var;
+        $this->has_options = true;
+    }
+
+    public function hasOptions()
+    {
+        return $this->has_options;
+    }
+
+    public function getClientStreaming()
+    {
+        return $this->client_streaming;
+    }
+
+    public function setClientStreaming($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->client_streaming = $var;
+        $this->has_client_streaming = true;
+    }
+
+    public function hasClientStreaming()
+    {
+        return $this->has_client_streaming;
+    }
+
+    public function getServerStreaming()
+    {
+        return $this->server_streaming;
+    }
+
+    public function setServerStreaming($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->server_streaming = $var;
+        $this->has_server_streaming = true;
+    }
+
+    public function hasServerStreaming()
+    {
+        return $this->has_server_streaming;
+    }
+
+}
+
+class FileOptions extends \Google\Protobuf\Internal\Message
+{
+    private $java_package = '';
+    private $has_java_package = false;
+    private $java_outer_classname = '';
+    private $has_java_outer_classname = false;
+    private $java_multiple_files = false;
+    private $has_java_multiple_files = false;
+    private $java_generate_equals_and_hash = false;
+    private $has_java_generate_equals_and_hash = false;
+    private $java_string_check_utf8 = false;
+    private $has_java_string_check_utf8 = false;
+    private $optimize_for = 0;
+    private $has_optimize_for = false;
+    private $go_package = '';
+    private $has_go_package = false;
+    private $cc_generic_services = false;
+    private $has_cc_generic_services = false;
+    private $java_generic_services = false;
+    private $has_java_generic_services = false;
+    private $py_generic_services = false;
+    private $has_py_generic_services = false;
+    private $deprecated = false;
+    private $has_deprecated = false;
+    private $cc_enable_arenas = false;
+    private $has_cc_enable_arenas = false;
+    private $objc_class_prefix = '';
+    private $has_objc_class_prefix = false;
+    private $csharp_namespace = '';
+    private $has_csharp_namespace = false;
+    private $uninterpreted_option;
+    private $has_uninterpreted_option = false;
+
+    public function getJavaPackage()
+    {
+        return $this->java_package;
+    }
+
+    public function setJavaPackage($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->java_package = $var;
+        $this->has_java_package = true;
+    }
+
+    public function hasJavaPackage()
+    {
+        return $this->has_java_package;
+    }
+
+    public function getJavaOuterClassname()
+    {
+        return $this->java_outer_classname;
+    }
+
+    public function setJavaOuterClassname($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->java_outer_classname = $var;
+        $this->has_java_outer_classname = true;
+    }
+
+    public function hasJavaOuterClassname()
+    {
+        return $this->has_java_outer_classname;
+    }
+
+    public function getJavaMultipleFiles()
+    {
+        return $this->java_multiple_files;
+    }
+
+    public function setJavaMultipleFiles($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->java_multiple_files = $var;
+        $this->has_java_multiple_files = true;
+    }
+
+    public function hasJavaMultipleFiles()
+    {
+        return $this->has_java_multiple_files;
+    }
+
+    public function getJavaGenerateEqualsAndHash()
+    {
+        return $this->java_generate_equals_and_hash;
+    }
+
+    public function setJavaGenerateEqualsAndHash($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->java_generate_equals_and_hash = $var;
+        $this->has_java_generate_equals_and_hash = true;
+    }
+
+    public function hasJavaGenerateEqualsAndHash()
+    {
+        return $this->has_java_generate_equals_and_hash;
+    }
+
+    public function getJavaStringCheckUtf8()
+    {
+        return $this->java_string_check_utf8;
+    }
+
+    public function setJavaStringCheckUtf8($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->java_string_check_utf8 = $var;
+        $this->has_java_string_check_utf8 = true;
+    }
+
+    public function hasJavaStringCheckUtf8()
+    {
+        return $this->has_java_string_check_utf8;
+    }
+
+    public function getOptimizeFor()
+    {
+        return $this->optimize_for;
+    }
+
+    public function setOptimizeFor($var)
+    {
+        GPBUtil::checkEnum($var, \Google\Protobuf\Internal\FileOptions_OptimizeMode::class);
+        $this->optimize_for = $var;
+        $this->has_optimize_for = true;
+    }
+
+    public function hasOptimizeFor()
+    {
+        return $this->has_optimize_for;
+    }
+
+    public function getGoPackage()
+    {
+        return $this->go_package;
+    }
+
+    public function setGoPackage($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->go_package = $var;
+        $this->has_go_package = true;
+    }
+
+    public function hasGoPackage()
+    {
+        return $this->has_go_package;
+    }
+
+    public function getCcGenericServices()
+    {
+        return $this->cc_generic_services;
+    }
+
+    public function setCcGenericServices($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->cc_generic_services = $var;
+        $this->has_cc_generic_services = true;
+    }
+
+    public function hasCcGenericServices()
+    {
+        return $this->has_cc_generic_services;
+    }
+
+    public function getJavaGenericServices()
+    {
+        return $this->java_generic_services;
+    }
+
+    public function setJavaGenericServices($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->java_generic_services = $var;
+        $this->has_java_generic_services = true;
+    }
+
+    public function hasJavaGenericServices()
+    {
+        return $this->has_java_generic_services;
+    }
+
+    public function getPyGenericServices()
+    {
+        return $this->py_generic_services;
+    }
+
+    public function setPyGenericServices($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->py_generic_services = $var;
+        $this->has_py_generic_services = true;
+    }
+
+    public function hasPyGenericServices()
+    {
+        return $this->has_py_generic_services;
+    }
+
+    public function getDeprecated()
+    {
+        return $this->deprecated;
+    }
+
+    public function setDeprecated($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->deprecated = $var;
+        $this->has_deprecated = true;
+    }
+
+    public function hasDeprecated()
+    {
+        return $this->has_deprecated;
+    }
+
+    public function getCcEnableArenas()
+    {
+        return $this->cc_enable_arenas;
+    }
+
+    public function setCcEnableArenas($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->cc_enable_arenas = $var;
+        $this->has_cc_enable_arenas = true;
+    }
+
+    public function hasCcEnableArenas()
+    {
+        return $this->has_cc_enable_arenas;
+    }
+
+    public function getObjcClassPrefix()
+    {
+        return $this->objc_class_prefix;
+    }
+
+    public function setObjcClassPrefix($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->objc_class_prefix = $var;
+        $this->has_objc_class_prefix = true;
+    }
+
+    public function hasObjcClassPrefix()
+    {
+        return $this->has_objc_class_prefix;
+    }
+
+    public function getCsharpNamespace()
+    {
+        return $this->csharp_namespace;
+    }
+
+    public function setCsharpNamespace($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->csharp_namespace = $var;
+        $this->has_csharp_namespace = true;
+    }
+
+    public function hasCsharpNamespace()
+    {
+        return $this->has_csharp_namespace;
+    }
+
+    public function getUninterpretedOption()
+    {
+        return $this->uninterpreted_option;
+    }
+
+    public function setUninterpretedOption(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption::class);
+        $this->uninterpreted_option = $var;
+        $this->has_uninterpreted_option = true;
+    }
+
+    public function hasUninterpretedOption()
+    {
+        return $this->has_uninterpreted_option;
+    }
+
+}
+
+class FileOptions_OptimizeMode
+{
+    const SPEED = 1;
+    const CODE_SIZE = 2;
+    const LITE_RUNTIME = 3;
+}
+
+class MessageOptions extends \Google\Protobuf\Internal\Message
+{
+    private $message_set_wire_format = false;
+    private $has_message_set_wire_format = false;
+    private $no_standard_descriptor_accessor = false;
+    private $has_no_standard_descriptor_accessor = false;
+    private $deprecated = false;
+    private $has_deprecated = false;
+    private $map_entry = false;
+    private $has_map_entry = false;
+    private $uninterpreted_option;
+    private $has_uninterpreted_option = false;
+
+    public function getMessageSetWireFormat()
+    {
+        return $this->message_set_wire_format;
+    }
+
+    public function setMessageSetWireFormat($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->message_set_wire_format = $var;
+        $this->has_message_set_wire_format = true;
+    }
+
+    public function hasMessageSetWireFormat()
+    {
+        return $this->has_message_set_wire_format;
+    }
+
+    public function getNoStandardDescriptorAccessor()
+    {
+        return $this->no_standard_descriptor_accessor;
+    }
+
+    public function setNoStandardDescriptorAccessor($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->no_standard_descriptor_accessor = $var;
+        $this->has_no_standard_descriptor_accessor = true;
+    }
+
+    public function hasNoStandardDescriptorAccessor()
+    {
+        return $this->has_no_standard_descriptor_accessor;
+    }
+
+    public function getDeprecated()
+    {
+        return $this->deprecated;
+    }
+
+    public function setDeprecated($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->deprecated = $var;
+        $this->has_deprecated = true;
+    }
+
+    public function hasDeprecated()
+    {
+        return $this->has_deprecated;
+    }
+
+    public function getMapEntry()
+    {
+        return $this->map_entry;
+    }
+
+    public function setMapEntry($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->map_entry = $var;
+        $this->has_map_entry = true;
+    }
+
+    public function hasMapEntry()
+    {
+        return $this->has_map_entry;
+    }
+
+    public function getUninterpretedOption()
+    {
+        return $this->uninterpreted_option;
+    }
+
+    public function setUninterpretedOption(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption::class);
+        $this->uninterpreted_option = $var;
+        $this->has_uninterpreted_option = true;
+    }
+
+    public function hasUninterpretedOption()
+    {
+        return $this->has_uninterpreted_option;
+    }
+
+}
+
+class FieldOptions extends \Google\Protobuf\Internal\Message
+{
+    private $ctype = 0;
+    private $has_ctype = false;
+    private $packed = false;
+    private $has_packed = false;
+    private $jstype = 0;
+    private $has_jstype = false;
+    private $lazy = false;
+    private $has_lazy = false;
+    private $deprecated = false;
+    private $has_deprecated = false;
+    private $weak = false;
+    private $has_weak = false;
+    private $uninterpreted_option;
+    private $has_uninterpreted_option = false;
+
+    public function getCtype()
+    {
+        return $this->ctype;
+    }
+
+    public function setCtype($var)
+    {
+        GPBUtil::checkEnum($var, \Google\Protobuf\Internal\FieldOptions_CType::class);
+        $this->ctype = $var;
+        $this->has_ctype = true;
+    }
+
+    public function hasCtype()
+    {
+        return $this->has_ctype;
+    }
+
+    public function getPacked()
+    {
+        return $this->packed;
+    }
+
+    public function setPacked($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->packed = $var;
+        $this->has_packed = true;
+    }
+
+    public function hasPacked()
+    {
+        return $this->has_packed;
+    }
+
+    public function getJstype()
+    {
+        return $this->jstype;
+    }
+
+    public function setJstype($var)
+    {
+        GPBUtil::checkEnum($var, \Google\Protobuf\Internal\FieldOptions_JSType::class);
+        $this->jstype = $var;
+        $this->has_jstype = true;
+    }
+
+    public function hasJstype()
+    {
+        return $this->has_jstype;
+    }
+
+    public function getLazy()
+    {
+        return $this->lazy;
+    }
+
+    public function setLazy($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->lazy = $var;
+        $this->has_lazy = true;
+    }
+
+    public function hasLazy()
+    {
+        return $this->has_lazy;
+    }
+
+    public function getDeprecated()
+    {
+        return $this->deprecated;
+    }
+
+    public function setDeprecated($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->deprecated = $var;
+        $this->has_deprecated = true;
+    }
+
+    public function hasDeprecated()
+    {
+        return $this->has_deprecated;
+    }
+
+    public function getWeak()
+    {
+        return $this->weak;
+    }
+
+    public function setWeak($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->weak = $var;
+        $this->has_weak = true;
+    }
+
+    public function hasWeak()
+    {
+        return $this->has_weak;
+    }
+
+    public function getUninterpretedOption()
+    {
+        return $this->uninterpreted_option;
+    }
+
+    public function setUninterpretedOption(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption::class);
+        $this->uninterpreted_option = $var;
+        $this->has_uninterpreted_option = true;
+    }
+
+    public function hasUninterpretedOption()
+    {
+        return $this->has_uninterpreted_option;
+    }
+
+}
+
+class FieldOptions_CType
+{
+    const STRING = 0;
+    const CORD = 1;
+    const STRING_PIECE = 2;
+}
+
+class FieldOptions_JSType
+{
+    const JS_NORMAL = 0;
+    const JS_STRING = 1;
+    const JS_NUMBER = 2;
+}
+
+class OneofOptions extends \Google\Protobuf\Internal\Message
+{
+    private $uninterpreted_option;
+    private $has_uninterpreted_option = false;
+
+    public function getUninterpretedOption()
+    {
+        return $this->uninterpreted_option;
+    }
+
+    public function setUninterpretedOption(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption::class);
+        $this->uninterpreted_option = $var;
+        $this->has_uninterpreted_option = true;
+    }
+
+    public function hasUninterpretedOption()
+    {
+        return $this->has_uninterpreted_option;
+    }
+
+}
+
+class EnumOptions extends \Google\Protobuf\Internal\Message
+{
+    private $allow_alias = false;
+    private $has_allow_alias = false;
+    private $deprecated = false;
+    private $has_deprecated = false;
+    private $uninterpreted_option;
+    private $has_uninterpreted_option = false;
+
+    public function getAllowAlias()
+    {
+        return $this->allow_alias;
+    }
+
+    public function setAllowAlias($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->allow_alias = $var;
+        $this->has_allow_alias = true;
+    }
+
+    public function hasAllowAlias()
+    {
+        return $this->has_allow_alias;
+    }
+
+    public function getDeprecated()
+    {
+        return $this->deprecated;
+    }
+
+    public function setDeprecated($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->deprecated = $var;
+        $this->has_deprecated = true;
+    }
+
+    public function hasDeprecated()
+    {
+        return $this->has_deprecated;
+    }
+
+    public function getUninterpretedOption()
+    {
+        return $this->uninterpreted_option;
+    }
+
+    public function setUninterpretedOption(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption::class);
+        $this->uninterpreted_option = $var;
+        $this->has_uninterpreted_option = true;
+    }
+
+    public function hasUninterpretedOption()
+    {
+        return $this->has_uninterpreted_option;
+    }
+
+}
+
+class EnumValueOptions extends \Google\Protobuf\Internal\Message
+{
+    private $deprecated = false;
+    private $has_deprecated = false;
+    private $uninterpreted_option;
+    private $has_uninterpreted_option = false;
+
+    public function getDeprecated()
+    {
+        return $this->deprecated;
+    }
+
+    public function setDeprecated($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->deprecated = $var;
+        $this->has_deprecated = true;
+    }
+
+    public function hasDeprecated()
+    {
+        return $this->has_deprecated;
+    }
+
+    public function getUninterpretedOption()
+    {
+        return $this->uninterpreted_option;
+    }
+
+    public function setUninterpretedOption(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption::class);
+        $this->uninterpreted_option = $var;
+        $this->has_uninterpreted_option = true;
+    }
+
+    public function hasUninterpretedOption()
+    {
+        return $this->has_uninterpreted_option;
+    }
+
+}
+
+class ServiceOptions extends \Google\Protobuf\Internal\Message
+{
+    private $deprecated = false;
+    private $has_deprecated = false;
+    private $uninterpreted_option;
+    private $has_uninterpreted_option = false;
+
+    public function getDeprecated()
+    {
+        return $this->deprecated;
+    }
+
+    public function setDeprecated($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->deprecated = $var;
+        $this->has_deprecated = true;
+    }
+
+    public function hasDeprecated()
+    {
+        return $this->has_deprecated;
+    }
+
+    public function getUninterpretedOption()
+    {
+        return $this->uninterpreted_option;
+    }
+
+    public function setUninterpretedOption(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption::class);
+        $this->uninterpreted_option = $var;
+        $this->has_uninterpreted_option = true;
+    }
+
+    public function hasUninterpretedOption()
+    {
+        return $this->has_uninterpreted_option;
+    }
+
+}
+
+class MethodOptions extends \Google\Protobuf\Internal\Message
+{
+    private $deprecated = false;
+    private $has_deprecated = false;
+    private $uninterpreted_option;
+    private $has_uninterpreted_option = false;
+
+    public function getDeprecated()
+    {
+        return $this->deprecated;
+    }
+
+    public function setDeprecated($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->deprecated = $var;
+        $this->has_deprecated = true;
+    }
+
+    public function hasDeprecated()
+    {
+        return $this->has_deprecated;
+    }
+
+    public function getUninterpretedOption()
+    {
+        return $this->uninterpreted_option;
+    }
+
+    public function setUninterpretedOption(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption::class);
+        $this->uninterpreted_option = $var;
+        $this->has_uninterpreted_option = true;
+    }
+
+    public function hasUninterpretedOption()
+    {
+        return $this->has_uninterpreted_option;
+    }
+
+}
+
+class UninterpretedOption extends \Google\Protobuf\Internal\Message
+{
+    private $name;
+    private $has_name = false;
+    private $identifier_value = '';
+    private $has_identifier_value = false;
+    private $positive_int_value = 0;
+    private $has_positive_int_value = false;
+    private $negative_int_value = 0;
+    private $has_negative_int_value = false;
+    private $double_value = 0.0;
+    private $has_double_value = false;
+    private $string_value = '';
+    private $has_string_value = false;
+    private $aggregate_value = '';
+    private $has_aggregate_value = false;
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\UninterpretedOption_NamePart::class);
+        $this->name = $var;
+        $this->has_name = true;
+    }
+
+    public function hasName()
+    {
+        return $this->has_name;
+    }
+
+    public function getIdentifierValue()
+    {
+        return $this->identifier_value;
+    }
+
+    public function setIdentifierValue($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->identifier_value = $var;
+        $this->has_identifier_value = true;
+    }
+
+    public function hasIdentifierValue()
+    {
+        return $this->has_identifier_value;
+    }
+
+    public function getPositiveIntValue()
+    {
+        return $this->positive_int_value;
+    }
+
+    public function setPositiveIntValue($var)
+    {
+        GPBUtil::checkUint64($var);
+        $this->positive_int_value = $var;
+        $this->has_positive_int_value = true;
+    }
+
+    public function hasPositiveIntValue()
+    {
+        return $this->has_positive_int_value;
+    }
+
+    public function getNegativeIntValue()
+    {
+        return $this->negative_int_value;
+    }
+
+    public function setNegativeIntValue($var)
+    {
+        GPBUtil::checkInt64($var);
+        $this->negative_int_value = $var;
+        $this->has_negative_int_value = true;
+    }
+
+    public function hasNegativeIntValue()
+    {
+        return $this->has_negative_int_value;
+    }
+
+    public function getDoubleValue()
+    {
+        return $this->double_value;
+    }
+
+    public function setDoubleValue($var)
+    {
+        GPBUtil::checkDouble($var);
+        $this->double_value = $var;
+        $this->has_double_value = true;
+    }
+
+    public function hasDoubleValue()
+    {
+        return $this->has_double_value;
+    }
+
+    public function getStringValue()
+    {
+        return $this->string_value;
+    }
+
+    public function setStringValue($var)
+    {
+        GPBUtil::checkString($var, False);
+        $this->string_value = $var;
+        $this->has_string_value = true;
+    }
+
+    public function hasStringValue()
+    {
+        return $this->has_string_value;
+    }
+
+    public function getAggregateValue()
+    {
+        return $this->aggregate_value;
+    }
+
+    public function setAggregateValue($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->aggregate_value = $var;
+        $this->has_aggregate_value = true;
+    }
+
+    public function hasAggregateValue()
+    {
+        return $this->has_aggregate_value;
+    }
+
+}
+
+class UninterpretedOption_NamePart extends \Google\Protobuf\Internal\Message
+{
+    private $name_part = '';
+    private $has_name_part = false;
+    private $is_extension = false;
+    private $has_is_extension = false;
+
+    public function getNamePart()
+    {
+        return $this->name_part;
+    }
+
+    public function setNamePart($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->name_part = $var;
+        $this->has_name_part = true;
+    }
+
+    public function hasNamePart()
+    {
+        return $this->has_name_part;
+    }
+
+    public function getIsExtension()
+    {
+        return $this->is_extension;
+    }
+
+    public function setIsExtension($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->is_extension = $var;
+        $this->has_is_extension = true;
+    }
+
+    public function hasIsExtension()
+    {
+        return $this->has_is_extension;
+    }
+
+}
+
+class SourceCodeInfo extends \Google\Protobuf\Internal\Message
+{
+    private $location;
+    private $has_location = false;
+
+    public function getLocation()
+    {
+        return $this->location;
+    }
+
+    public function setLocation(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\SourceCodeInfo_Location::class);
+        $this->location = $var;
+        $this->has_location = true;
+    }
+
+    public function hasLocation()
+    {
+        return $this->has_location;
+    }
+
+}
+
+class SourceCodeInfo_Location extends \Google\Protobuf\Internal\Message
+{
+    private $path;
+    private $has_path = false;
+    private $span;
+    private $has_span = false;
+    private $leading_comments = '';
+    private $has_leading_comments = false;
+    private $trailing_comments = '';
+    private $has_trailing_comments = false;
+    private $leading_detached_comments;
+    private $has_leading_detached_comments = false;
+
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    public function setPath(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT32);
+        $this->path = $var;
+        $this->has_path = true;
+    }
+
+    public function hasPath()
+    {
+        return $this->has_path;
+    }
+
+    public function getSpan()
+    {
+        return $this->span;
+    }
+
+    public function setSpan(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT32);
+        $this->span = $var;
+        $this->has_span = true;
+    }
+
+    public function hasSpan()
+    {
+        return $this->has_span;
+    }
+
+    public function getLeadingComments()
+    {
+        return $this->leading_comments;
+    }
+
+    public function setLeadingComments($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->leading_comments = $var;
+        $this->has_leading_comments = true;
+    }
+
+    public function hasLeadingComments()
+    {
+        return $this->has_leading_comments;
+    }
+
+    public function getTrailingComments()
+    {
+        return $this->trailing_comments;
+    }
+
+    public function setTrailingComments($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->trailing_comments = $var;
+        $this->has_trailing_comments = true;
+    }
+
+    public function hasTrailingComments()
+    {
+        return $this->has_trailing_comments;
+    }
+
+    public function getLeadingDetachedComments()
+    {
+        return $this->leading_detached_comments;
+    }
+
+    public function setLeadingDetachedComments(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::STRING);
+        $this->leading_detached_comments = $var;
+        $this->has_leading_detached_comments = true;
+    }
+
+    public function hasLeadingDetachedComments()
+    {
+        return $this->has_leading_detached_comments;
+    }
+
+}
+
+class GeneratedCodeInfo extends \Google\Protobuf\Internal\Message
+{
+    private $annotation;
+    private $has_annotation = false;
+
+    public function getAnnotation()
+    {
+        return $this->annotation;
+    }
+
+    public function setAnnotation(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Google\Protobuf\Internal\GeneratedCodeInfo_Annotation::class);
+        $this->annotation = $var;
+        $this->has_annotation = true;
+    }
+
+    public function hasAnnotation()
+    {
+        return $this->has_annotation;
+    }
+
+}
+
+class GeneratedCodeInfo_Annotation extends \Google\Protobuf\Internal\Message
+{
+    private $path;
+    private $has_path = false;
+    private $source_file = '';
+    private $has_source_file = false;
+    private $begin = 0;
+    private $has_begin = false;
+    private $end = 0;
+    private $has_end = false;
+
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    public function setPath(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT32);
+        $this->path = $var;
+        $this->has_path = true;
+    }
+
+    public function hasPath()
+    {
+        return $this->has_path;
+    }
+
+    public function getSourceFile()
+    {
+        return $this->source_file;
+    }
+
+    public function setSourceFile($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->source_file = $var;
+        $this->has_source_file = true;
+    }
+
+    public function hasSourceFile()
+    {
+        return $this->has_source_file;
+    }
+
+    public function getBegin()
+    {
+        return $this->begin;
+    }
+
+    public function setBegin($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->begin = $var;
+        $this->has_begin = true;
+    }
+
+    public function hasBegin()
+    {
+        return $this->has_begin;
+    }
+
+    public function getEnd()
+    {
+        return $this->end;
+    }
+
+    public function setEnd($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->end = $var;
+        $this->has_end = true;
+    }
+
+    public function hasEnd()
+    {
+        return $this->has_end;
+    }
+
+}
+
+$pool = DescriptorPool::getGeneratedPool();
+
+$pool->addMessage('google.protobuf.internal.FileDescriptorSet', FileDescriptorSet::class)
+    ->repeated('file', GPBType::MESSAGE, 1, 'google.protobuf.internal.FileDescriptorProto')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.FileDescriptorProto', FileDescriptorProto::class)
+    ->optional('name', GPBType::STRING, 1)
+    ->optional('package', GPBType::STRING, 2)
+    ->repeated('dependency', GPBType::STRING, 3)
+    ->repeated('public_dependency', GPBType::INT32, 10)
+    ->repeated('weak_dependency', GPBType::INT32, 11)
+    ->repeated('message_type', GPBType::MESSAGE, 4, 'google.protobuf.internal.DescriptorProto')
+    ->repeated('enum_type', GPBType::MESSAGE, 5, 'google.protobuf.internal.EnumDescriptorProto')
+    ->repeated('service', GPBType::MESSAGE, 6, 'google.protobuf.internal.ServiceDescriptorProto')
+    ->repeated('extension', GPBType::MESSAGE, 7, 'google.protobuf.internal.FieldDescriptorProto')
+    ->optional('options', GPBType::MESSAGE, 8, 'google.protobuf.internal.FileOptions')
+    ->optional('source_code_info', GPBType::MESSAGE, 9, 'google.protobuf.internal.SourceCodeInfo')
+    ->optional('syntax', GPBType::STRING, 12)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.DescriptorProto', DescriptorProto::class)
+    ->optional('name', GPBType::STRING, 1)
+    ->repeated('field', GPBType::MESSAGE, 2, 'google.protobuf.internal.FieldDescriptorProto')
+    ->repeated('extension', GPBType::MESSAGE, 6, 'google.protobuf.internal.FieldDescriptorProto')
+    ->repeated('nested_type', GPBType::MESSAGE, 3, 'google.protobuf.internal.DescriptorProto')
+    ->repeated('enum_type', GPBType::MESSAGE, 4, 'google.protobuf.internal.EnumDescriptorProto')
+    ->repeated('extension_range', GPBType::MESSAGE, 5, 'google.protobuf.internal.DescriptorProto.ExtensionRange')
+    ->repeated('oneof_decl', GPBType::MESSAGE, 8, 'google.protobuf.internal.OneofDescriptorProto')
+    ->optional('options', GPBType::MESSAGE, 7, 'google.protobuf.internal.MessageOptions')
+    ->repeated('reserved_range', GPBType::MESSAGE, 9, 'google.protobuf.internal.DescriptorProto.ReservedRange')
+    ->repeated('reserved_name', GPBType::STRING, 10)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.DescriptorProto.ExtensionRange', DescriptorProto_ExtensionRange::class)
+    ->optional('start', GPBType::INT32, 1)
+    ->optional('end', GPBType::INT32, 2)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.DescriptorProto.ReservedRange', DescriptorProto_ReservedRange::class)
+    ->optional('start', GPBType::INT32, 1)
+    ->optional('end', GPBType::INT32, 2)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.FieldDescriptorProto', FieldDescriptorProto::class)
+    ->optional('name', GPBType::STRING, 1)
+    ->optional('number', GPBType::INT32, 3)
+    ->optional('label', GPBType::ENUM, 4, 'google.protobuf.internal.FieldDescriptorProto.Label')
+    ->optional('type', GPBType::ENUM, 5, 'google.protobuf.internal.FieldDescriptorProto.Type')
+    ->optional('type_name', GPBType::STRING, 6)
+    ->optional('extendee', GPBType::STRING, 2)
+    ->optional('default_value', GPBType::STRING, 7)
+    ->optional('oneof_index', GPBType::INT32, 9)
+    ->optional('json_name', GPBType::STRING, 10)
+    ->optional('options', GPBType::MESSAGE, 8, 'google.protobuf.internal.FieldOptions')
+    ->finalizeToPool();
+
+$pool->addEnum('google.protobuf.internal.FieldDescriptorProto.Type', Type::class)
+    ->value("TYPE_DOUBLE", 1)
+    ->value("TYPE_FLOAT", 2)
+    ->value("TYPE_INT64", 3)
+    ->value("TYPE_UINT64", 4)
+    ->value("TYPE_INT32", 5)
+    ->value("TYPE_FIXED64", 6)
+    ->value("TYPE_FIXED32", 7)
+    ->value("TYPE_BOOL", 8)
+    ->value("TYPE_STRING", 9)
+    ->value("TYPE_GROUP", 10)
+    ->value("TYPE_MESSAGE", 11)
+    ->value("TYPE_BYTES", 12)
+    ->value("TYPE_UINT32", 13)
+    ->value("TYPE_ENUM", 14)
+    ->value("TYPE_SFIXED32", 15)
+    ->value("TYPE_SFIXED64", 16)
+    ->value("TYPE_SINT32", 17)
+    ->value("TYPE_SINT64", 18)
+    ->finalizeToPool();
+
+$pool->addEnum('google.protobuf.internal.FieldDescriptorProto.Label', Label::class)
+    ->value("LABEL_OPTIONAL", 1)
+    ->value("LABEL_REQUIRED", 2)
+    ->value("LABEL_REPEATED", 3)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.OneofDescriptorProto', OneofDescriptorProto::class)
+    ->optional('name', GPBType::STRING, 1)
+    ->optional('options', GPBType::MESSAGE, 2, 'google.protobuf.internal.OneofOptions')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.EnumDescriptorProto', EnumDescriptorProto::class)
+    ->optional('name', GPBType::STRING, 1)
+    ->repeated('value', GPBType::MESSAGE, 2, 'google.protobuf.internal.EnumValueDescriptorProto')
+    ->optional('options', GPBType::MESSAGE, 3, 'google.protobuf.internal.EnumOptions')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.EnumValueDescriptorProto', EnumValueDescriptorProto::class)
+    ->optional('name', GPBType::STRING, 1)
+    ->optional('number', GPBType::INT32, 2)
+    ->optional('options', GPBType::MESSAGE, 3, 'google.protobuf.internal.EnumValueOptions')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.ServiceDescriptorProto', ServiceDescriptorProto::class)
+    ->optional('name', GPBType::STRING, 1)
+    ->repeated('method', GPBType::MESSAGE, 2, 'google.protobuf.internal.MethodDescriptorProto')
+    ->optional('options', GPBType::MESSAGE, 3, 'google.protobuf.internal.ServiceOptions')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.MethodDescriptorProto', MethodDescriptorProto::class)
+    ->optional('name', GPBType::STRING, 1)
+    ->optional('input_type', GPBType::STRING, 2)
+    ->optional('output_type', GPBType::STRING, 3)
+    ->optional('options', GPBType::MESSAGE, 4, 'google.protobuf.internal.MethodOptions')
+    ->optional('client_streaming', GPBType::BOOL, 5)
+    ->optional('server_streaming', GPBType::BOOL, 6)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.FileOptions', FileOptions::class)
+    ->optional('java_package', GPBType::STRING, 1)
+    ->optional('java_outer_classname', GPBType::STRING, 8)
+    ->optional('java_multiple_files', GPBType::BOOL, 10)
+    ->optional('java_generate_equals_and_hash', GPBType::BOOL, 20)
+    ->optional('java_string_check_utf8', GPBType::BOOL, 27)
+    ->optional('optimize_for', GPBType::ENUM, 9, 'google.protobuf.internal.FileOptions.OptimizeMode')
+    ->optional('go_package', GPBType::STRING, 11)
+    ->optional('cc_generic_services', GPBType::BOOL, 16)
+    ->optional('java_generic_services', GPBType::BOOL, 17)
+    ->optional('py_generic_services', GPBType::BOOL, 18)
+    ->optional('deprecated', GPBType::BOOL, 23)
+    ->optional('cc_enable_arenas', GPBType::BOOL, 31)
+    ->optional('objc_class_prefix', GPBType::STRING, 36)
+    ->optional('csharp_namespace', GPBType::STRING, 37)
+    ->repeated('uninterpreted_option', GPBType::MESSAGE, 999, 'google.protobuf.internal.UninterpretedOption')
+    ->finalizeToPool();
+
+$pool->addEnum('google.protobuf.internal.FileOptions.OptimizeMode', OptimizeMode::class)
+    ->value("SPEED", 1)
+    ->value("CODE_SIZE", 2)
+    ->value("LITE_RUNTIME", 3)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.MessageOptions', MessageOptions::class)
+    ->optional('message_set_wire_format', GPBType::BOOL, 1)
+    ->optional('no_standard_descriptor_accessor', GPBType::BOOL, 2)
+    ->optional('deprecated', GPBType::BOOL, 3)
+    ->optional('map_entry', GPBType::BOOL, 7)
+    ->repeated('uninterpreted_option', GPBType::MESSAGE, 999, 'google.protobuf.internal.UninterpretedOption')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.FieldOptions', FieldOptions::class)
+    ->optional('ctype', GPBType::ENUM, 1, 'google.protobuf.internal.FieldOptions.CType')
+    ->optional('packed', GPBType::BOOL, 2)
+    ->optional('jstype', GPBType::ENUM, 6, 'google.protobuf.internal.FieldOptions.JSType')
+    ->optional('lazy', GPBType::BOOL, 5)
+    ->optional('deprecated', GPBType::BOOL, 3)
+    ->optional('weak', GPBType::BOOL, 10)
+    ->repeated('uninterpreted_option', GPBType::MESSAGE, 999, 'google.protobuf.internal.UninterpretedOption')
+    ->finalizeToPool();
+
+$pool->addEnum('google.protobuf.internal.FieldOptions.CType', CType::class)
+    ->value("STRING", 0)
+    ->value("CORD", 1)
+    ->value("STRING_PIECE", 2)
+    ->finalizeToPool();
+
+$pool->addEnum('google.protobuf.internal.FieldOptions.JSType', JSType::class)
+    ->value("JS_NORMAL", 0)
+    ->value("JS_STRING", 1)
+    ->value("JS_NUMBER", 2)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.OneofOptions', OneofOptions::class)
+    ->repeated('uninterpreted_option', GPBType::MESSAGE, 999, 'google.protobuf.internal.UninterpretedOption')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.EnumOptions', EnumOptions::class)
+    ->optional('allow_alias', GPBType::BOOL, 2)
+    ->optional('deprecated', GPBType::BOOL, 3)
+    ->repeated('uninterpreted_option', GPBType::MESSAGE, 999, 'google.protobuf.internal.UninterpretedOption')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.EnumValueOptions', EnumValueOptions::class)
+    ->optional('deprecated', GPBType::BOOL, 1)
+    ->repeated('uninterpreted_option', GPBType::MESSAGE, 999, 'google.protobuf.internal.UninterpretedOption')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.ServiceOptions', ServiceOptions::class)
+    ->optional('deprecated', GPBType::BOOL, 33)
+    ->repeated('uninterpreted_option', GPBType::MESSAGE, 999, 'google.protobuf.internal.UninterpretedOption')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.MethodOptions', MethodOptions::class)
+    ->optional('deprecated', GPBType::BOOL, 33)
+    ->repeated('uninterpreted_option', GPBType::MESSAGE, 999, 'google.protobuf.internal.UninterpretedOption')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.UninterpretedOption', UninterpretedOption::class)
+    ->repeated('name', GPBType::MESSAGE, 2, 'google.protobuf.internal.UninterpretedOption.NamePart')
+    ->optional('identifier_value', GPBType::STRING, 3)
+    ->optional('positive_int_value', GPBType::UINT64, 4)
+    ->optional('negative_int_value', GPBType::INT64, 5)
+    ->optional('double_value', GPBType::DOUBLE, 6)
+    ->optional('string_value', GPBType::BYTES, 7)
+    ->optional('aggregate_value', GPBType::STRING, 8)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.UninterpretedOption.NamePart', UninterpretedOption_NamePart::class)
+    ->required('name_part', GPBType::STRING, 1)
+    ->required('is_extension', GPBType::BOOL, 2)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.SourceCodeInfo', SourceCodeInfo::class)
+    ->repeated('location', GPBType::MESSAGE, 1, 'google.protobuf.internal.SourceCodeInfo.Location')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.SourceCodeInfo.Location', SourceCodeInfo_Location::class)
+    ->repeated('path', GPBType::INT32, 1)
+    ->repeated('span', GPBType::INT32, 2)
+    ->optional('leading_comments', GPBType::STRING, 3)
+    ->optional('trailing_comments', GPBType::STRING, 4)
+    ->repeated('leading_detached_comments', GPBType::STRING, 6)
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.GeneratedCodeInfo', GeneratedCodeInfo::class)
+    ->repeated('annotation', GPBType::MESSAGE, 1, 'google.protobuf.internal.GeneratedCodeInfo.Annotation')
+    ->finalizeToPool();
+
+$pool->addMessage('google.protobuf.internal.GeneratedCodeInfo.Annotation', GeneratedCodeInfo_Annotation::class)
+    ->repeated('path', GPBType::INT32, 1)
+    ->optional('source_file', GPBType::STRING, 2)
+    ->optional('begin', GPBType::INT32, 3)
+    ->optional('end', GPBType::INT32, 4)
+    ->finalizeToPool();
+
+$pool->finish();

+ 15 - 0
php/src/phpdoc.dist.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- Install phpDocumentor to generate docs. -->
+<phpdoc>
+  <parser>
+    <target>doc</target>
+  </parser>
+  <transformer>
+    <target>doc</target>
+  </transformer>
+  <files>
+    <file>Google/Protobuf/Internal/MapField.php</file>
+    <file>Google/Protobuf/Internal/Message.php</file>
+    <file>Google/Protobuf/Internal/RepeatedField.php</file>
+  </files>
+</phpdoc>

+ 888 - 0
php/tests/array_test.php

@@ -0,0 +1,888 @@
+<?php
+
+require_once('test.pb.php');
+require_once('test_util.php');
+
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\GPBType;
+use Foo\TestMessage;
+use Foo\TestMessage_Sub;
+
+class RepeatedFieldTest extends PHPUnit_Framework_TestCase
+{
+
+    #########################################################
+    # Test int32 field.
+    #########################################################
+
+    public function testInt32()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+
+        // Test append.
+        $arr []= MAX_INT32;
+        $this->assertSame(MAX_INT32, $arr[0]);
+        $arr []= MIN_INT32;
+        $this->assertSame(MIN_INT32, $arr[1]);
+
+        $arr []= 1.1;
+        $this->assertSame(1, $arr[2]);
+        $arr []= MAX_INT32_FLOAT;
+        $this->assertSame(MAX_INT32, $arr[3]);
+        $arr []= MAX_INT32_FLOAT;
+        $this->assertSame(MAX_INT32, $arr[4]);
+
+        $arr []= '2';
+        $this->assertSame(2, $arr[5]);
+        $arr []= '3.1';
+        $this->assertSame(3, $arr[6]);
+        $arr []= MAX_INT32_STRING;
+        $this->assertSame(MAX_INT32, $arr[7]);
+
+        $this->assertEquals(8, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            $this->assertSame(0, $arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= MAX_INT32;
+        $this->assertSame(MAX_INT32, $arr[0]);
+        $arr [1]= MIN_INT32;
+        $this->assertSame(MIN_INT32, $arr[1]);
+
+        $arr [2]= 1.1;
+        $this->assertSame(1, $arr[2]);
+        $arr [3]= MAX_INT32_FLOAT;
+        $this->assertSame(MAX_INT32, $arr[3]);
+        $arr [4]= MAX_INT32_FLOAT;
+        $this->assertSame(MAX_INT32, $arr[4]);
+
+        $arr [5]= '2';
+        $this->assertSame(2, $arr[5]);
+        $arr [6]= '3.1';
+        $this->assertSame(3, $arr[6]);
+        $arr [7]= MAX_INT32_STRING;
+        $this->assertSame(MAX_INT32, $arr[7]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32AppendStringFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr []= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32SetStringFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr []= 0;
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32AppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr []= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32SetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr []= 0;
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test uint32 field.
+    #########################################################
+
+    public function testUint32()
+    {
+        $arr = new RepeatedField(GPBType::UINT32);
+
+        // Test append.
+        $arr []= MAX_UINT32;
+        $this->assertSame(-1, $arr[0]);
+        $arr []= -1;
+        $this->assertSame(-1, $arr[1]);
+        $arr []= MIN_UINT32;
+        $this->assertSame(MIN_UINT32, $arr[2]);
+
+        $arr []= 1.1;
+        $this->assertSame(1, $arr[3]);
+        $arr []= MAX_UINT32_FLOAT;
+        $this->assertSame(-1, $arr[4]);
+        $arr []= -1.0;
+        $this->assertSame(-1, $arr[5]);
+        $arr []= MIN_UINT32_FLOAT;
+        $this->assertSame(MIN_UINT32, $arr[6]);
+
+        $arr []= '2';
+        $this->assertSame(2, $arr[7]);
+        $arr []= '3.1';
+        $this->assertSame(3, $arr[8]);
+        $arr []= MAX_UINT32_STRING;
+        $this->assertSame(-1, $arr[9]);
+        $arr []= '-1.0';
+        $this->assertSame(-1, $arr[10]);
+        $arr []= MIN_UINT32_STRING;
+        $this->assertSame(MIN_UINT32, $arr[11]);
+
+        $this->assertEquals(12, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            $this->assertSame(0, $arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= MAX_UINT32;
+        $this->assertSame(-1, $arr[0]);
+        $arr [1]= -1;
+        $this->assertSame(-1, $arr[1]);
+        $arr [2]= MIN_UINT32;
+        $this->assertSame(MIN_UINT32, $arr[2]);
+
+        $arr [3]= 1.1;
+        $this->assertSame(1, $arr[3]);
+        $arr [4]= MAX_UINT32_FLOAT;
+        $this->assertSame(-1, $arr[4]);
+        $arr [5]= -1.0;
+        $this->assertSame(-1, $arr[5]);
+        $arr [6]= MIN_UINT32_FLOAT;
+        $this->assertSame(MIN_UINT32, $arr[6]);
+
+        $arr [7]= '2';
+        $this->assertSame(2, $arr[7]);
+        $arr [8]= '3.1';
+        $this->assertSame(3, $arr[8]);
+        $arr [9]= MAX_UINT32_STRING;
+        $this->assertSame(-1, $arr[9]);
+        $arr [10]= '-1.0';
+        $this->assertSame(-1, $arr[10]);
+        $arr [11]= MIN_UINT32_STRING;
+        $this->assertSame(MIN_UINT32, $arr[11]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32AppendStringFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT32);
+        $arr []= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32SetStringFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT32);
+        $arr []= 0;
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32AppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT32);
+        $arr []= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32SetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT32);
+        $arr []= 0;
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test int64 field.
+    #########################################################
+
+    public function testInt64()
+    {
+        $arr = new RepeatedField(GPBType::INT64);
+
+        // Test append.
+        $arr []= MAX_INT64;
+        $this->assertSame(MAX_INT64, $arr[0]);
+        $arr []= MIN_INT64;
+        $this->assertEquals(MIN_INT64, $arr[1]);
+
+        $arr []= 1.1;
+        $this->assertSame(1, $arr[2]);
+
+        $arr []= '2';
+        $this->assertSame(2, $arr[3]);
+        $arr []= '3.1';
+        $this->assertSame(3, $arr[4]);
+        $arr []= MAX_INT64_STRING;
+        $this->assertSame(MAX_INT64, $arr[5]);
+        $arr []= MIN_INT64_STRING;
+        $this->assertEquals(MIN_INT64, $arr[6]);
+
+        $this->assertEquals(7, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            $this->assertSame(0, $arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= MAX_INT64;
+        $this->assertSame(MAX_INT64, $arr[0]);
+        $arr [1]= MIN_INT64;
+        $this->assertEquals(MIN_INT64, $arr[1]);
+
+        $arr [2]= 1.1;
+        $this->assertSame(1, $arr[2]);
+
+        $arr [3]= '2';
+        $this->assertSame(2, $arr[3]);
+        $arr [4]= '3.1';
+        $this->assertSame(3, $arr[4]);
+        $arr [5]= MAX_INT64_STRING;
+        $this->assertSame(MAX_INT64, $arr[5]);
+        $arr [6]= MIN_INT64_STRING;
+        $this->assertEquals(MIN_INT64, $arr[6]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64AppendStringFail()
+    {
+        $arr = new RepeatedField(GPBType::INT64);
+        $arr []= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64SetStringFail()
+    {
+        $arr = new RepeatedField(GPBType::INT64);
+        $arr []= 0;
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64AppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::INT64);
+        $arr []= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64SetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::INT64);
+        $arr []= 0;
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test uint64 field.
+    #########################################################
+
+    public function testUint64()
+    {
+        $arr = new RepeatedField(GPBType::UINT64);
+
+        // Test append.
+        $arr []= MAX_UINT64;
+        $this->assertEquals(MAX_UINT64, $arr[0]);
+
+        $arr []= 1.1;
+        $this->assertSame(1, $arr[1]);
+
+        $arr []= '2';
+        $this->assertSame(2, $arr[2]);
+        $arr []= '3.1';
+        $this->assertSame(3, $arr[3]);
+        $arr []= MAX_UINT64_STRING;
+        $this->assertEquals(MAX_UINT64, $arr[4]);
+
+        $this->assertEquals(5, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            $this->assertSame(0, $arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= MAX_UINT64;
+        $this->assertEquals(MAX_UINT64, $arr[0]);
+
+        $arr [1]= 1.1;
+        $this->assertSame(1, $arr[1]);
+
+        $arr [2]= '2';
+        $this->assertSame(2, $arr[2]);
+        $arr [3]= '3.1';
+        $this->assertSame(3, $arr[3]);
+        $arr [4]= MAX_UINT64_STRING;
+        $this->assertEquals(MAX_UINT64, $arr[4]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64AppendStringFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT64);
+        $arr []= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64SetStringFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT64);
+        $arr []= 0;
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64AppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT64);
+        $arr []= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64SetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT64);
+        $arr []= 0;
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test float field.
+    #########################################################
+
+    public function testFloat()
+    {
+        $arr = new RepeatedField(GPBType::FLOAT);
+
+        // Test append.
+        $arr []= 1;
+        $this->assertEquals(1.0, $arr[0], '', MAX_FLOAT_DIFF);
+
+        $arr []= 1.1;
+        $this->assertEquals(1.1, $arr[1], '', MAX_FLOAT_DIFF);
+
+        $arr []= '2';
+        $this->assertEquals(2.0, $arr[2], '', MAX_FLOAT_DIFF);
+        $arr []= '3.1';
+        $this->assertEquals(3.1, $arr[3], '', MAX_FLOAT_DIFF);
+
+        $this->assertEquals(4, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            $this->assertSame(0.0, $arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= 1;
+        $this->assertEquals(1.0, $arr[0], '', MAX_FLOAT_DIFF);
+
+        $arr [1]= 1.1;
+        $this->assertEquals(1.1, $arr[1], '', MAX_FLOAT_DIFF);
+
+        $arr [2]= '2';
+        $this->assertEquals(2.0, $arr[2], '', MAX_FLOAT_DIFF);
+        $arr [3]= '3.1';
+        $this->assertEquals(3.1, $arr[3], '', MAX_FLOAT_DIFF);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatAppendStringFail()
+    {
+        $arr = new RepeatedField(GPBType::FLOAT);
+        $arr []= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatSetStringFail()
+    {
+        $arr = new RepeatedField(GPBType::FLOAT);
+        $arr []= 0.0;
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatAppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::FLOAT);
+        $arr []= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatSetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::FLOAT);
+        $arr []= 0.0;
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test double field.
+    #########################################################
+
+    public function testDouble()
+    {
+        $arr = new RepeatedField(GPBType::DOUBLE);
+
+        // Test append.
+        $arr []= 1;
+        $this->assertEquals(1.0, $arr[0], '', MAX_FLOAT_DIFF);
+
+        $arr []= 1.1;
+        $this->assertEquals(1.1, $arr[1], '', MAX_FLOAT_DIFF);
+
+        $arr []= '2';
+        $this->assertEquals(2.0, $arr[2], '', MAX_FLOAT_DIFF);
+        $arr []= '3.1';
+        $this->assertEquals(3.1, $arr[3], '', MAX_FLOAT_DIFF);
+
+        $this->assertEquals(4, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            $this->assertSame(0.0, $arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= 1;
+        $this->assertEquals(1.0, $arr[0], '', MAX_FLOAT_DIFF);
+
+        $arr [1]= 1.1;
+        $this->assertEquals(1.1, $arr[1], '', MAX_FLOAT_DIFF);
+
+        $arr [2]= '2';
+        $this->assertEquals(2.0, $arr[2], '', MAX_FLOAT_DIFF);
+        $arr [3]= '3.1';
+        $this->assertEquals(3.1, $arr[3], '', MAX_FLOAT_DIFF);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleAppendStringFail()
+    {
+        $arr = new RepeatedField(GPBType::DOUBLE);
+        $arr []= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleSetStringFail()
+    {
+        $arr = new RepeatedField(GPBType::DOUBLE);
+        $arr []= 0.0;
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleAppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::DOUBLE);
+        $arr []= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleSetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::DOUBLE);
+        $arr []= 0.0;
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test bool field.
+    #########################################################
+
+    public function testBool()
+    {
+        $arr = new RepeatedField(GPBType::BOOL);
+
+        // Test append.
+        $arr []= true;
+        $this->assertSame(true, $arr[0]);
+
+        $arr []= -1;
+        $this->assertSame(true, $arr[1]);
+
+        $arr []= 1.1;
+        $this->assertSame(true, $arr[2]);
+
+        $arr []= '';
+        $this->assertSame(false, $arr[3]);
+
+        $this->assertEquals(4, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            $this->assertSame(false, $arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= true;
+        $this->assertSame(true, $arr[0]);
+
+        $arr [1]= -1;
+        $this->assertSame(true, $arr[1]);
+
+        $arr [2]= 1.1;
+        $this->assertSame(true, $arr[2]);
+
+        $arr [3]= '';
+        $this->assertSame(false, $arr[3]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolAppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::BOOL);
+        $arr []= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolSetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::BOOL);
+        $arr []= true;
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test string field.
+    #########################################################
+
+    public function testString()
+    {
+        $arr = new RepeatedField(GPBType::STRING);
+
+        // Test append.
+        $arr []= 'abc';
+        $this->assertSame('abc', $arr[0]);
+
+        $arr []= 1;
+        $this->assertSame('1', $arr[1]);
+
+        $arr []= 1.1;
+        $this->assertSame('1.1', $arr[2]);
+
+        $arr []= true;
+        $this->assertSame('1', $arr[3]);
+
+        $this->assertEquals(4, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = '';
+            $this->assertSame('', $arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= 'abc';
+        $this->assertSame('abc', $arr[0]);
+
+        $arr [1]= 1;
+        $this->assertSame('1', $arr[1]);
+
+        $arr [2]= 1.1;
+        $this->assertSame('1.1', $arr[2]);
+
+        $arr [3]= true;
+        $this->assertSame('1', $arr[3]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringAppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::STRING);
+        $arr []= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringSetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::STRING);
+        $arr []= 'abc';
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringAppendInvalidUTF8Fail()
+    {
+        $arr = new RepeatedField(GPBType::STRING);
+        $hex = hex2bin("ff");
+        $arr []= $hex;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringSetInvalidUTF8Fail()
+    {
+        $arr = new RepeatedField(GPBType::STRING);
+        $arr []= 'abc';
+        $hex = hex2bin("ff");
+        $arr [0]= $hex;
+    }
+
+    #########################################################
+    # Test message field.
+    #########################################################
+
+    public function testMessage()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, TestMessage_Sub::class);
+
+        // Test append.
+        $sub_m = new TestMessage_Sub();
+        $sub_m->setA(1);
+        $arr []= $sub_m;
+        $this->assertSame(1, $arr[0]->getA());
+
+        $null = null;
+        $arr []= $null;
+        $this->assertNull($arr[1]);
+
+        $this->assertEquals(2, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = $null;
+            $this->assertNull($arr[$i]);
+        }
+
+        // Test set.
+        $arr [0]= $sub_m;
+        $this->assertSame(1, $arr[0]->getA());
+
+        $arr [1]= $null;
+        $this->assertNull($arr[1]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageAppendIntFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, TestMessage_Sub::class);
+        $arr []= 1;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetIntFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, TestMessage_Sub::class);
+        $arr []= new TestMessage_Sub;
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageAppendStringFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, TestMessage_Sub::class);
+        $arr []= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetStringFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, TestMessage_Sub::class);
+        $arr []= new TestMessage_Sub;
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageAppendOtherMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, TestMessage_Sub::class);
+        $arr []= new TestMessage;
+    }
+
+    #########################################################
+    # Test offset type
+    #########################################################
+
+    public function testOffset()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr []= 0;
+
+        $arr [0]= 1;
+        $this->assertSame(1, $arr[0]);
+        $this->assertSame(1, count($arr));
+
+        $arr ['0']= 2;
+        $this->assertSame(2, $arr['0']);
+        $this->assertSame(2, $arr[0]);
+        $this->assertSame(1, count($arr));
+
+        $arr [0.0]= 3;
+        $this->assertSame(3, $arr[0.0]);
+        $this->assertSame(1, count($arr));
+    }
+
+      public function testInsertRemoval()
+      {
+          $arr = new RepeatedField(GPBType::INT32);
+
+          $arr []= 0;
+          $arr []= 1;
+          $arr []= 2;
+          $this->assertSame(3, count($arr));
+
+          unset($arr[2]);
+          $this->assertSame(2, count($arr));
+          $this->assertSame(0, $arr[0]);
+          $this->assertSame(1, $arr[1]);
+
+          $arr [] = 3;
+          $this->assertSame(3, count($arr));
+          $this->assertSame(0, $arr[0]);
+          $this->assertSame(1, $arr[1]);
+          $this->assertSame(3, $arr[2]);
+      }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testRemoveMiddleFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+
+        $arr []= 0;
+        $arr []= 1;
+        $arr []= 2;
+        $this->assertSame(3, count($arr));
+
+        unset($arr[1]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testRemoveEmptyFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+
+        unset($arr[0]);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageOffsetFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr []= 0;
+        $arr [new TestMessage_Sub()]= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringOffsetFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr []= 0;
+        $arr ['abc']= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testSetNonExistedOffsetFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr [0]= 0;
+    }
+
+    #########################################################
+    # Test memory leak
+    #########################################################
+
+    public function testCycleLeak()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, TestMessage::class);
+        $arr []= new TestMessage;
+        $arr[0]->SetRepeatedRecursive($arr);
+
+        // Clean up memory before test.
+        gc_collect_cycles();
+        $start = memory_get_usage();
+        unset($arr);
+
+        // Explicitly trigger garbage collection.
+        gc_collect_cycles();
+
+        $end = memory_get_usage();
+        $this->assertLessThan($start, $end);
+    }
+}

+ 0 - 4
php/tests/autoload.php

@@ -1,4 +0,0 @@
-<?php
-
-require_once('test.pb.php');
-require_once('test_util.php');

+ 136 - 0
php/tests/encode_decode_test.php

@@ -0,0 +1,136 @@
+<?php
+
+require_once('test.pb.php');
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Google\Protobuf\RepeatedField;
+use Google\Protobuf\GPBType;
+use Foo\TestEnum;
+use Foo\TestMessage;
+use Foo\TestMessage_Sub;
+use Foo\TestPackedMessage;
+use Foo\TestUnpackedMessage;
+
+class EncodeDecodeTest extends TestBase
+{
+
+    public function testEncode()
+    {
+        $from = new TestMessage();
+        $this->expectEmptyFields($from);
+        $this->setFields($from);
+        $this->expectFields($from);
+
+        $data = $from->encode();
+        $this->assertSame(TestUtil::getGoldenTestMessage(), $data);
+    }
+
+    public function testDecode()
+    {
+        $to = new TestMessage();
+        $to->decode(TestUtil::getGoldenTestMessage());
+        $this->expectFields($to);
+    }
+
+    public function testEncodeDecode()
+    {
+        $from = new TestMessage();
+        $this->expectEmptyFields($from);
+        $this->setFields($from);
+        $this->expectFields($from);
+
+        $data = $from->encode();
+
+        $to = new TestMessage();
+        $to->decode($data);
+        $this->expectFields($to);
+    }
+
+    public function testEncodeDecodeEmpty()
+    {
+        $from = new TestMessage();
+        $this->expectEmptyFields($from);
+
+        $data = $from->encode();
+
+        $to = new TestMessage();
+        $to->decode($data);
+        $this->expectEmptyFields($to);
+    }
+
+    public function testEncodeDecodeOneof()
+    {
+        $m = new TestMessage();
+
+        $m->setOneofInt32(1);
+        $data = $m->encode();
+        $n = new TestMessage();
+        $n->decode($data);
+        $this->assertSame(1, $n->getOneofInt32());
+
+        $m->setOneofFloat(2.0);
+        $data = $m->encode();
+        $n = new TestMessage();
+        $n->decode($data);
+        $this->assertSame(2.0, $n->getOneofFloat());
+
+        $m->setOneofString('abc');
+        $data = $m->encode();
+        $n = new TestMessage();
+        $n->decode($data);
+        $this->assertSame('abc', $n->getOneofString());
+
+        $sub_m = new TestMessage_Sub();
+        $sub_m->setA(1);
+        $m->setOneofMessage($sub_m);
+        $data = $m->encode();
+        $n = new TestMessage();
+        $n->decode($data);
+        $this->assertSame(1, $n->getOneofMessage()->getA());
+    }
+
+    public function testPackedEncode()
+    {
+        $from = new TestPackedMessage();
+        TestUtil::setTestPackedMessage($from);
+        $this->assertSame(TestUtil::getGoldenTestPackedMessage(),
+                          $from->encode());
+    }
+
+    public function testPackedDecodePacked()
+    {
+        $to = new TestPackedMessage();
+        $to->decode(TestUtil::getGoldenTestPackedMessage());
+        TestUtil::assertTestPackedMessage($to);
+    }
+
+    public function testPackedDecodeUnpacked()
+    {
+        $to = new TestPackedMessage();
+        $to->decode(TestUtil::getGoldenTestUnpackedMessage());
+        TestUtil::assertTestPackedMessage($to);
+    }
+
+    public function testUnpackedEncode()
+    {
+        $from = new TestUnpackedMessage();
+        TestUtil::setTestPackedMessage($from);
+        $this->assertSame(TestUtil::getGoldenTestUnpackedMessage(),
+                          $from->encode());
+    }
+
+    public function testUnpackedDecodePacked()
+    {
+        $to = new TestUnpackedMessage();
+        $to->decode(TestUtil::getGoldenTestPackedMessage());
+        TestUtil::assertTestPackedMessage($to);
+    }
+
+    public function testUnpackedDecodeUnpacked()
+    {
+        $to = new TestUnpackedMessage();
+        $to->decode(TestUtil::getGoldenTestUnpackedMessage());
+        TestUtil::assertTestPackedMessage($to);
+    }
+}

+ 557 - 0
php/tests/generated_class_test.php

@@ -0,0 +1,557 @@
+<?php
+
+require_once('test.pb.php');
+require_once('test_util.php');
+
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\GPBType;
+use Foo\TestEnum;
+use Foo\TestMessage;
+use Foo\TestMessage_Sub;
+
+class GeneratedClassTest extends PHPUnit_Framework_TestCase
+{
+
+    #########################################################
+    # Test field accessors.
+    #########################################################
+
+    public function testSetterGetter()
+    {
+        $m = new TestMessage();
+        $m->setOptionalInt32(1);
+        $this->assertSame(1, $m->getOptionalInt32());
+    }
+
+    #########################################################
+    # Test int32 field.
+    #########################################################
+
+    public function testInt32Field()
+    {
+        $m = new TestMessage();
+
+        // Set integer.
+        $m->setOptionalInt32(MAX_INT32);
+        $this->assertSame(MAX_INT32, $m->getOptionalInt32());
+        $m->setOptionalInt32(MIN_INT32);
+        $this->assertSame(MIN_INT32, $m->getOptionalInt32());
+
+        // Set float.
+        $m->setOptionalInt32(1.1);
+        $this->assertSame(1, $m->getOptionalInt32());
+        $m->setOptionalInt32(MAX_INT32_FLOAT);
+        $this->assertSame(MAX_INT32, $m->getOptionalInt32());
+        $m->setOptionalInt32(MIN_INT32_FLOAT);
+        $this->assertSame(MIN_INT32, $m->getOptionalInt32());
+
+        // Set string.
+        $m->setOptionalInt32('2');
+        $this->assertSame(2, $m->getOptionalInt32());
+        $m->setOptionalInt32('3.1');
+        $this->assertSame(3, $m->getOptionalInt32());
+        $m->setOptionalInt32(MAX_INT32_STRING);
+        $this->assertSame(MAX_INT32, $m->getOptionalInt32());
+        $m->setOptionalInt32(MIN_INT32_STRING);
+        $this->assertSame(MIN_INT32, $m->getOptionalInt32());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32FieldInvalidTypeFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalInt32(new TestMessage());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32FieldInvalidStringFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalInt32('abc');
+    }
+
+    #########################################################
+    # Test uint32 field.
+    #########################################################
+
+    public function testUint32Field()
+    {
+        $m = new TestMessage();
+
+        // Set integer.
+        $m->setOptionalUint32(MAX_UINT32);
+        $this->assertSame(-1, $m->getOptionalUint32());
+        $m->setOptionalUint32(-1);
+        $this->assertSame(-1, $m->getOptionalUint32());
+        $m->setOptionalUint32(MIN_UINT32);
+        $this->assertSame(MIN_INT32, $m->getOptionalUint32());
+
+        // Set float.
+        $m->setOptionalUint32(1.1);
+        $this->assertSame(1, $m->getOptionalUint32());
+        $m->setOptionalUint32(MAX_UINT32_FLOAT);
+        $this->assertSame(-1, $m->getOptionalUint32());
+        $m->setOptionalUint32(-1.0);
+        $this->assertSame(-1, $m->getOptionalUint32());
+        $m->setOptionalUint32(MIN_UINT32_FLOAT);
+        $this->assertSame(MIN_INT32, $m->getOptionalUint32());
+
+        // Set string.
+        $m->setOptionalUint32('2');
+        $this->assertSame(2, $m->getOptionalUint32());
+        $m->setOptionalUint32('3.1');
+        $this->assertSame(3, $m->getOptionalUint32());
+        $m->setOptionalUint32(MAX_UINT32_STRING);
+        $this->assertSame(-1, $m->getOptionalUint32());
+        $m->setOptionalUint32('-1.0');
+        $this->assertSame(-1, $m->getOptionalUint32());
+        $m->setOptionalUint32(MIN_UINT32_STRING);
+        $this->assertSame(MIN_INT32, $m->getOptionalUint32());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32FieldInvalidTypeFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalUint32(new TestMessage());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32FieldInvalidStringFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalUint32('abc');
+    }
+
+    #########################################################
+    # Test int64 field.
+    #########################################################
+
+    public function testInt64Field()
+    {
+        $m = new TestMessage();
+
+        // Set integer.
+        $m->setOptionalInt64(MAX_INT64);
+        $this->assertSame(MAX_INT64, $m->getOptionalInt64());
+        $m->setOptionalInt64(MIN_INT64);
+        $this->assertEquals(MIN_INT64, $m->getOptionalInt64());
+
+        // Set float.
+        $m->setOptionalInt64(1.1);
+        $this->assertSame(1, $m->getOptionalInt64());
+
+        // Set string.
+        $m->setOptionalInt64('2');
+        $this->assertSame(2, $m->getOptionalInt64());
+        $m->setOptionalInt64('3.1');
+        $this->assertSame(3, $m->getOptionalInt64());
+        $m->setOptionalInt64(MAX_INT64_STRING);
+        $this->assertSame(MAX_INT64, $m->getOptionalInt64());
+        $m->setOptionalInt64(MIN_INT64_STRING);
+        $this->assertEquals(MIN_INT64, $m->getOptionalInt64());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64FieldInvalidTypeFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalInt64(new TestMessage());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64FieldInvalidStringFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalInt64('abc');
+    }
+
+    #########################################################
+    # Test uint64 field.
+    #########################################################
+
+    public function testUint64Field()
+    {
+        $m = new TestMessage();
+
+        // Set integer.
+        $m->setOptionalUint64(MAX_UINT64);
+        $this->assertEquals(MAX_UINT64, $m->getOptionalUint64());
+
+        // Set float.
+        $m->setOptionalUint64(1.1);
+        $this->assertSame(1, $m->getOptionalUint64());
+
+        // Set string.
+        $m->setOptionalUint64('2');
+        $this->assertSame(2, $m->getOptionalUint64());
+        $m->setOptionalUint64('3.1');
+        $this->assertSame(3, $m->getOptionalUint64());
+        $m->setOptionalUint64(MAX_UINT64_STRING);
+        $this->assertEquals(MAX_UINT64, $m->getOptionalUint64());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64FieldInvalidTypeFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalUint64(new TestMessage());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64FieldInvalidStringFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalUint64('abc');
+    }
+
+    #########################################################
+    # Test enum field.
+    #########################################################
+
+    public function testEnumField()
+    {
+        $m = new TestMessage();
+
+        // Set enum.
+        $m->setOptionalEnum(TestEnum::ONE);
+        $this->assertEquals(TestEnum::ONE, $m->getOptionalEnum());
+
+        // Set integer.
+        $m->setOptionalEnum(1);
+        $this->assertEquals(TestEnum::ONE, $m->getOptionalEnum());
+
+        // Set float.
+        $m->setOptionalEnum(1.1);
+        $this->assertEquals(TestEnum::ONE, $m->getOptionalEnum());
+
+        // Set string.
+        $m->setOptionalEnum("1");
+        $this->assertEquals(TestEnum::ONE, $m->getOptionalEnum());
+    }
+
+    #########################################################
+    # Test float field.
+    #########################################################
+
+    public function testFloatField()
+    {
+        $m = new TestMessage();
+
+        // Set integer.
+        $m->setOptionalFloat(1);
+        $this->assertEquals(1.0, $m->getOptionalFloat(), '', MAX_FLOAT_DIFF);
+
+        // Set float.
+        $m->setOptionalFloat(1.1);
+        $this->assertEquals(1.1, $m->getOptionalFloat(), '', MAX_FLOAT_DIFF);
+
+        // Set string.
+        $m->setOptionalFloat('2');
+        $this->assertEquals(2.0, $m->getOptionalFloat(), '', MAX_FLOAT_DIFF);
+        $m->setOptionalFloat('3.1');
+        $this->assertEquals(3.1, $m->getOptionalFloat(), '', MAX_FLOAT_DIFF);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatFieldInvalidTypeFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalFloat(new TestMessage());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatFieldInvalidStringFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalFloat('abc');
+    }
+
+    #########################################################
+    # Test double field.
+    #########################################################
+
+    public function testDoubleField()
+    {
+        $m = new TestMessage();
+
+        // Set integer.
+        $m->setOptionalDouble(1);
+        $this->assertEquals(1.0, $m->getOptionalDouble(), '', MAX_FLOAT_DIFF);
+
+        // Set float.
+        $m->setOptionalDouble(1.1);
+        $this->assertEquals(1.1, $m->getOptionalDouble(), '', MAX_FLOAT_DIFF);
+
+        // Set string.
+        $m->setOptionalDouble('2');
+        $this->assertEquals(2.0, $m->getOptionalDouble(), '', MAX_FLOAT_DIFF);
+        $m->setOptionalDouble('3.1');
+        $this->assertEquals(3.1, $m->getOptionalDouble(), '', MAX_FLOAT_DIFF);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleFieldInvalidTypeFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalDouble(new TestMessage());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleFieldInvalidStringFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalDouble('abc');
+    }
+
+    #########################################################
+    # Test bool field.
+    #########################################################
+
+    public function testBoolField()
+    {
+        $m = new TestMessage();
+
+        // Set bool.
+        $m->setOptionalBool(true);
+        $this->assertSame(true, $m->getOptionalBool());
+
+        // Set integer.
+        $m->setOptionalBool(-1);
+        $this->assertSame(true, $m->getOptionalBool());
+
+        // Set float.
+        $m->setOptionalBool(1.1);
+        $this->assertSame(true, $m->getOptionalBool());
+
+        // Set string.
+        $m->setOptionalBool('');
+        $this->assertSame(false, $m->getOptionalBool());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolFieldInvalidStringFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalBool(new TestMessage());
+    }
+
+    #########################################################
+    # Test string field.
+    #########################################################
+
+    public function testStringField()
+    {
+        $m = new TestMessage();
+
+        // Set string.
+        $m->setOptionalString('abc');
+        $this->assertSame('abc', $m->getOptionalString());
+
+        // Set integer.
+        $m->setOptionalString(1);
+        $this->assertSame('1', $m->getOptionalString());
+
+        // Set double.
+        $m->setOptionalString(1.1);
+        $this->assertSame('1.1', $m->getOptionalString());
+
+        // Set bool.
+        $m->setOptionalString(true);
+        $this->assertSame('1', $m->getOptionalString());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringFieldInvalidUTF8Fail()
+    {
+        $m = new TestMessage();
+        $hex = hex2bin("ff");
+        $m->setOptionalString($hex);
+    }
+
+    #########################################################
+    # Test bytes field.
+    #########################################################
+
+    public function testBytesField()
+    {
+        $m = new TestMessage();
+
+        // Set string.
+        $m->setOptionalBytes('abc');
+        $this->assertSame('abc', $m->getOptionalBytes());
+
+        // Set integer.
+        $m->setOptionalBytes(1);
+        $this->assertSame('1', $m->getOptionalBytes());
+
+        // Set double.
+        $m->setOptionalBytes(1.1);
+        $this->assertSame('1.1', $m->getOptionalBytes());
+
+        // Set bool.
+        $m->setOptionalBytes(true);
+        $this->assertSame('1', $m->getOptionalBytes());
+    }
+
+      public function testBytesFieldInvalidUTF8Success()
+      {
+          $m = new TestMessage();
+          $hex = hex2bin("ff");
+          $m->setOptionalBytes($hex);
+      }
+
+    #########################################################
+    # Test message field.
+    #########################################################
+
+    public function testMessageField()
+    {
+        $m = new TestMessage();
+
+        $sub_m = new TestMessage_Sub();
+        $sub_m->setA(1);
+        $m->setOptionalMessage($sub_m);
+        $this->assertSame(1, $m->getOptionalMessage()->getA());
+
+        $null = null;
+        $m->setOptionalMessage($null);
+        $this->assertNull($m->getOptionalMessage());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageFieldWrongTypeFail()
+    {
+        $m = new TestMessage();
+        $a = 1;
+        $m->setOptionalMessage($a);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageFieldWrongClassFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalMessage(new TestMessage());
+    }
+
+    #########################################################
+    # Test repeated field.
+    #########################################################
+
+    public function testRepeatedField()
+    {
+        $m = new TestMessage();
+
+        $repeated_int32 = new RepeatedField(GPBType::INT32);
+        $m->setRepeatedInt32($repeated_int32);
+        $this->assertSame($repeated_int32, $m->getRepeatedInt32());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testRepeatedFieldWrongTypeFail()
+    {
+        $m = new TestMessage();
+        $a = 1;
+        $m->setRepeatedInt32($a);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testRepeatedFieldWrongObjectFail()
+    {
+        $m = new TestMessage();
+        $m->setRepeatedInt32($m);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testRepeatedFieldWrongRepeatedTypeFail()
+    {
+        $m = new TestMessage();
+
+        $repeated_int32 = new RepeatedField(GPBType::UINT32);
+        $m->setRepeatedInt32($repeated_int32);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testRepeatedFieldWrongRepeatedMessageClassFail()
+    {
+        $m = new TestMessage();
+
+        $repeated_message = new RepeatedField(GPBType::MESSAGE,
+                                              TestMessage::class);
+        $m->setRepeatedMessage($repeated_message);
+    }
+
+    #########################################################
+    # Test oneof field.
+    #########################################################
+
+    public function testOneofField() {
+        $m = new TestMessage();
+
+        $m->setOneofInt32(1);
+        $this->assertSame(1, $m->getOneofInt32());
+        $this->assertSame(0.0, $m->getOneofFloat());
+        $this->assertSame('', $m->getOneofString());
+        $this->assertSame(NULL, $m->getOneofMessage());
+
+        $m->setOneofFloat(2.0);
+        $this->assertSame(0, $m->getOneofInt32());
+        $this->assertSame(2.0, $m->getOneofFloat());
+        $this->assertSame('', $m->getOneofString());
+        $this->assertSame(NULL, $m->getOneofMessage());
+
+        $m->setOneofString('abc');
+        $this->assertSame(0, $m->getOneofInt32());
+        $this->assertSame(0.0, $m->getOneofFloat());
+        $this->assertSame('abc', $m->getOneofString());
+        $this->assertSame(NULL, $m->getOneofMessage());
+
+        $sub_m = new TestMessage_Sub();
+        $sub_m->setA(1);
+        $m->setOneofMessage($sub_m);
+        $this->assertSame(0, $m->getOneofInt32());
+        $this->assertSame(0.0, $m->getOneofFloat());
+        $this->assertSame('', $m->getOneofString());
+        $this->assertSame(1, $m->getOneofMessage()->getA());
+    }
+}

+ 648 - 0
php/tests/map_field_test.php

@@ -0,0 +1,648 @@
+<?php
+
+require_once('test.pb.php');
+require_once('test_util.php');
+
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\MapField;
+use Foo\TestMessage;
+use Foo\TestMessage_Sub;
+
+class MapFieldTest extends PHPUnit_Framework_TestCase {
+
+    #########################################################
+    # Test int32 field.
+    #########################################################
+
+    public function testInt32() {
+        $arr = new MapField(GPBType::INT32, GPBType::INT32);
+
+        // Test integer argument.
+        $arr[MAX_INT32] = MAX_INT32;
+        $this->assertSame(MAX_INT32, $arr[MAX_INT32]);
+        $arr[MIN_INT32] = MIN_INT32;
+        $this->assertSame(MIN_INT32, $arr[MIN_INT32]);
+        $this->assertEquals(2, count($arr));
+        $this->assertTrue(isset($arr[MAX_INT32]));
+        $this->assertTrue(isset($arr[MIN_INT32]));
+        unset($arr[MAX_INT32]);
+        unset($arr[MIN_INT32]);
+        $this->assertEquals(0, count($arr));
+
+        // Test float argument.
+        $arr[1.9] = 1.9;
+        $arr[2.1] = 2.1;
+        $this->assertSame(1, $arr[1]);
+        $this->assertSame(2, $arr[2]);
+        $arr[MAX_INT32_FLOAT] = MAX_INT32_FLOAT;
+        $this->assertSame(MAX_INT32, $arr[MAX_INT32]);
+        $arr[MIN_INT32_FLOAT] = MIN_INT32_FLOAT;
+        $this->assertSame(MIN_INT32, $arr[MIN_INT32]);
+        $this->assertEquals(4, count($arr));
+        unset($arr[1.9]);
+        unset($arr[2.9]);
+        unset($arr[MAX_INT32_FLOAT]);
+        unset($arr[MIN_INT32_FLOAT]);
+        $this->assertEquals(0, count($arr));
+
+        // Test string argument.
+        $arr['2'] = '2';
+        $this->assertSame(2, $arr[2]);
+        $arr['3.1'] = '3.1';
+        $this->assertSame(3, $arr[3]);
+        $arr[MAX_INT32_STRING] = MAX_INT32_STRING;
+        $this->assertSame(MAX_INT32, $arr[MAX_INT32]);
+        $this->assertEquals(3, count($arr));
+        unset($arr['2']);
+        unset($arr['3.1']);
+        unset($arr[MAX_INT32_STRING]);
+        $this->assertEquals(0, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32SetStringKeyFail()
+    {
+        $arr = new MapField(GPBType::INT32, GPBType::INT32);
+        $arr ['abc']= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32SetStringValueFail()
+    {
+        $arr = new MapField(GPBType::INT32, GPBType::INT32);
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32SetMessageKeyFail()
+    {
+        $arr = new MapField(GPBType::INT32, GPBType::INT32);
+        $arr [new TestMessage_Sub()]= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32SetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::INT32, GPBType::INT32);
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test uint32 field.
+    #########################################################
+
+    public function testUint32() {
+        $arr = new MapField(GPBType::UINT32, GPBType::UINT32);
+
+        // Test integer argument.
+        $arr[MAX_UINT32] = MAX_UINT32;
+        $this->assertSame(-1, $arr[-1]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[MAX_UINT32]);
+        $this->assertEquals(0, count($arr));
+
+        $arr[-1] = -1;
+        $this->assertSame(-1, $arr[-1]);
+        $arr[MIN_UINT32] = MIN_UINT32;
+        $this->assertSame(MIN_UINT32, $arr[MIN_UINT32]);
+        $this->assertEquals(2, count($arr));
+        unset($arr[-1]);
+        unset($arr[MIN_UINT32]);
+        $this->assertEquals(0, count($arr));
+
+        // Test float argument.
+        $arr[MAX_UINT32_FLOAT] = MAX_UINT32_FLOAT;
+        $this->assertSame(-1, $arr[-1]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[MAX_UINT32_FLOAT]);
+        $this->assertEquals(0, count($arr));
+
+        $arr[3.1] = 3.1;
+        $this->assertSame(3, $arr[3]);
+        $arr[-1.0] = -1.0;
+        $this->assertSame(-1, $arr[-1]);
+        $arr[MIN_UINT32_FLOAT] = MIN_UINT32_FLOAT;
+        $this->assertSame(MIN_UINT32, $arr[MIN_UINT32]);
+        $this->assertEquals(3, count($arr));
+        unset($arr[3.1]);
+        unset($arr[-1.0]);
+        unset($arr[MIN_UINT32_FLOAT]);
+        $this->assertEquals(0, count($arr));
+
+        // Test string argument.
+        $arr[MAX_UINT32_STRING] = MAX_UINT32_STRING;
+        $this->assertSame(-1, $arr[-1]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[MAX_UINT32_STRING]);
+        $this->assertEquals(0, count($arr));
+
+        $arr['7'] = '7';
+        $this->assertSame(7, $arr[7]);
+        $arr['3.1'] = '3.1';
+        $this->assertSame(3, $arr[3]);
+        $arr['-1.0'] = '-1.0';
+        $this->assertSame(-1, $arr[-1]);
+        $arr[MIN_UINT32_STRING] = MIN_UINT32_STRING;
+        $this->assertSame(MIN_UINT32, $arr[MIN_UINT32]);
+        $this->assertEquals(4, count($arr));
+        unset($arr['7']);
+        unset($arr['3.1']);
+        unset($arr['-1.0']);
+        unset($arr[MIN_UINT32_STRING]);
+        $this->assertEquals(0, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32SetStringKeyFail()
+    {
+        $arr = new MapField(GPBType::UINT32, GPBType::UINT32);
+        $arr ['abc']= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32SetStringValueFail()
+    {
+        $arr = new MapField(GPBType::UINT32, GPBType::UINT32);
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32SetMessageKeyFail()
+    {
+        $arr = new MapField(GPBType::UINT32, GPBType::UINT32);
+        $arr [new TestMessage_Sub()]= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32SetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::UINT32, GPBType::UINT32);
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test int64 field.
+    #########################################################
+
+    public function testInt64() {
+        $arr = new MapField(GPBType::INT64, GPBType::INT64);
+
+        // Test integer argument.
+        $arr[MAX_INT64] = MAX_INT64;
+        $this->assertSame(MAX_INT64, $arr[MAX_INT64]);
+        $arr[MIN_INT64] = MIN_INT64;
+        $this->assertEquals(MIN_INT64, $arr[MIN_INT64]);
+        $this->assertEquals(2, count($arr));
+        unset($arr[MAX_INT64]);
+        unset($arr[MIN_INT64]);
+        $this->assertEquals(0, count($arr));
+
+        // Test float argument.
+        $arr[1.1] = 1.1;
+        $this->assertSame(1, $arr[1]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[1.1]);
+        $this->assertEquals(0, count($arr));
+
+        // Test string argument.
+        $arr['2'] = '2';
+        $this->assertSame(2, $arr[2]);
+        $arr['3.1'] = '3.1';
+        $this->assertSame(3, $arr[3]);
+        $arr[MAX_INT64_STRING] = MAX_INT64_STRING;
+        $this->assertSame(MAX_INT64, $arr[MAX_INT64]);
+        $arr[MIN_INT64_STRING] = MIN_INT64_STRING;
+        $this->assertEquals(MIN_INT64, $arr[MIN_INT64]);
+        $this->assertEquals(4, count($arr));
+        unset($arr['2']);
+        unset($arr['3.1']);
+        unset($arr[MAX_INT64_STRING]);
+        unset($arr[MIN_INT64_STRING]);
+        $this->assertEquals(0, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64SetStringKeyFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::INT64);
+        $arr ['abc']= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64SetStringValueFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::INT64);
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64SetMessageKeyFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::INT64);
+        $arr [new TestMessage_Sub()]= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64SetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::INT64);
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test uint64 field.
+    #########################################################
+
+    public function testUint64() {
+        $arr = new MapField(GPBType::UINT64, GPBType::UINT64);
+
+        // Test integer argument.
+        $arr[MAX_UINT64] = MAX_UINT64;
+        $this->assertEquals(MAX_UINT64, $arr[MAX_UINT64]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[MAX_UINT64]);
+        $this->assertEquals(0, count($arr));
+
+        // Test float argument.
+        $arr[1.1] = 1.1;
+        $this->assertSame(1, $arr[1]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[1.1]);
+        $this->assertEquals(0, count($arr));
+
+        // Test string argument.
+        $arr['2'] = '2';
+        $this->assertSame(2, $arr[2]);
+        $arr['3.1'] = '3.1';
+        $this->assertSame(3, $arr[3]);
+        $arr[MAX_UINT64_STRING] = MAX_UINT64_STRING;
+        $this->assertEquals(MAX_UINT64, $arr[MAX_UINT64]);
+        $this->assertEquals(3, count($arr));
+        unset($arr['2']);
+        unset($arr['3.1']);
+        unset($arr[MAX_UINT64_STRING]);
+        $this->assertEquals(0, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64SetStringKeyFail()
+    {
+        $arr = new MapField(GPBType::UINT64, GPBType::UINT64);
+        $arr ['abc']= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64SetStringValueFail()
+    {
+        $arr = new MapField(GPBType::UINT64, GPBType::UINT64);
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64SetMessageKeyFail()
+    {
+        $arr = new MapField(GPBType::UINT64, GPBType::UINT64);
+        $arr [new TestMessage_Sub()]= 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64SetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::UINT64, GPBType::UINT64);
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test float field.
+    #########################################################
+
+    public function testFloat() {
+        $arr = new MapField(GPBType::INT32, GPBType::FLOAT);
+
+        // Test set.
+        $arr[0] = 1;
+        $this->assertEquals(1.0, $arr[0], '', MAX_FLOAT_DIFF);
+
+        $arr[1] = 1.1;
+        $this->assertEquals(1.1, $arr[1], '', MAX_FLOAT_DIFF);
+
+        $arr[2] = '2';
+        $this->assertEquals(2.0, $arr[2], '', MAX_FLOAT_DIFF);
+        $arr[3] = '3.1';
+        $this->assertEquals(3.1, $arr[3], '', MAX_FLOAT_DIFF);
+
+        $this->assertEquals(4, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatSetStringValueFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::FLOAT);
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatSetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::FLOAT);
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test double field.
+    #########################################################
+
+    public function testDouble() {
+        $arr = new MapField(GPBType::INT32, GPBType::DOUBLE);
+
+        // Test set.
+        $arr[0] = 1;
+        $this->assertEquals(1.0, $arr[0], '', MAX_FLOAT_DIFF);
+
+        $arr[1] = 1.1;
+        $this->assertEquals(1.1, $arr[1], '', MAX_FLOAT_DIFF);
+
+        $arr[2] = '2';
+        $this->assertEquals(2.0, $arr[2], '', MAX_FLOAT_DIFF);
+        $arr[3] = '3.1';
+        $this->assertEquals(3.1, $arr[3], '', MAX_FLOAT_DIFF);
+
+        $this->assertEquals(4, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleSetStringValueFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::DOUBLE);
+        $arr [0]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleSetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::DOUBLE);
+        $arr [0]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test bool field.
+    #########################################################
+
+    public function testBool() {
+        $arr = new MapField(GPBType::BOOL, GPBType::BOOL);
+
+        // Test boolean.
+        $arr[True] = True;
+        $this->assertSame(True, $arr[True]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[True]);
+        $this->assertEquals(0, count($arr));
+
+        $arr[False] = False;
+        $this->assertSame(False, $arr[False]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[False]);
+        $this->assertEquals(0, count($arr));
+
+        // Test integer.
+        $arr[-1] = -1;
+        $this->assertSame(True, $arr[True]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[-1]);
+        $this->assertEquals(0, count($arr));
+
+        $arr[0] = 0;
+        $this->assertSame(False, $arr[False]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[0]);
+        $this->assertEquals(0, count($arr));
+
+        // Test float.
+        $arr[1.1] = 1.1;
+        $this->assertSame(True, $arr[True]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[1.1]);
+        $this->assertEquals(0, count($arr));
+
+        $arr[0.0] = 0.0;
+        $this->assertSame(False, $arr[False]);
+        $this->assertEquals(1, count($arr));
+        unset($arr[0.0]);
+        $this->assertEquals(0, count($arr));
+
+        // Test string.
+        $arr['a'] = 'a';
+        $this->assertSame(True, $arr[True]);
+        $this->assertEquals(1, count($arr));
+        unset($arr['a']);
+        $this->assertEquals(0, count($arr));
+
+        $arr[''] = '';
+        $this->assertSame(False, $arr[False]);
+        $this->assertEquals(1, count($arr));
+        unset($arr['']);
+        $this->assertEquals(0, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolSetMessageKeyFail()
+    {
+        $arr = new MapField(GPBType::BOOL, GPBType::BOOL);
+        $arr [new TestMessage_Sub()]= true;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolSetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::BOOL, GPBType::BOOL);
+        $arr [true]= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test string field.
+    #########################################################
+
+    public function testString() {
+        $arr = new MapField(GPBType::STRING, GPBType::STRING);
+
+        // Test set.
+        $arr['abc'] = 'abc';
+        $this->assertSame('abc', $arr['abc']);
+        $this->assertEquals(1, count($arr));
+        unset($arr['abc']);
+        $this->assertEquals(0, count($arr));
+
+        $arr[1] = 1;
+        $this->assertSame('1', $arr['1']);
+        $this->assertEquals(1, count($arr));
+        unset($arr[1]);
+        $this->assertEquals(0, count($arr));
+
+        $arr[1.1] = 1.1;
+        $this->assertSame('1.1', $arr['1.1']);
+        $this->assertEquals(1, count($arr));
+        unset($arr[1.1]);
+        $this->assertEquals(0, count($arr));
+
+        $arr[True] = True;
+        $this->assertSame('1', $arr['1']);
+        $this->assertEquals(1, count($arr));
+        unset($arr[True]);
+        $this->assertEquals(0, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringSetInvalidUTF8KeyFail()
+    {
+        $arr = new MapField(GPBType::STRING, GPBType::STRING);
+        $arr[hex2bin("ff")]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringSetInvalidUTF8ValueFail()
+    {
+        $arr = new MapField(GPBType::STRING, GPBType::STRING);
+        $arr ['abc']= hex2bin("ff");
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringSetMessageKeyFail()
+    {
+        $arr = new MapField(GPBType::STRING, GPBType::STRING);
+        $arr [new TestMessage_Sub()]= 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringSetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::STRING, GPBType::STRING);
+        $arr ['abc']= new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test message field.
+    #########################################################
+
+    public function testMessage() {
+        $arr = new MapField(GPBType::INT32,
+            GPBType::MESSAGE, TestMessage_Sub::class);
+
+        // Test append.
+        $sub_m = new TestMessage_Sub();
+        $sub_m->setA(1);
+        $arr[0] = $sub_m;
+        $this->assertSame(1, $arr[0]->getA());
+
+        $null = NULL;
+        $arr[1] = $null;
+        $this->assertNull($arr[1]);
+
+        $this->assertEquals(2, count($arr));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetIntValueFail()
+    {
+       $arr =
+           new MapField(GPBType::INT32, GPBType::MESSAGE, TestMessage::class);
+       $arr[0] = 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetStringValueFail()
+    {
+       $arr =
+           new MapField(GPBType::INT32, GPBType::MESSAGE, TestMessage::class);
+       $arr[0] = 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetOtherMessageValueFail()
+    {
+       $arr =
+           new MapField(GPBType::INT32, GPBType::MESSAGE, TestMessage::class);
+       $arr[0] = new TestMessage_Sub();
+    }
+
+    #########################################################
+    # Test memory leak
+    #########################################################
+
+    // TODO(teboring): Add it back.
+    // public function testCycleLeak()
+    // {
+    //     $arr = new MapField(GPBType::INT32,
+    //         GPBType::MESSAGE, TestMessage::class);
+    //     $arr [0]= new TestMessage;
+    //     $arr[0]->SetMapRecursive($arr);
+
+    //     // Clean up memory before test.
+    //     gc_collect_cycles();
+    //     $start = memory_get_usage();
+    //     unset($arr);
+
+    //     // Explicitly trigger garbage collection.
+    //     gc_collect_cycles();
+
+    //     $end = memory_get_usage();
+    //     $this->assertLessThan($start, $end);
+    // }
+}

+ 73 - 0
php/tests/memory_leak_test.php

@@ -0,0 +1,73 @@
+<?php
+
+# phpunit has memory leak by itself. Thus, it cannot be used to test memory leak.
+
+require_once('test.pb.php');
+require_once('test_util.php');
+
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\GPBType;
+use Foo\TestMessage;
+use Foo\TestMessage_Sub;
+
+$from = new TestMessage();
+TestUtil::setTestMessage($from);
+TestUtil::assertTestMessage($from);
+
+$data = $from->encode();
+
+$to = new TestMessage();
+$to->decode($data);
+
+TestUtil::assertTestMessage($to);
+
+$from->setRecursive($from);
+
+$arr = new RepeatedField(GPBType::MESSAGE, TestMessage::class);
+$arr []= new TestMessage;
+$arr[0]->SetRepeatedRecursive($arr);
+
+// Test oneof fields.
+$m = new TestMessage();
+
+$m->setOneofInt32(1);
+assert(1 === $m->getOneofInt32());
+assert(0.0 === $m->getOneofFloat());
+assert('' === $m->getOneofString());
+assert(NULL === $m->getOneofMessage());
+$data = $m->encode();
+$n = new TestMessage();
+$n->decode($data);
+assert(1 === $n->getOneofInt32());
+
+$m->setOneofFloat(2.0);
+assert(0 === $m->getOneofInt32());
+assert(2.0 === $m->getOneofFloat());
+assert('' === $m->getOneofString());
+assert(NULL === $m->getOneofMessage());
+$data = $m->encode();
+$n = new TestMessage();
+$n->decode($data);
+assert(2.0 === $n->getOneofFloat());
+
+$m->setOneofString('abc');
+assert(0 === $m->getOneofInt32());
+assert(0.0 === $m->getOneofFloat());
+assert('abc' === $m->getOneofString());
+assert(NULL === $m->getOneofMessage());
+$data = $m->encode();
+$n = new TestMessage();
+$n->decode($data);
+assert('abc' === $n->getOneofString());
+
+$sub_m = new TestMessage_Sub();
+$sub_m->setA(1);
+$m->setOneofMessage($sub_m);
+assert(0 === $m->getOneofInt32());
+assert(0.0 === $m->getOneofFloat());
+assert('' === $m->getOneofString());
+assert(1 === $m->getOneofMessage()->getA());
+$data = $m->encode();
+$n = new TestMessage();
+$n->decode($data);
+assert(1 === $n->getOneofMessage()->getA());

+ 443 - 0
php/tests/php_implementation_test.php

@@ -0,0 +1,443 @@
+<?php
+
+require_once('test.pb.php');
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Foo\TestMessage;
+use Foo\TestMessage_Sub;
+use Foo\TestPackedMessage;
+use Google\Protobuf\Internal\InputStream;
+use Google\Protobuf\Internal\FileDescriptorSet;
+use Google\Protobuf\Internal\GPBUtil;
+use Google\Protobuf\Internal\Int64;
+use Google\Protobuf\Internal\Uint64;
+use Google\Protobuf\Internal\GPBLabel;
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\GPBWire;
+use Google\Protobuf\Internal\OutputStream;
+use Google\Protobuf\Internal\RepeatedField;
+
+class ImplementationTest extends TestBase
+{
+
+    public function testReadInt32()
+    {
+        $value = null;
+
+        // Positive number.
+        $input = new InputStream(hex2bin("01"));
+        GPBWire::readInt32($input, $value);
+        $this->assertSame(1, $value);
+
+        // Negative number.
+        $input = new InputStream(hex2bin("ffffffff0f"));
+        GPBWire::readInt32($input, $value);
+        $this->assertSame(-1, $value);
+
+        // Discard overflow bits.
+        $input = new InputStream(hex2bin("ffffffff7f"));
+        GPBWire::readInt32($input, $value);
+        $this->assertSame(-1, $value);
+    }
+
+    public function testReadUint32()
+    {
+        $value = null;
+
+        // Positive number.
+        $input = new InputStream(hex2bin("01"));
+        GPBWire::readUint32($input, $value);
+        $this->assertSame(1, $value);
+
+        // Max uint32.
+        $input = new InputStream(hex2bin("ffffffff0f"));
+        GPBWire::readUint32($input, $value);
+        $this->assertSame(-1, $value);
+
+        // Discard overflow bits.
+        $input = new InputStream(hex2bin("ffffffff7f"));
+        GPBWire::readUint32($input, $value);
+        $this->assertSame(-1, $value);
+    }
+
+    public function testReadInt64()
+    {
+        $value = null;
+
+        // Positive number.
+        $input = new InputStream(hex2bin("01"));
+        GPBWire::readInt64($input, $value);
+        $this->assertSame(1, $value->toInteger());
+
+        // Negative number.
+        $input = new InputStream(hex2bin("ffffffffffffffffff01"));
+        GPBWire::readInt64($input, $value);
+        $this->assertSame(-1, $value->toInteger());
+
+        // Discard overflow bits.
+        $input = new InputStream(hex2bin("ffffffffffffffffff0f"));
+        GPBWire::readInt64($input, $value);
+        $this->assertSame(-1, $value->toInteger());
+    }
+
+    public function testReadUint64()
+    {
+        $value = null;
+
+        // Positive number.
+        $input = new InputStream(hex2bin("01"));
+        GPBWire::readUint64($input, $value);
+        $this->assertSame(1, $value->toInteger());
+
+        // Negative number.
+        $input = new InputStream(hex2bin("FFFFFFFFFFFFFFFFFF01"));
+        GPBWire::readUint64($input, $value);
+        $this->assertSame(-1, $value->toInteger());
+
+        // Discard overflow bits.
+        $input = new InputStream(hex2bin("FFFFFFFFFFFFFFFFFF0F"));
+        GPBWire::readUint64($input, $value);
+        $this->assertSame(-1, $value->toInteger());
+    }
+
+    public function testReadSint32()
+    {
+        $value = null;
+
+        $input = new InputStream(hex2bin("00"));
+        GPBWire::readSint32($input, $value);
+        $this->assertSame(0, $value);
+
+        $input = new InputStream(hex2bin("01"));
+        GPBWire::readSint32($input, $value);
+        $this->assertSame(-1, $value);
+
+        $input = new InputStream(hex2bin("02"));
+        GPBWire::readSint32($input, $value);
+        $this->assertSame(1, $value);
+    }
+
+    public function testReadSint64()
+    {
+        $value = null;
+
+        $input = new InputStream(hex2bin("00"));
+        GPBWire::readSint64($input, $value);
+        $this->assertEquals(GPBUtil::Int64(0), $value);
+
+        $input = new InputStream(hex2bin("01"));
+        GPBWire::readSint64($input, $value);
+        $this->assertEquals(GPBUtil::Int64(-1), $value);
+
+        $input = new InputStream(hex2bin("02"));
+        GPBWire::readSint64($input, $value);
+        $this->assertEquals(GPBUtil::Int64(1), $value);
+    }
+
+    public function testReadFixed32()
+    {
+        $value = null;
+        $input = new InputStream(hex2bin("12345678"));
+        GPBWire::readFixed32($input, $value);
+        $this->assertSame(0x78563412, $value);
+    }
+
+    public function testReadFixed64()
+    {
+        $value = null;
+        $input = new InputStream(hex2bin("1234567812345678"));
+        GPBWire::readFixed64($input, $value);
+        $this->assertEquals(Uint64::newValue(0x78563412, 0x78563412), $value);
+    }
+
+    public function testReadSfixed32()
+    {
+        $value = null;
+        $input = new InputStream(hex2bin("12345678"));
+        GPBWire::readSfixed32($input, $value);
+        $this->assertSame(0x78563412, $value);
+    }
+
+    public function testReadFloat()
+    {
+        $value = null;
+        $input = new InputStream(hex2bin("0000803F"));
+        GPBWire::readFloat($input, $value);
+        $this->assertSame(1.0, $value);
+    }
+
+    public function testReadBool()
+    {
+        $value = null;
+
+        $input = new InputStream(hex2bin("00"));
+        GPBWire::readBool($input, $value);
+        $this->assertSame(false, $value);
+
+        $input = new InputStream(hex2bin("01"));
+        GPBWire::readBool($input, $value);
+        $this->assertSame(true, $value);
+    }
+
+    public function testReadDouble()
+    {
+        $value = null;
+        $input = new InputStream(hex2bin("000000000000F03F"));
+        GPBWire::readDouble($input, $value);
+        $this->assertSame(1.0, $value);
+    }
+
+    public function testReadSfixed64()
+    {
+        $value = null;
+        $input = new InputStream(hex2bin("1234567812345678"));
+        GPBWire::readSfixed64($input, $value);
+        $this->assertEquals(Int64::newValue(0x78563412, 0x78563412), $value);
+    }
+
+    public function testZigZagEncodeDecode()
+    {
+        $this->assertSame(0, GPBWire::zigZagEncode32(0));
+        $this->assertSame(1, GPBWire::zigZagEncode32(-1));
+        $this->assertSame(2, GPBWire::zigZagEncode32(1));
+        $this->assertSame(3, GPBWire::zigZagEncode32(-2));
+        $this->assertSame(0x7FFFFFFE, GPBWire::zigZagEncode32(0x3FFFFFFF));
+        $this->assertSame(0x7FFFFFFF, GPBWire::zigZagEncode32(0xC0000000));
+        $this->assertSame(-2, GPBWire::zigZagEncode32(0x7FFFFFFF));
+        $this->assertSame(-1, GPBWire::zigZagEncode32(0x80000000));
+
+        $this->assertSame(0,  GPBWire::zigZagDecode32(0));
+        $this->assertSame(-1, GPBWire::zigZagDecode32(1));
+        $this->assertSame(1,  GPBWire::zigZagDecode32(2));
+        $this->assertSame(-2, GPBWire::zigZagDecode32(3));
+        $this->assertSame(0x3FFFFFFF,  GPBWire::zigZagDecode32(0x7FFFFFFE));
+        $this->assertSame(-1073741824, GPBWire::zigZagDecode32(0x7FFFFFFF));
+        $this->assertSame(0x7FFFFFFF,  GPBWire::zigZagDecode32(0xFFFFFFFE));
+        $this->assertSame(-2147483648, GPBWire::zigZagDecode32(0xFFFFFFFF));
+
+        $this->assertEquals(GPBUtil::Uint64(0),
+                        GPBWire::zigZagEncode64(GPBUtil::Int64(0)));
+        $this->assertEquals(GPBUtil::Uint64(1),
+                        GPBWire::zigZagEncode64(GPBUtil::Int64(-1)));
+        $this->assertEquals(GPBUtil::Uint64(2),
+                        GPBWire::zigZagEncode64(GPBUtil::Int64(1)));
+        $this->assertEquals(GPBUtil::Uint64(3),
+                        GPBWire::zigZagEncode64(GPBUtil::Int64(-2)));
+        $this->assertEquals(
+        GPBUtil::Uint64(0x000000007FFFFFFE),
+        GPBWire::zigZagEncode64(GPBUtil::Int64(0x000000003FFFFFFF)));
+        $this->assertEquals(
+        GPBUtil::Uint64(0x000000007FFFFFFF),
+        GPBWire::zigZagEncode64(GPBUtil::Int64(0xFFFFFFFFC0000000)));
+        $this->assertEquals(
+        GPBUtil::Uint64(0x00000000FFFFFFFE),
+        GPBWire::zigZagEncode64(GPBUtil::Int64(0x000000007FFFFFFF)));
+        $this->assertEquals(
+        GPBUtil::Uint64(0x00000000FFFFFFFF),
+        GPBWire::zigZagEncode64(GPBUtil::Int64(0xFFFFFFFF80000000)));
+        $this->assertEquals(
+        Uint64::newValue(4294967295, 4294967294),
+        GPBWire::zigZagEncode64(GPBUtil::Int64(0x7FFFFFFFFFFFFFFF)));
+        $this->assertEquals(
+        Uint64::newValue(4294967295, 4294967295),
+        GPBWire::zigZagEncode64(GPBUtil::Int64(0x8000000000000000)));
+
+        $this->assertEquals(GPBUtil::Int64(0),
+                        GPBWire::zigZagDecode64(GPBUtil::Uint64(0)));
+        $this->assertEquals(GPBUtil::Int64(-1),
+                        GPBWire::zigZagDecode64(GPBUtil::Uint64(1)));
+        $this->assertEquals(GPBUtil::Int64(1),
+                        GPBWire::zigZagDecode64(GPBUtil::Uint64(2)));
+        $this->assertEquals(GPBUtil::Int64(-2),
+                        GPBWire::zigZagDecode64(GPBUtil::Uint64(3)));
+
+        // Round trip
+        $this->assertSame(0, GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(0)));
+        $this->assertSame(1, GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(1)));
+        $this->assertSame(-1, GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(-1)));
+        $this->assertSame(14927,
+                      GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(14927)));
+        $this->assertSame(-3612,
+                      GPBWire::zigZagDecode32(GPBWire::zigZagEncode32(-3612)));
+    }
+
+    public function testDecode()
+    {
+        $m = new TestMessage();
+        $m->decode(TestUtil::getGoldenTestMessage());
+        TestUtil::assertTestMessage($m);
+    }
+
+    public function testDescriptorDecode()
+    {
+        $file_desc_set = new FileDescriptorSet();
+        $file_desc_set->decode(hex2bin(
+            "0a3b0a12746573745f696e636c7564652e70726f746f120362617222180a" .
+            "0b54657374496e636c75646512090a0161180120012805620670726f746f33"));
+
+        $this->assertSame(1, sizeof($file_desc_set->getFile()));
+
+        $file_desc = $file_desc_set->getFile()[0];
+        $this->assertSame("test_include.proto", $file_desc->getName());
+        $this->assertSame("bar", $file_desc->getPackage());
+        $this->assertSame(0, sizeof($file_desc->getDependency()));
+        $this->assertSame(1, sizeof($file_desc->getMessageType()));
+        $this->assertSame(0, sizeof($file_desc->getEnumType()));
+        $this->assertSame("proto3", $file_desc->getSyntax());
+
+        $desc = $file_desc->getMessageType()[0];
+        $this->assertSame("TestInclude", $desc->getName());
+        $this->assertSame(1, sizeof($desc->getField()));
+        $this->assertSame(0, sizeof($desc->getNestedType()));
+        $this->assertSame(0, sizeof($desc->getEnumType()));
+        $this->assertSame(0, sizeof($desc->getOneofDecl()));
+
+        $field = $desc->getField()[0];
+        $this->assertSame("a", $field->getName());
+        $this->assertSame(1, $field->getNumber());
+        $this->assertSame(GPBLabel::OPTIONAL, $field->getLabel());
+        $this->assertSame(GPBType::INT32, $field->getType());
+    }
+
+    public function testReadVarint64()
+    {
+        $var = 0;
+
+        // Empty buffer.
+        $input = new InputStream(hex2bin(''));
+        $this->assertFalse($input->readVarint64($var));
+
+        // The largest varint is 10 bytes long.
+        $input = new InputStream(hex2bin('8080808080808080808001'));
+        $this->assertFalse($input->readVarint64($var));
+
+        // Corrupted varint.
+        $input = new InputStream(hex2bin('808080'));
+        $this->assertFalse($input->readVarint64($var));
+
+        // Normal case.
+        $input = new InputStream(hex2bin('808001'));
+        $this->assertTrue($input->readVarint64($var));
+        $this->assertSame(16384, $var->toInteger());
+        $this->assertFalse($input->readVarint64($var));
+
+        // Read two varint.
+        $input = new InputStream(hex2bin('808001808002'));
+        $this->assertTrue($input->readVarint64($var));
+        $this->assertSame(16384, $var->toInteger());
+        $this->assertTrue($input->readVarint64($var));
+        $this->assertSame(32768, $var->toInteger());
+        $this->assertFalse($input->readVarint64($var));
+    }
+
+    public function testReadVarint32()
+    {
+        $var = 0;
+
+        // Empty buffer.
+        $input = new InputStream(hex2bin(''));
+        $this->assertFalse($input->readVarint32($var));
+
+        // The largest varint is 10 bytes long.
+        $input = new InputStream(hex2bin('8080808080808080808001'));
+        $this->assertFalse($input->readVarint32($var));
+
+        // Corrupted varint.
+        $input = new InputStream(hex2bin('808080'));
+        $this->assertFalse($input->readVarint32($var));
+
+        // Normal case.
+        $input = new InputStream(hex2bin('808001'));
+        $this->assertTrue($input->readVarint32($var));
+        $this->assertSame(16384, $var);
+        $this->assertFalse($input->readVarint32($var));
+
+        // Read two varint.
+        $input = new InputStream(hex2bin('808001808002'));
+        $this->assertTrue($input->readVarint32($var));
+        $this->assertSame(16384, $var);
+        $this->assertTrue($input->readVarint32($var));
+        $this->assertSame(32768, $var);
+        $this->assertFalse($input->readVarint32($var));
+
+        // Read a 64-bit integer. High-order bits should be discarded.
+        $input = new InputStream(hex2bin('808081808001'));
+        $this->assertTrue($input->readVarint32($var));
+        $this->assertSame(16384, $var);
+        $this->assertFalse($input->readVarint32($var));
+    }
+
+    public function testReadTag()
+    {
+        $input = new InputStream(hex2bin('808001'));
+        $tag = $input->readTag();
+        $this->assertSame(16384, $tag);
+        $tag = $input->readTag();
+        $this->assertSame(0, $tag);
+    }
+
+    public function testPushPopLimit()
+    {
+        $input = new InputStream(hex2bin('808001'));
+        $old_limit = $input->pushLimit(0);
+        $tag = $input->readTag();
+        $this->assertSame(0, $tag);
+        $input->popLimit($old_limit);
+        $tag = $input->readTag();
+        $this->assertSame(16384, $tag);
+    }
+
+    public function testReadRaw()
+    {
+        $input = new InputStream(hex2bin('808001'));
+        $buffer = null;
+
+        $this->assertTrue($input->readRaw(3, $buffer));
+        $this->assertSame(hex2bin('808001'), $buffer);
+
+        $this->assertFalse($input->readRaw(1, $buffer));
+    }
+
+    public function testWriteVarint32()
+    {
+        $output = new OutputStream(3);
+        $output->writeVarint32(16384);
+        $this->assertSame(hex2bin('808001'), $output->getData());
+    }
+
+    public function testWriteVarint64()
+    {
+        $output = new OutputStream(10);
+        $output->writeVarint64(-43);
+        $this->assertSame(hex2bin('D5FFFFFFFFFFFFFFFF01'), $output->getData());
+    }
+
+    public function testWriteLittleEndian32()
+    {
+        $output = new OutputStream(4);
+        $output->writeLittleEndian32(46);
+        $this->assertSame(hex2bin('2E000000'), $output->getData());
+    }
+
+    public function testWriteLittleEndian64()
+    {
+        $output = new OutputStream(8);
+        $output->writeLittleEndian64(47);
+        $this->assertSame(hex2bin('2F00000000000000'), $output->getData());
+    }
+
+    public function testByteSize()
+    {
+        $m = new TestMessage();
+        TestUtil::setTestMessage($m);
+        $this->assertSame(447, $m->byteSize());
+    }
+
+    public function testPackedByteSize()
+    {
+        $m = new TestPackedMessage();
+        TestUtil::setTestPackedMessage($m);
+        $this->assertSame(156, $m->byteSize());
+    }
+}

+ 1385 - 0
php/tests/test.pb.php

@@ -0,0 +1,1385 @@
+<?php
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: test.proto
+
+namespace Foo;
+
+require_once('test_include.pb.php');
+use Google\Protobuf\Internal\DescriptorPool;
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\GPBUtil;
+
+class TestMessage extends \Google\Protobuf\Internal\Message
+{
+    private $optional_int32 = 0;
+    private $optional_int64 = 0;
+    private $optional_uint32 = 0;
+    private $optional_uint64 = 0;
+    private $optional_sint32 = 0;
+    private $optional_sint64 = 0;
+    private $optional_fixed32 = 0;
+    private $optional_fixed64 = 0;
+    private $optional_sfixed32 = 0;
+    private $optional_sfixed64 = 0;
+    private $optional_float = 0.0;
+    private $optional_double = 0.0;
+    private $optional_bool = false;
+    private $optional_string = '';
+    private $optional_bytes = '';
+    private $optional_enum = 0;
+    private $optional_message = null;
+    private $optional_included_message = null;
+    private $recursive = null;
+    private $repeated_int32;
+    private $repeated_int64;
+    private $repeated_uint32;
+    private $repeated_uint64;
+    private $repeated_sint32;
+    private $repeated_sint64;
+    private $repeated_fixed32;
+    private $repeated_fixed64;
+    private $repeated_sfixed32;
+    private $repeated_sfixed64;
+    private $repeated_float;
+    private $repeated_double;
+    private $repeated_bool;
+    private $repeated_string;
+    private $repeated_bytes;
+    private $repeated_enum;
+    private $repeated_message;
+    private $repeated_recursive;
+    private $map_int32_int32;
+    private $map_int64_int64;
+    private $map_uint32_uint32;
+    private $map_uint64_uint64;
+    private $map_sint32_sint32;
+    private $map_sint64_sint64;
+    private $map_fixed32_fixed32;
+    private $map_fixed64_fixed64;
+    private $map_sfixed32_sfixed32;
+    private $map_sfixed64_sfixed64;
+    private $map_int32_float;
+    private $map_int32_double;
+    private $map_bool_bool;
+    private $map_string_string;
+    private $map_int32_bytes;
+    private $map_int32_enum;
+    private $map_int32_message;
+    private $map_recursive;
+    protected $my_oneof;
+
+    public function getOptionalInt32()
+    {
+        return $this->optional_int32;
+    }
+
+    public function setOptionalInt32($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->optional_int32 = $var;
+    }
+
+    public function getOptionalInt64()
+    {
+        return $this->optional_int64;
+    }
+
+    public function setOptionalInt64($var)
+    {
+        GPBUtil::checkInt64($var);
+        $this->optional_int64 = $var;
+    }
+
+    public function getOptionalUint32()
+    {
+        return $this->optional_uint32;
+    }
+
+    public function setOptionalUint32($var)
+    {
+        GPBUtil::checkUint32($var);
+        $this->optional_uint32 = $var;
+    }
+
+    public function getOptionalUint64()
+    {
+        return $this->optional_uint64;
+    }
+
+    public function setOptionalUint64($var)
+    {
+        GPBUtil::checkUint64($var);
+        $this->optional_uint64 = $var;
+    }
+
+    public function getOptionalSint32()
+    {
+        return $this->optional_sint32;
+    }
+
+    public function setOptionalSint32($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->optional_sint32 = $var;
+    }
+
+    public function getOptionalSint64()
+    {
+        return $this->optional_sint64;
+    }
+
+    public function setOptionalSint64($var)
+    {
+        GPBUtil::checkInt64($var);
+        $this->optional_sint64 = $var;
+    }
+
+    public function getOptionalFixed32()
+    {
+        return $this->optional_fixed32;
+    }
+
+    public function setOptionalFixed32($var)
+    {
+        GPBUtil::checkUint32($var);
+        $this->optional_fixed32 = $var;
+    }
+
+    public function getOptionalFixed64()
+    {
+        return $this->optional_fixed64;
+    }
+
+    public function setOptionalFixed64($var)
+    {
+        GPBUtil::checkUint64($var);
+        $this->optional_fixed64 = $var;
+    }
+
+    public function getOptionalSfixed32()
+    {
+        return $this->optional_sfixed32;
+    }
+
+    public function setOptionalSfixed32($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->optional_sfixed32 = $var;
+    }
+
+    public function getOptionalSfixed64()
+    {
+        return $this->optional_sfixed64;
+    }
+
+    public function setOptionalSfixed64($var)
+    {
+        GPBUtil::checkInt64($var);
+        $this->optional_sfixed64 = $var;
+    }
+
+    public function getOptionalFloat()
+    {
+        return $this->optional_float;
+    }
+
+    public function setOptionalFloat($var)
+    {
+        GPBUtil::checkFloat($var);
+        $this->optional_float = $var;
+    }
+
+    public function getOptionalDouble()
+    {
+        return $this->optional_double;
+    }
+
+    public function setOptionalDouble($var)
+    {
+        GPBUtil::checkDouble($var);
+        $this->optional_double = $var;
+    }
+
+    public function getOptionalBool()
+    {
+        return $this->optional_bool;
+    }
+
+    public function setOptionalBool($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->optional_bool = $var;
+    }
+
+    public function getOptionalString()
+    {
+        return $this->optional_string;
+    }
+
+    public function setOptionalString($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->optional_string = $var;
+    }
+
+    public function getOptionalBytes()
+    {
+        return $this->optional_bytes;
+    }
+
+    public function setOptionalBytes($var)
+    {
+        GPBUtil::checkString($var, False);
+        $this->optional_bytes = $var;
+    }
+
+    public function getOptionalEnum()
+    {
+        return $this->optional_enum;
+    }
+
+    public function setOptionalEnum($var)
+    {
+        GPBUtil::checkEnum($var, \Foo\TestEnum::class);
+        $this->optional_enum = $var;
+    }
+
+    public function getOptionalMessage()
+    {
+        return $this->optional_message;
+    }
+
+    public function setOptionalMessage(&$var)
+    {
+        GPBUtil::checkMessage($var, \Foo\TestMessage_Sub::class);
+        $this->optional_message = $var;
+    }
+
+    public function getOptionalIncludedMessage()
+    {
+        return $this->optional_included_message;
+    }
+
+    public function setOptionalIncludedMessage(&$var)
+    {
+        GPBUtil::checkMessage($var, \Bar\TestInclude::class);
+        $this->optional_included_message = $var;
+    }
+
+    public function getRecursive()
+    {
+        return $this->recursive;
+    }
+
+    public function setRecursive(&$var)
+    {
+        GPBUtil::checkMessage($var, \Foo\TestMessage::class);
+        $this->recursive = $var;
+    }
+
+    public function getRepeatedInt32()
+    {
+        return $this->repeated_int32;
+    }
+
+    public function setRepeatedInt32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT32);
+        $this->repeated_int32 = $var;
+    }
+
+    public function getRepeatedInt64()
+    {
+        return $this->repeated_int64;
+    }
+
+    public function setRepeatedInt64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT64);
+        $this->repeated_int64 = $var;
+    }
+
+    public function getRepeatedUint32()
+    {
+        return $this->repeated_uint32;
+    }
+
+    public function setRepeatedUint32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::UINT32);
+        $this->repeated_uint32 = $var;
+    }
+
+    public function getRepeatedUint64()
+    {
+        return $this->repeated_uint64;
+    }
+
+    public function setRepeatedUint64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::UINT64);
+        $this->repeated_uint64 = $var;
+    }
+
+    public function getRepeatedSint32()
+    {
+        return $this->repeated_sint32;
+    }
+
+    public function setRepeatedSint32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SINT32);
+        $this->repeated_sint32 = $var;
+    }
+
+    public function getRepeatedSint64()
+    {
+        return $this->repeated_sint64;
+    }
+
+    public function setRepeatedSint64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SINT64);
+        $this->repeated_sint64 = $var;
+    }
+
+    public function getRepeatedFixed32()
+    {
+        return $this->repeated_fixed32;
+    }
+
+    public function setRepeatedFixed32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FIXED32);
+        $this->repeated_fixed32 = $var;
+    }
+
+    public function getRepeatedFixed64()
+    {
+        return $this->repeated_fixed64;
+    }
+
+    public function setRepeatedFixed64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FIXED64);
+        $this->repeated_fixed64 = $var;
+    }
+
+    public function getRepeatedSfixed32()
+    {
+        return $this->repeated_sfixed32;
+    }
+
+    public function setRepeatedSfixed32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SFIXED32);
+        $this->repeated_sfixed32 = $var;
+    }
+
+    public function getRepeatedSfixed64()
+    {
+        return $this->repeated_sfixed64;
+    }
+
+    public function setRepeatedSfixed64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SFIXED64);
+        $this->repeated_sfixed64 = $var;
+    }
+
+    public function getRepeatedFloat()
+    {
+        return $this->repeated_float;
+    }
+
+    public function setRepeatedFloat(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FLOAT);
+        $this->repeated_float = $var;
+    }
+
+    public function getRepeatedDouble()
+    {
+        return $this->repeated_double;
+    }
+
+    public function setRepeatedDouble(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::DOUBLE);
+        $this->repeated_double = $var;
+    }
+
+    public function getRepeatedBool()
+    {
+        return $this->repeated_bool;
+    }
+
+    public function setRepeatedBool(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::BOOL);
+        $this->repeated_bool = $var;
+    }
+
+    public function getRepeatedString()
+    {
+        return $this->repeated_string;
+    }
+
+    public function setRepeatedString(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::STRING);
+        $this->repeated_string = $var;
+    }
+
+    public function getRepeatedBytes()
+    {
+        return $this->repeated_bytes;
+    }
+
+    public function setRepeatedBytes(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::BYTES);
+        $this->repeated_bytes = $var;
+    }
+
+    public function getRepeatedEnum()
+    {
+        return $this->repeated_enum;
+    }
+
+    public function setRepeatedEnum(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::ENUM, Foo\TestEnum::class);
+        $this->repeated_enum = $var;
+    }
+
+    public function getRepeatedMessage()
+    {
+        return $this->repeated_message;
+    }
+
+    public function setRepeatedMessage(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Foo\TestMessage_Sub::class);
+        $this->repeated_message = $var;
+    }
+
+    public function getRepeatedRecursive()
+    {
+        return $this->repeated_recursive;
+    }
+
+    public function setRepeatedRecursive(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::MESSAGE, \Foo\TestMessage::class);
+        $this->repeated_recursive = $var;
+    }
+
+    public function getOneofInt32()
+    {
+        return $this->readOneof(51);
+    }
+
+    public function setOneofInt32($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->writeOneof(51, $var);
+    }
+
+    public function getOneofInt64()
+    {
+        return $this->readOneof(52);
+    }
+
+    public function setOneofInt64($var)
+    {
+        GPBUtil::checkInt64($var);
+        $this->writeOneof(52, $var);
+    }
+
+    public function getOneofUint32()
+    {
+        return $this->readOneof(53);
+    }
+
+    public function setOneofUint32($var)
+    {
+        GPBUtil::checkUint32($var);
+        $this->writeOneof(53, $var);
+    }
+
+    public function getOneofUint64()
+    {
+        return $this->readOneof(54);
+    }
+
+    public function setOneofUint64($var)
+    {
+        GPBUtil::checkUint64($var);
+        $this->writeOneof(54, $var);
+    }
+
+    public function getOneofSint32()
+    {
+        return $this->readOneof(55);
+    }
+
+    public function setOneofSint32($var)
+    {
+        GPBUtil::checkUint32($var);
+        $this->writeOneof(55, $var);
+    }
+
+    public function getOneofSint64()
+    {
+        return $this->readOneof(56);
+    }
+
+    public function setOneofSint64($var)
+    {
+        GPBUtil::checkUint64($var);
+        $this->writeOneof(56, $var);
+    }
+
+    public function getOneofFixed32()
+    {
+        return $this->readOneof(57);
+    }
+
+    public function setOneofFixed32($var)
+    {
+        GPBUtil::checkUint32($var);
+        $this->writeOneof(57, $var);
+    }
+
+    public function getOneofFixed64()
+    {
+        return $this->readOneof(58);
+    }
+
+    public function setOneofFixed64($var)
+    {
+        GPBUtil::checkUint64($var);
+        $this->writeOneof(58, $var);
+    }
+
+    public function getOneofSfixed32()
+    {
+        return $this->readOneof(59);
+    }
+
+    public function setOneofSfixed32($var)
+    {
+        GPBUtil::checkUint32($var);
+        $this->writeOneof(59, $var);
+    }
+
+    public function getOneofSfixed64()
+    {
+        return $this->readOneof(60);
+    }
+
+    public function setOneofSfixed64($var)
+    {
+        GPBUtil::checkUint64($var);
+        $this->writeOneof(60, $var);
+    }
+
+    public function getOneofDouble()
+    {
+        return $this->readOneof(61);
+    }
+
+    public function setOneofDouble($var)
+    {
+        GPBUtil::checkDouble($var);
+        $this->writeOneof(61, $var);
+    }
+
+    public function getOneofFloat()
+    {
+        return $this->readOneof(62);
+    }
+
+    public function setOneofFloat($var)
+    {
+        GPBUtil::checkFloat($var);
+        $this->writeOneof(62, $var);
+    }
+
+    public function getOneofBool()
+    {
+        return $this->readOneof(63);
+    }
+
+    public function setOneofBool($var)
+    {
+        GPBUtil::checkBool($var);
+        $this->writeOneof(63, $var);
+    }
+
+    public function getOneofString()
+    {
+        return $this->readOneof(64);
+    }
+
+    public function setOneofString($var)
+    {
+        GPBUtil::checkString($var, True);
+        $this->writeOneof(64, $var);
+    }
+
+    public function getOneofBytes()
+    {
+        return $this->readOneof(65);
+    }
+
+    public function setOneofBytes($var)
+    {
+        GPBUtil::checkString($var, False);
+        $this->writeOneof(65, $var);
+    }
+
+    public function getOneofEnum()
+    {
+        return $this->readOneof(66);
+    }
+
+    public function setOneofEnum($var)
+    {
+        GPBUtil::checkEnum($var, \Foo\TestEnum::class);
+        $this->writeOneof(66, $var);
+    }
+
+    public function getOneofMessage()
+    {
+        return $this->readOneof(67);
+    }
+
+    public function setOneofMessage(&$var)
+    {
+        GPBUtil::checkMessage($var, \Foo\TestMessage_Sub::class);
+        $this->writeOneof(67, $var);
+    }
+
+    public function getMapInt32Int32()
+    {
+        return $this->map_int32_int32;
+    }
+
+    public function setMapInt32Int32(&$var)
+    {
+        $this->map_int32_int32 = $var;
+    }
+
+    public function getMapInt64Int64()
+    {
+        return $this->map_int64_int64;
+    }
+
+    public function setMapInt64Int64(&$var)
+    {
+        $this->map_int64_int64 = $var;
+    }
+
+    public function getMapUint32Uint32()
+    {
+        return $this->map_uint32_uint32;
+    }
+
+    public function setMapUint32Uint32(&$var)
+    {
+        $this->map_uint32_uint32 = $var;
+    }
+
+    public function getMapUint64Uint64()
+    {
+        return $this->map_uint64_uint64;
+    }
+
+    public function setMapUint64Uint64(&$var)
+    {
+        $this->map_uint64_uint64 = $var;
+    }
+
+    public function getMapSint32Sint32()
+    {
+        return $this->map_sint32_sint32;
+    }
+
+    public function setMapSint32Sint32(&$var)
+    {
+        $this->map_sint32_sint32 = $var;
+    }
+
+    public function getMapSint64Sint64()
+    {
+        return $this->map_sint64_sint64;
+    }
+
+    public function setMapSint64Sint64(&$var)
+    {
+        $this->map_sint64_sint64 = $var;
+    }
+
+    public function getMapFixed32Fixed32()
+    {
+        return $this->map_fixed32_fixed32;
+    }
+
+    public function setMapFixed32Fixed32(&$var)
+    {
+        $this->map_fixed32_fixed32 = $var;
+    }
+
+    public function getMapFixed64Fixed64()
+    {
+        return $this->map_fixed64_fixed64;
+    }
+
+    public function setMapFixed64Fixed64(&$var)
+    {
+        $this->map_fixed64_fixed64 = $var;
+    }
+
+    public function getMapSfixed32Sfixed32()
+    {
+        return $this->map_sfixed32_sfixed32;
+    }
+
+    public function setMapSfixed32Sfixed32(&$var)
+    {
+        $this->map_sfixed32_sfixed32 = $var;
+    }
+
+    public function getMapSfixed64Sfixed64()
+    {
+        return $this->map_sfixed64_sfixed64;
+    }
+
+    public function setMapSfixed64Sfixed64(&$var)
+    {
+        $this->map_sfixed64_sfixed64 = $var;
+    }
+
+    public function getMapInt32Float()
+    {
+        return $this->map_int32_float;
+    }
+
+    public function setMapInt32Float(&$var)
+    {
+        $this->map_int32_float = $var;
+    }
+
+    public function getMapInt32Double()
+    {
+        return $this->map_int32_double;
+    }
+
+    public function setMapInt32Double(&$var)
+    {
+        $this->map_int32_double = $var;
+    }
+
+    public function getMapBoolBool()
+    {
+        return $this->map_bool_bool;
+    }
+
+    public function setMapBoolBool(&$var)
+    {
+        $this->map_bool_bool = $var;
+    }
+
+    public function getMapStringString()
+    {
+        return $this->map_string_string;
+    }
+
+    public function setMapStringString(&$var)
+    {
+        $this->map_string_string = $var;
+    }
+
+    public function getMapInt32Bytes()
+    {
+        return $this->map_int32_bytes;
+    }
+
+    public function setMapInt32Bytes(&$var)
+    {
+        $this->map_int32_bytes = $var;
+    }
+
+    public function getMapInt32Enum()
+    {
+        return $this->map_int32_enum;
+    }
+
+    public function setMapInt32Enum(&$var)
+    {
+        $this->map_int32_enum = $var;
+    }
+
+    public function getMapInt32Message()
+    {
+        return $this->map_int32_message;
+    }
+
+    public function setMapInt32Message(&$var)
+    {
+        $this->map_int32_message = $var;
+    }
+
+    public function getMapRecursive()
+    {
+        return $this->map_recursive;
+    }
+
+    public function setMapRecursive(&$var)
+    {
+        $this->map_recursive = $var;
+    }
+
+    public function getMyOneof()
+    {
+        return $this->my_oneof;
+    }
+
+}
+
+class TestMessage_Sub extends \Google\Protobuf\Internal\Message
+{
+    private $a = 0;
+
+    public function getA()
+    {
+        return $this->a;
+    }
+
+    public function setA($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->a = $var;
+    }
+
+}
+
+class TestPackedMessage extends \Google\Protobuf\Internal\Message
+{
+    private $repeated_int32;
+    private $repeated_int64;
+    private $repeated_uint32;
+    private $repeated_uint64;
+    private $repeated_sint32;
+    private $repeated_sint64;
+    private $repeated_fixed32;
+    private $repeated_fixed64;
+    private $repeated_sfixed32;
+    private $repeated_sfixed64;
+    private $repeated_float;
+    private $repeated_double;
+    private $repeated_bool;
+    private $repeated_enum;
+
+    public function getRepeatedInt32()
+    {
+        return $this->repeated_int32;
+    }
+
+    public function setRepeatedInt32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT32);
+        $this->repeated_int32 = $var;
+    }
+
+    public function getRepeatedInt64()
+    {
+        return $this->repeated_int64;
+    }
+
+    public function setRepeatedInt64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT64);
+        $this->repeated_int64 = $var;
+    }
+
+    public function getRepeatedUint32()
+    {
+        return $this->repeated_uint32;
+    }
+
+    public function setRepeatedUint32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::UINT32);
+        $this->repeated_uint32 = $var;
+    }
+
+    public function getRepeatedUint64()
+    {
+        return $this->repeated_uint64;
+    }
+
+    public function setRepeatedUint64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::UINT64);
+        $this->repeated_uint64 = $var;
+    }
+
+    public function getRepeatedSint32()
+    {
+        return $this->repeated_sint32;
+    }
+
+    public function setRepeatedSint32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SINT32);
+        $this->repeated_sint32 = $var;
+    }
+
+    public function getRepeatedSint64()
+    {
+        return $this->repeated_sint64;
+    }
+
+    public function setRepeatedSint64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SINT64);
+        $this->repeated_sint64 = $var;
+    }
+
+    public function getRepeatedFixed32()
+    {
+        return $this->repeated_fixed32;
+    }
+
+    public function setRepeatedFixed32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FIXED32);
+        $this->repeated_fixed32 = $var;
+    }
+
+    public function getRepeatedFixed64()
+    {
+        return $this->repeated_fixed64;
+    }
+
+    public function setRepeatedFixed64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FIXED64);
+        $this->repeated_fixed64 = $var;
+    }
+
+    public function getRepeatedSfixed32()
+    {
+        return $this->repeated_sfixed32;
+    }
+
+    public function setRepeatedSfixed32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SFIXED32);
+        $this->repeated_sfixed32 = $var;
+    }
+
+    public function getRepeatedSfixed64()
+    {
+        return $this->repeated_sfixed64;
+    }
+
+    public function setRepeatedSfixed64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SFIXED64);
+        $this->repeated_sfixed64 = $var;
+    }
+
+    public function getRepeatedFloat()
+    {
+        return $this->repeated_float;
+    }
+
+    public function setRepeatedFloat(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FLOAT);
+        $this->repeated_float = $var;
+    }
+
+    public function getRepeatedDouble()
+    {
+        return $this->repeated_double;
+    }
+
+    public function setRepeatedDouble(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::DOUBLE);
+        $this->repeated_double = $var;
+    }
+
+    public function getRepeatedBool()
+    {
+        return $this->repeated_bool;
+    }
+
+    public function setRepeatedBool(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::BOOL);
+        $this->repeated_bool = $var;
+    }
+
+    public function getRepeatedEnum()
+    {
+        return $this->repeated_enum;
+    }
+
+    public function setRepeatedEnum(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::ENUM, Foo\TestEnum::class);
+        $this->repeated_enum = $var;
+    }
+
+}
+
+class TestUnpackedMessage extends \Google\Protobuf\Internal\Message
+{
+    private $repeated_int32;
+    private $repeated_int64;
+    private $repeated_uint32;
+    private $repeated_uint64;
+    private $repeated_sint32;
+    private $repeated_sint64;
+    private $repeated_fixed32;
+    private $repeated_fixed64;
+    private $repeated_sfixed32;
+    private $repeated_sfixed64;
+    private $repeated_float;
+    private $repeated_double;
+    private $repeated_bool;
+    private $repeated_enum;
+
+    public function getRepeatedInt32()
+    {
+        return $this->repeated_int32;
+    }
+
+    public function setRepeatedInt32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT32);
+        $this->repeated_int32 = $var;
+    }
+
+    public function getRepeatedInt64()
+    {
+        return $this->repeated_int64;
+    }
+
+    public function setRepeatedInt64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::INT64);
+        $this->repeated_int64 = $var;
+    }
+
+    public function getRepeatedUint32()
+    {
+        return $this->repeated_uint32;
+    }
+
+    public function setRepeatedUint32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::UINT32);
+        $this->repeated_uint32 = $var;
+    }
+
+    public function getRepeatedUint64()
+    {
+        return $this->repeated_uint64;
+    }
+
+    public function setRepeatedUint64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::UINT64);
+        $this->repeated_uint64 = $var;
+    }
+
+    public function getRepeatedSint32()
+    {
+        return $this->repeated_sint32;
+    }
+
+    public function setRepeatedSint32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SINT32);
+        $this->repeated_sint32 = $var;
+    }
+
+    public function getRepeatedSint64()
+    {
+        return $this->repeated_sint64;
+    }
+
+    public function setRepeatedSint64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SINT64);
+        $this->repeated_sint64 = $var;
+    }
+
+    public function getRepeatedFixed32()
+    {
+        return $this->repeated_fixed32;
+    }
+
+    public function setRepeatedFixed32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FIXED32);
+        $this->repeated_fixed32 = $var;
+    }
+
+    public function getRepeatedFixed64()
+    {
+        return $this->repeated_fixed64;
+    }
+
+    public function setRepeatedFixed64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FIXED64);
+        $this->repeated_fixed64 = $var;
+    }
+
+    public function getRepeatedSfixed32()
+    {
+        return $this->repeated_sfixed32;
+    }
+
+    public function setRepeatedSfixed32(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SFIXED32);
+        $this->repeated_sfixed32 = $var;
+    }
+
+    public function getRepeatedSfixed64()
+    {
+        return $this->repeated_sfixed64;
+    }
+
+    public function setRepeatedSfixed64(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::SFIXED64);
+        $this->repeated_sfixed64 = $var;
+    }
+
+    public function getRepeatedFloat()
+    {
+        return $this->repeated_float;
+    }
+
+    public function setRepeatedFloat(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::FLOAT);
+        $this->repeated_float = $var;
+    }
+
+    public function getRepeatedDouble()
+    {
+        return $this->repeated_double;
+    }
+
+    public function setRepeatedDouble(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::DOUBLE);
+        $this->repeated_double = $var;
+    }
+
+    public function getRepeatedBool()
+    {
+        return $this->repeated_bool;
+    }
+
+    public function setRepeatedBool(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::BOOL);
+        $this->repeated_bool = $var;
+    }
+
+    public function getRepeatedEnum()
+    {
+        return $this->repeated_enum;
+    }
+
+    public function setRepeatedEnum(&$var)
+    {
+        GPBUtil::checkRepeatedField($var, GPBType::ENUM, Foo\TestEnum::class);
+        $this->repeated_enum = $var;
+    }
+
+}
+
+class TestEnum
+{
+    const ZERO = 0;
+    const ONE = 1;
+}
+
+$pool = DescriptorPool::getGeneratedPool();
+
+$pool->internalAddGeneratedFile(hex2bin(
+    "0a83250a0a746573742e70726f746f1203666f6f1a12746573745f696e63" .
+    "6c7564652e70726f746f22be1d0a0b546573744d65737361676512160a0e" .
+    "6f7074696f6e616c5f696e74333218012001280512160a0e6f7074696f6e" .
+    "616c5f696e74363418022001280312170a0f6f7074696f6e616c5f75696e" .
+    "74333218032001280d12170a0f6f7074696f6e616c5f75696e7436341804" .
+    "2001280412170a0f6f7074696f6e616c5f73696e74333218052001281112" .
+    "170a0f6f7074696f6e616c5f73696e74363418062001281212180a106f70" .
+    "74696f6e616c5f6669786564333218072001280712180a106f7074696f6e" .
+    "616c5f6669786564363418082001280612190a116f7074696f6e616c5f73" .
+    "6669786564333218092001280f12190a116f7074696f6e616c5f73666978" .
+    "65643634180a2001281012160a0e6f7074696f6e616c5f666c6f6174180b" .
+    "2001280212170a0f6f7074696f6e616c5f646f75626c65180c2001280112" .
+    "150a0d6f7074696f6e616c5f626f6f6c180d2001280812170a0f6f707469" .
+    "6f6e616c5f737472696e67180e2001280912160a0e6f7074696f6e616c5f" .
+    "6279746573180f2001280c12240a0d6f7074696f6e616c5f656e756d1810" .
+    "2001280e320d2e666f6f2e54657374456e756d122e0a106f7074696f6e61" .
+    "6c5f6d65737361676518112001280b32142e666f6f2e546573744d657373" .
+    "6167652e53756212330a196f7074696f6e616c5f696e636c756465645f6d" .
+    "65737361676518122001280b32102e6261722e54657374496e636c756465" .
+    "12230a0972656375727369766518132001280b32102e666f6f2e54657374" .
+    "4d65737361676512160a0e72657065617465645f696e743332181f200328" .
+    "0512160a0e72657065617465645f696e74363418202003280312170a0f72" .
+    "657065617465645f75696e74333218212003280d12170a0f726570656174" .
+    "65645f75696e74363418222003280412170a0f72657065617465645f7369" .
+    "6e74333218232003281112170a0f72657065617465645f73696e74363418" .
+    "242003281212180a1072657065617465645f666978656433321825200328" .
+    "0712180a1072657065617465645f6669786564363418262003280612190a" .
+    "1172657065617465645f736669786564333218272003280f12190a117265" .
+    "7065617465645f736669786564363418282003281012160a0e7265706561" .
+    "7465645f666c6f617418292003280212170a0f72657065617465645f646f" .
+    "75626c65182a2003280112150a0d72657065617465645f626f6f6c182b20" .
+    "03280812170a0f72657065617465645f737472696e67182c200328091216" .
+    "0a0e72657065617465645f6279746573182d2003280c12240a0d72657065" .
+    "617465645f656e756d182e2003280e320d2e666f6f2e54657374456e756d" .
+    "122e0a1072657065617465645f6d657373616765182f2003280b32142e66" .
+    "6f6f2e546573744d6573736167652e537562122c0a127265706561746564" .
+    "5f72656375727369766518302003280b32102e666f6f2e546573744d6573" .
+    "7361676512150a0b6f6e656f665f696e743332183320012805480012150a" .
+    "0b6f6e656f665f696e743634183420012803480012160a0c6f6e656f665f" .
+    "75696e74333218352001280d480012160a0c6f6e656f665f75696e743634" .
+    "183620012804480012160a0c6f6e656f665f73696e74333218372001280d" .
+    "480012160a0c6f6e656f665f73696e743634183820012804480012170a0d" .
+    "6f6e656f665f6669786564333218392001280d480012170a0d6f6e656f66" .
+    "5f66697865643634183a20012804480012180a0e6f6e656f665f73666978" .
+    "65643332183b2001280d480012180a0e6f6e656f665f7366697865643634" .
+    "183c20012804480012160a0c6f6e656f665f646f75626c65183d20012801" .
+    "480012150a0b6f6e656f665f666c6f6174183e20012802480012140a0a6f" .
+    "6e656f665f626f6f6c183f20012808480012160a0c6f6e656f665f737472" .
+    "696e67184020012809480012150a0b6f6e656f665f627974657318412001" .
+    "280c480012230a0a6f6e656f665f656e756d18422001280e320d2e666f6f" .
+    "2e54657374456e756d4800122d0a0d6f6e656f665f6d6573736167651843" .
+    "2001280b32142e666f6f2e546573744d6573736167652e5375624800123c" .
+    "0a0f6d61705f696e7433325f696e74333218472003280b32232e666f6f2e" .
+    "546573744d6573736167652e4d6170496e743332496e743332456e747279" .
+    "123c0a0f6d61705f696e7436345f696e74363418482003280b32232e666f" .
+    "6f2e546573744d6573736167652e4d6170496e743634496e743634456e74" .
+    "727912400a116d61705f75696e7433325f75696e74333218492003280b32" .
+    "252e666f6f2e546573744d6573736167652e4d617055696e74333255696e" .
+    "743332456e74727912400a116d61705f75696e7436345f75696e74363418" .
+    "4a2003280b32252e666f6f2e546573744d6573736167652e4d617055696e" .
+    "74363455696e743634456e74727912400a116d61705f73696e7433325f73" .
+    "696e743332184b2003280b32252e666f6f2e546573744d6573736167652e" .
+    "4d617053696e74333253696e743332456e74727912400a116d61705f7369" .
+    "6e7436345f73696e743634184c2003280b32252e666f6f2e546573744d65" .
+    "73736167652e4d617053696e74363453696e743634456e74727912440a13" .
+    "6d61705f666978656433325f66697865643332184d2003280b32272e666f" .
+    "6f2e546573744d6573736167652e4d617046697865643332466978656433" .
+    "32456e74727912440a136d61705f666978656436345f6669786564363418" .
+    "4e2003280b32272e666f6f2e546573744d6573736167652e4d6170466978" .
+    "6564363446697865643634456e74727912480a156d61705f736669786564" .
+    "33325f7366697865643332184f2003280b32292e666f6f2e546573744d65" .
+    "73736167652e4d617053666978656433325366697865643332456e747279" .
+    "12480a156d61705f73666978656436345f73666978656436341850200328" .
+    "0b32292e666f6f2e546573744d6573736167652e4d617053666978656436" .
+    "345366697865643634456e747279123c0a0f6d61705f696e7433325f666c" .
+    "6f617418512003280b32232e666f6f2e546573744d6573736167652e4d61" .
+    "70496e743332466c6f6174456e747279123e0a106d61705f696e7433325f" .
+    "646f75626c6518522003280b32242e666f6f2e546573744d657373616765" .
+    "2e4d6170496e743332446f75626c65456e74727912380a0d6d61705f626f" .
+    "6f6c5f626f6f6c18532003280b32212e666f6f2e546573744d6573736167" .
+    "652e4d6170426f6f6c426f6f6c456e74727912400a116d61705f73747269" .
+    "6e675f737472696e6718542003280b32252e666f6f2e546573744d657373" .
+    "6167652e4d6170537472696e67537472696e67456e747279123c0a0f6d61" .
+    "705f696e7433325f627974657318552003280b32232e666f6f2e54657374" .
+    "4d6573736167652e4d6170496e7433324279746573456e747279123a0a0e" .
+    "6d61705f696e7433325f656e756d18562003280b32222e666f6f2e546573" .
+    "744d6573736167652e4d6170496e743332456e756d456e74727912400a11" .
+    "6d61705f696e7433325f6d65737361676518572003280b32252e666f6f2e" .
+    "546573744d6573736167652e4d6170496e7433324d657373616765456e74" .
+    "727912390a0d6d61705f72656375727369766518582003280b32222e666f" .
+    "6f2e546573744d6573736167652e4d6170526563757273697665456e7472" .
+    "791a340a124d6170496e743332496e743332456e747279120b0a036b6579" .
+    "180120012805120d0a0576616c75651802200128053a0238011a340a124d" .
+    "6170496e743634496e743634456e747279120b0a036b6579180120012803" .
+    "120d0a0576616c75651802200128033a0238011a360a144d617055696e74" .
+    "333255696e743332456e747279120b0a036b657918012001280d120d0a05" .
+    "76616c756518022001280d3a0238011a360a144d617055696e7436345569" .
+    "6e743634456e747279120b0a036b6579180120012804120d0a0576616c75" .
+    "651802200128043a0238011a360a144d617053696e74333253696e743332" .
+    "456e747279120b0a036b6579180120012811120d0a0576616c7565180220" .
+    "0128113a0238011a360a144d617053696e74363453696e743634456e7472" .
+    "79120b0a036b6579180120012812120d0a0576616c75651802200128123a" .
+    "0238011a380a164d61704669786564333246697865643332456e74727912" .
+    "0b0a036b6579180120012807120d0a0576616c75651802200128073a0238" .
+    "011a380a164d61704669786564363446697865643634456e747279120b0a" .
+    "036b6579180120012806120d0a0576616c75651802200128063a0238011a" .
+    "3a0a184d617053666978656433325366697865643332456e747279120b0a" .
+    "036b657918012001280f120d0a0576616c756518022001280f3a0238011a" .
+    "3a0a184d617053666978656436345366697865643634456e747279120b0a" .
+    "036b6579180120012810120d0a0576616c75651802200128103a0238011a" .
+    "340a124d6170496e743332466c6f6174456e747279120b0a036b65791801" .
+    "20012805120d0a0576616c75651802200128023a0238011a350a134d6170" .
+    "496e743332446f75626c65456e747279120b0a036b657918012001280512" .
+    "0d0a0576616c75651802200128013a0238011a320a104d6170426f6f6c42" .
+    "6f6f6c456e747279120b0a036b6579180120012808120d0a0576616c7565" .
+    "1802200128083a0238011a360a144d6170537472696e67537472696e6745" .
+    "6e747279120b0a036b6579180120012809120d0a0576616c756518022001" .
+    "28093a0238011a340a124d6170496e7433324279746573456e747279120b" .
+    "0a036b6579180120012805120d0a0576616c756518022001280c3a023801" .
+    "1a420a114d6170496e743332456e756d456e747279120b0a036b65791801" .
+    "20012805121c0a0576616c756518022001280e320d2e666f6f2e54657374" .
+    "456e756d3a0238011a4c0a144d6170496e7433324d657373616765456e74" .
+    "7279120b0a036b657918012001280512230a0576616c756518022001280b" .
+    "32142e666f6f2e546573744d6573736167652e5375623a0238011a450a11" .
+    "4d6170526563757273697665456e747279120b0a036b6579180120012805" .
+    "121f0a0576616c756518022001280b32102e666f6f2e546573744d657373" .
+    "6167653a0238011a100a0353756212090a0161180120012805420a0a086d" .
+    "795f6f6e656f6622b7030a11546573745061636b65644d65737361676512" .
+    "1a0a0e72657065617465645f696e743332185a2003280542021001121a0a" .
+    "0e72657065617465645f696e743634185b2003280342021001121b0a0f72" .
+    "657065617465645f75696e743332185c2003280d42021001121b0a0f7265" .
+    "7065617465645f75696e743634185d2003280442021001121b0a0f726570" .
+    "65617465645f73696e743332185e2003281142021001121b0a0f72657065" .
+    "617465645f73696e743634185f2003281242021001121c0a107265706561" .
+    "7465645f6669786564333218602003280742021001121c0a107265706561" .
+    "7465645f6669786564363418612003280642021001121d0a117265706561" .
+    "7465645f736669786564333218622003280f42021001121d0a1172657065" .
+    "617465645f736669786564363418632003281042021001121a0a0e726570" .
+    "65617465645f666c6f617418642003280242021001121b0a0f7265706561" .
+    "7465645f646f75626c651865200328014202100112190a0d726570656174" .
+    "65645f626f6f6c1866200328084202100112280a0d72657065617465645f" .
+    "656e756d18672003280e320d2e666f6f2e54657374456e756d4202100122" .
+    "b9030a1354657374556e7061636b65644d657373616765121a0a0e726570" .
+    "65617465645f696e743332185a2003280542021000121a0a0e7265706561" .
+    "7465645f696e743634185b2003280342021000121b0a0f72657065617465" .
+    "645f75696e743332185c2003280d42021000121b0a0f7265706561746564" .
+    "5f75696e743634185d2003280442021000121b0a0f72657065617465645f" .
+    "73696e743332185e2003281142021000121b0a0f72657065617465645f73" .
+    "696e743634185f2003281242021000121c0a1072657065617465645f6669" .
+    "786564333218602003280742021000121c0a1072657065617465645f6669" .
+    "786564363418612003280642021000121d0a1172657065617465645f7366" .
+    "69786564333218622003280f42021000121d0a1172657065617465645f73" .
+    "6669786564363418632003281042021000121a0a0e72657065617465645f" .
+    "666c6f617418642003280242021000121b0a0f72657065617465645f646f" .
+    "75626c651865200328014202100012190a0d72657065617465645f626f6f" .
+    "6c1866200328084202100012280a0d72657065617465645f656e756d1867" .
+    "2003280e320d2e666f6f2e54657374456e756d420210002a1d0a08546573" .
+    "74456e756d12080a045a45524f100012070a034f4e451001620670726f74" .
+    "6f33"
+));
+

+ 136 - 0
php/tests/test.proto

@@ -0,0 +1,136 @@
+syntax = "proto3";
+
+import 'test_include.proto';
+
+package foo;
+
+message TestMessage {
+  // Singular
+  int32 optional_int32 = 1;
+  int64 optional_int64 = 2;
+  uint32 optional_uint32 = 3;
+  uint64 optional_uint64 = 4;
+  sint32 optional_sint32 = 5;
+  sint64 optional_sint64 = 6;
+  fixed32 optional_fixed32 = 7;
+  fixed64 optional_fixed64 = 8;
+  sfixed32 optional_sfixed32 = 9;
+  sfixed64 optional_sfixed64 = 10;
+  float optional_float = 11;
+  double optional_double = 12;
+  bool optional_bool = 13;
+  string optional_string = 14;
+  bytes optional_bytes = 15;
+
+  TestEnum optional_enum = 16;
+  Sub optional_message = 17;
+  bar.TestInclude optional_included_message = 18;
+  TestMessage recursive = 19;
+
+  // Repeated
+  repeated    int32 repeated_int32    = 31;
+  repeated    int64 repeated_int64    = 32;
+  repeated   uint32 repeated_uint32   = 33;
+  repeated   uint64 repeated_uint64   = 34;
+  repeated   sint32 repeated_sint32   = 35;
+  repeated   sint64 repeated_sint64   = 36;
+  repeated  fixed32 repeated_fixed32  = 37;
+  repeated  fixed64 repeated_fixed64  = 38;
+  repeated sfixed32 repeated_sfixed32 = 39;
+  repeated sfixed64 repeated_sfixed64 = 40;
+  repeated    float repeated_float    = 41;
+  repeated   double repeated_double   = 42;
+  repeated     bool repeated_bool     = 43;
+  repeated   string repeated_string   = 44;
+  repeated    bytes repeated_bytes    = 45;
+
+  repeated TestEnum repeated_enum = 46;
+  repeated Sub repeated_message = 47;
+  repeated TestMessage repeated_recursive = 48;
+
+  oneof my_oneof {
+    int32  oneof_int32    = 51;
+    int64  oneof_int64    = 52;
+    uint32 oneof_uint32   = 53;
+    uint64 oneof_uint64   = 54;
+    uint32 oneof_sint32   = 55;
+    uint64 oneof_sint64   = 56;
+    uint32 oneof_fixed32  = 57;
+    uint64 oneof_fixed64  = 58;
+    uint32 oneof_sfixed32 = 59;
+    uint64 oneof_sfixed64 = 60;
+    double oneof_double   = 61;
+    float  oneof_float    = 62;
+    bool   oneof_bool     = 63;
+    string oneof_string   = 64;
+    bytes  oneof_bytes    = 65;
+    TestEnum oneof_enum   = 66;
+    Sub    oneof_message  = 67;
+  }
+
+  map<int32,       int32> map_int32_int32       = 71;
+  map<int64,       int64> map_int64_int64       = 72;
+  map<uint32,     uint32> map_uint32_uint32     = 73;
+  map<uint64,     uint64> map_uint64_uint64     = 74;
+  map<sint32,     sint32> map_sint32_sint32     = 75;
+  map<sint64,     sint64> map_sint64_sint64     = 76;
+  map<fixed32,   fixed32> map_fixed32_fixed32   = 77;
+  map<fixed64,   fixed64> map_fixed64_fixed64   = 78;
+  map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 79;
+  map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 80;
+  map<int32,       float> map_int32_float       = 81;
+  map<int32,      double> map_int32_double      = 82;
+  map<bool,         bool> map_bool_bool         = 83;
+  map<string,     string> map_string_string     = 84;
+  map<int32,       bytes> map_int32_bytes       = 85;
+  map<int32,    TestEnum> map_int32_enum        = 86;
+  map<int32,         Sub> map_int32_message     = 87;
+
+  map<int32, TestMessage> map_recursive = 88;
+
+  message Sub {
+    int32 a = 1;
+  }
+
+  // NestedMessage nested_message = 90;
+}
+
+enum TestEnum {
+  ZERO = 0;
+  ONE  = 1;
+}
+
+message TestPackedMessage {
+  repeated int32    repeated_int32    = 90  [packed = true];
+  repeated int64    repeated_int64    = 91  [packed = true];
+  repeated uint32   repeated_uint32   = 92  [packed = true];
+  repeated uint64   repeated_uint64   = 93  [packed = true];
+  repeated sint32   repeated_sint32   = 94  [packed = true];
+  repeated sint64   repeated_sint64   = 95  [packed = true];
+  repeated fixed32  repeated_fixed32  = 96  [packed = true];
+  repeated fixed64  repeated_fixed64  = 97  [packed = true];
+  repeated sfixed32 repeated_sfixed32 = 98  [packed = true];
+  repeated sfixed64 repeated_sfixed64 = 99  [packed = true];
+  repeated float    repeated_float    = 100 [packed = true];
+  repeated double   repeated_double   = 101 [packed = true];
+  repeated bool     repeated_bool     = 102 [packed = true];
+  repeated TestEnum repeated_enum     = 103 [packed = true];
+}
+
+// Need to be in sync with TestPackedMessage.
+message TestUnpackedMessage {
+  repeated int32    repeated_int32    = 90  [packed = false];
+  repeated int64    repeated_int64    = 91  [packed = false];
+  repeated uint32   repeated_uint32   = 92  [packed = false];
+  repeated uint64   repeated_uint64   = 93  [packed = false];
+  repeated sint32   repeated_sint32   = 94  [packed = false];
+  repeated sint64   repeated_sint64   = 95  [packed = false];
+  repeated fixed32  repeated_fixed32  = 96  [packed = false];
+  repeated fixed64  repeated_fixed64  = 97  [packed = false];
+  repeated sfixed32 repeated_sfixed32 = 98  [packed = false];
+  repeated sfixed64 repeated_sfixed64 = 99  [packed = false];
+  repeated float    repeated_float    = 100 [packed = false];
+  repeated double   repeated_double   = 101 [packed = false];
+  repeated bool     repeated_bool     = 102 [packed = false];
+  repeated TestEnum repeated_enum     = 103 [packed = false];
+}

+ 23 - 0
php/tests/test.sh

@@ -0,0 +1,23 @@
+#!/bin/bash
+cd ../ext/google/protobuf/
+make clean
+set -e
+
+phpize && ./configure --enable-debug CFLAGS='-g -O0' && make
+cd -
+
+tests=( array_test.php encode_decode_test.php generated_class_test.php map_field_test.php )
+
+for t in "${tests[@]}"
+do
+  echo "****************************"
+  echo "* $t"
+  echo "****************************"
+  php -dextension=../ext/google/protobuf/modules/protobuf.so `which phpunit` $t
+  echo ""
+done
+
+# Make sure to run the memory test in debug mode.
+php -dextension=../ext/google/protobuf/modules/protobuf.so memory_leak_test.php
+
+USE_ZEND_ALLOC=0 valgrind --leak-check=yes php -dextension=../ext/google/protobuf/modules/protobuf.so memory_leak_test.php

+ 92 - 0
php/tests/test_base.php

@@ -0,0 +1,92 @@
+<?php
+
+use Foo\TestMessage;
+use Foo\TestMessage_Sub;
+
+class TestBase extends PHPUnit_Framework_TestCase
+{
+
+    public function setFields(TestMessage $m)
+    {
+        TestUtil::setTestMessage($m);
+    }
+
+    public function expectFields(TestMessage $m)
+    {
+        $this->assertSame(-42,  $m->getOptionalInt32());
+        $this->assertSame(42,   $m->getOptionalUint32());
+        $this->assertSame(-43,  $m->getOptionalInt64());
+        $this->assertSame(43,   $m->getOptionalUint64());
+        $this->assertSame(-44,  $m->getOptionalSint32());
+        $this->assertSame(-45,  $m->getOptionalSint64());
+        $this->assertSame(46,   $m->getOptionalFixed32());
+        $this->assertSame(47,   $m->getOptionalFixed64());
+        $this->assertSame(-46,  $m->getOptionalSfixed32());
+        $this->assertSame(-47,  $m->getOptionalSfixed64());
+        $this->assertSame(1.5,  $m->getOptionalFloat());
+        $this->assertSame(1.6,  $m->getOptionalDouble());
+        $this->assertSame(true, $m->getOptionalBool());
+        $this->assertSame('a',  $m->getOptionalString());
+        $this->assertSame('b',  $m->getOptionalBytes());
+        $this->assertSame(33,   $m->getOptionalMessage()->getA());
+
+        $this->assertEquals(-42,  $m->getRepeatedInt32()[0]);
+        $this->assertEquals(42,   $m->getRepeatedUint32()[0]);
+        $this->assertEquals(-43,  $m->getRepeatedInt64()[0]);
+        $this->assertEquals(43,   $m->getRepeatedUint64()[0]);
+        $this->assertEquals(-44,  $m->getRepeatedSint32()[0]);
+        $this->assertEquals(-45,  $m->getRepeatedSint64()[0]);
+        $this->assertEquals(46,   $m->getRepeatedFixed32()[0]);
+        $this->assertEquals(47,   $m->getRepeatedFixed64()[0]);
+        $this->assertEquals(-46,  $m->getRepeatedSfixed32()[0]);
+        $this->assertEquals(-47,  $m->getRepeatedSfixed64()[0]);
+        $this->assertEquals(1.5,  $m->getRepeatedFloat()[0]);
+        $this->assertEquals(1.6,  $m->getRepeatedDouble()[0]);
+        $this->assertEquals(true, $m->getRepeatedBool()[0]);
+        $this->assertEquals('a',  $m->getRepeatedString()[0]);
+        $this->assertEquals('b',  $m->getRepeatedBytes()[0]);
+        $this->assertEquals(34,   $m->getRepeatedMessage()[0]->GetA());
+
+        $this->assertEquals(-52,   $m->getRepeatedInt32()[1]);
+        $this->assertEquals(52,    $m->getRepeatedUint32()[1]);
+        $this->assertEquals(-53,   $m->getRepeatedInt64()[1]);
+        $this->assertEquals(53,    $m->getRepeatedUint64()[1]);
+        $this->assertEquals(-54,   $m->getRepeatedSint32()[1]);
+        $this->assertEquals(-55,   $m->getRepeatedSint64()[1]);
+        $this->assertEquals(56,    $m->getRepeatedFixed32()[1]);
+        $this->assertEquals(57,    $m->getRepeatedFixed64()[1]);
+        $this->assertEquals(-56,   $m->getRepeatedSfixed32()[1]);
+        $this->assertEquals(-57,   $m->getRepeatedSfixed64()[1]);
+        $this->assertEquals(2.5,   $m->getRepeatedFloat()[1]);
+        $this->assertEquals(2.6,   $m->getRepeatedDouble()[1]);
+        $this->assertEquals(false, $m->getRepeatedBool()[1]);
+        $this->assertEquals('c',   $m->getRepeatedString()[1]);
+        $this->assertEquals('d',   $m->getRepeatedBytes()[1]);
+        $this->assertEquals(35,    $m->getRepeatedMessage()[1]->GetA());
+    }
+
+    public function expectEmptyFields(TestMessage $m)
+    {
+        $this->assertSame(0,   $m->getOptionalInt32());
+        $this->assertSame(0,   $m->getOptionalUint32());
+        $this->assertSame(0,   $m->getOptionalInt64());
+        $this->assertSame(0,   $m->getOptionalUint64());
+        $this->assertSame(0,   $m->getOptionalSint32());
+        $this->assertSame(0,   $m->getOptionalSint64());
+        $this->assertSame(0,   $m->getOptionalFixed32());
+        $this->assertSame(0,   $m->getOptionalFixed64());
+        $this->assertSame(0,   $m->getOptionalSfixed32());
+        $this->assertSame(0,   $m->getOptionalSfixed64());
+        $this->assertSame(0.0, $m->getOptionalFloat());
+        $this->assertSame(0.0, $m->getOptionalDouble());
+        $this->assertSame(false, $m->getOptionalBool());
+        $this->assertSame('',  $m->getOptionalString());
+        $this->assertSame('',  $m->getOptionalBytes());
+        $this->assertNull($m->getOptionalMessage());
+    }
+
+  // This test is to avoid the warning of no test by php unit.
+  public function testNone()
+  {
+  }
+}

+ 36 - 0
php/tests/test_include.pb.php

@@ -0,0 +1,36 @@
+<?php
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: test_include.proto
+
+namespace Bar;
+
+use Google\Protobuf\Internal\DescriptorPool;
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\GPBUtil;
+
+class TestInclude extends \Google\Protobuf\Internal\Message
+{
+    private $a = 0;
+
+    public function getA()
+    {
+        return $this->a;
+    }
+
+    public function setA($var)
+    {
+        GPBUtil::checkInt32($var);
+        $this->a = $var;
+    }
+
+}
+
+$pool = DescriptorPool::getGeneratedPool();
+
+$pool->internalAddGeneratedFile(hex2bin(
+    "0a3b0a12746573745f696e636c7564652e70726f746f120362617222180a" .
+    "0b54657374496e636c75646512090a0161180120012805620670726f746f" .
+    "33"
+));
+

+ 393 - 0
php/tests/test_util.php

@@ -0,0 +1,393 @@
+<?php
+
+use Foo\TestEnum;
+use Foo\TestMessage;
+use Foo\TestMessage_Sub;
+use Foo\TestPackedMessage;
+use Foo\TestUnpackedMessage;
+
+define('MAX_FLOAT_DIFF', 0.000001);
+
+if (PHP_INT_SIZE == 8) {
+    define('MAX_INT_STRING', '9223372036854775807');
+    define('MAX_INT_UPPER_STRING', '9223372036854775808');
+} else {
+    define('MAX_INT_STRING', '2147483647');
+    define('MAX_INT_UPPER_STRING', '2147483648');
+}
+
+define('MAX_INT32', 2147483647);
+define('MAX_INT32_FLOAT', 2147483647.0);
+define('MAX_INT32_STRING', '2147483647');
+
+define('MIN_INT32', -2147483648);
+define('MIN_INT32_FLOAT', -2147483648.0);
+define('MIN_INT32_STRING', '-2147483648');
+
+define('MAX_UINT32', 4294967295);
+define('MAX_UINT32_FLOAT', 4294967295.0);
+define('MAX_UINT32_STRING', '4294967295');
+
+define('MIN_UINT32', -2147483648);
+define('MIN_UINT32_FLOAT', -2147483648.0);
+define('MIN_UINT32_STRING', '-2147483648');
+
+define('MAX_INT64', 9223372036854775807);
+define('MAX_INT64_STRING', '9223372036854775807');
+
+define('MIN_INT64_STRING', '-9223372036854775808');
+if (PHP_INT_SIZE === 8) {
+    define('MIN_INT64', -9223372036854775808);
+} else {
+    define('MIN_INT64', MIN_INT64_STRING);
+}
+
+define('MAX_UINT64_STRING', '-9223372036854775808');
+define('MAX_UINT64', MAX_UINT64_STRING);
+
+class TestUtil
+{
+
+    public static function setTestMessage(TestMessage $m)
+    {
+        $m->setOptionalInt32(-42);
+        $m->setOptionalInt64(-43);
+        $m->setOptionalUint32(42);
+        $m->setOptionalUint64(43);
+        $m->setOptionalSint32(-44);
+        $m->setOptionalSint64(-45);
+        $m->setOptionalFixed32(46);
+        $m->setOptionalFixed64(47);
+        $m->setOptionalSfixed32(-46);
+        $m->setOptionalSfixed64(-47);
+        $m->setOptionalFloat(1.5);
+        $m->setOptionalDouble(1.6);
+        $m->setOptionalBool(true);
+        $m->setOptionalString('a');
+        $m->setOptionalBytes('b');
+        $m->setOptionalEnum(TestEnum::ONE);
+        $m->setOptionalMessage(new TestMessage_Sub());
+        $m->getOptionalMessage()->SetA(33);
+
+        $m->getRepeatedInt32()    []= -42;
+        $m->getRepeatedInt64()    []= -43;
+        $m->getRepeatedUint32()   []=  42;
+        $m->getRepeatedUint64()   []=  43;
+        $m->getRepeatedSint32()   []= -44;
+        $m->getRepeatedSint64()   []= -45;
+        $m->getRepeatedFixed32()  []=  46;
+        $m->getRepeatedFixed64()  []=  47;
+        $m->getRepeatedSfixed32() []= -46;
+        $m->getRepeatedSfixed64() []= -47;
+        $m->getRepeatedFloat()    []= 1.5;
+        $m->getRepeatedDouble()   []= 1.6;
+        $m->getRepeatedBool()     []= true;
+        $m->getRepeatedString()   []= 'a';
+        $m->getRepeatedBytes()    []= 'b';
+        $m->getRepeatedEnum()     []= TestEnum::ZERO;
+        $m->getRepeatedMessage()  []= new TestMessage_Sub();
+        $m->getRepeatedMessage()[0]->setA(34);
+
+        $m->getRepeatedInt32()    []= -52;
+        $m->getRepeatedInt64()    []= -53;
+        $m->getRepeatedUint32()   []=  52;
+        $m->getRepeatedUint64()   []=  53;
+        $m->getRepeatedSint32()   []= -54;
+        $m->getRepeatedSint64()   []= -55;
+        $m->getRepeatedFixed32()  []=  56;
+        $m->getRepeatedFixed64()  []=  57;
+        $m->getRepeatedSfixed32() []= -56;
+        $m->getRepeatedSfixed64() []= -57;
+        $m->getRepeatedFloat()    []= 2.5;
+        $m->getRepeatedDouble()   []= 2.6;
+        $m->getRepeatedBool()     []= false;
+        $m->getRepeatedString()   []= 'c';
+        $m->getRepeatedBytes()    []= 'd';
+        $m->getRepeatedEnum()     []= TestEnum::ONE;
+        $m->getRepeatedMessage()  []= new TestMessage_Sub();
+        $m->getRepeatedMessage()[1]->SetA(35);
+
+        $m->getMapInt32Int32()[-62] = -62;
+        $m->getMapInt64Int64()[-63] = -63;
+        $m->getMapUint32Uint32()[62] = 62;
+        $m->getMapUint64Uint64()[63] = 63;
+        $m->getMapSint32Sint32()[-64] = -64;
+        $m->getMapSint64Sint64()[-65] = -65;
+        $m->getMapFixed32Fixed32()[66] = 66;
+        $m->getMapFixed64Fixed64()[67] = 67;
+        $m->getMapInt32Float()[1] = 3.5;
+        $m->getMapInt32Double()[1] = 3.6;
+        $m->getMapBoolBool()[true] = true;
+        $m->getMapStringString()['e'] = 'e';
+        $m->getMapInt32Bytes()[1] = 'f';
+        $m->getMapInt32Enum()[1] = TestEnum::ONE;
+        $m->getMapInt32Message()[1] = new TestMessage_Sub();
+        $m->getMapInt32Message()[1]->SetA(36);
+    }
+
+    public static function assertTestMessage(TestMessage $m)
+    {
+        assert(-42 === $m->getOptionalInt32());
+        assert(42  === $m->getOptionalUint32());
+        assert(-43 === $m->getOptionalInt64());
+        assert(43  === $m->getOptionalUint64());
+        assert(-44 === $m->getOptionalSint32());
+        assert(-45 === $m->getOptionalSint64());
+        assert(46  === $m->getOptionalFixed32());
+        assert(47  === $m->getOptionalFixed64());
+        assert(-46 === $m->getOptionalSfixed32());
+        assert(-47 === $m->getOptionalSfixed64());
+        assert(1.5 === $m->getOptionalFloat());
+        assert(1.6 === $m->getOptionalDouble());
+        assert(true=== $m->getOptionalBool());
+        assert('a' === $m->getOptionalString());
+        assert('b' === $m->getOptionalBytes());
+        assert(TestEnum::ONE === $m->getOptionalEnum());
+        assert(33  === $m->getOptionalMessage()->getA());
+
+        assert(-42 === $m->getRepeatedInt32()[0]);
+        assert(42  === $m->getRepeatedUint32()[0]);
+        assert(-43 === $m->getRepeatedInt64()[0]);
+        assert(43  === $m->getRepeatedUint64()[0]);
+        assert(-44 === $m->getRepeatedSint32()[0]);
+        assert(-45 === $m->getRepeatedSint64()[0]);
+        assert(46  === $m->getRepeatedFixed32()[0]);
+        assert(47  === $m->getRepeatedFixed64()[0]);
+        assert(-46 === $m->getRepeatedSfixed32()[0]);
+        assert(-47 === $m->getRepeatedSfixed64()[0]);
+        assert(1.5 === $m->getRepeatedFloat()[0]);
+        assert(1.6 === $m->getRepeatedDouble()[0]);
+        assert(true=== $m->getRepeatedBool()[0]);
+        assert('a' === $m->getRepeatedString()[0]);
+        assert('b' === $m->getRepeatedBytes()[0]);
+        assert(TestEnum::ZERO === $m->getRepeatedEnum()[0]);
+        assert(34  === $m->getRepeatedMessage()[0]->getA());
+
+        assert(-52 === $m->getRepeatedInt32()[1]);
+        assert(52  === $m->getRepeatedUint32()[1]);
+        assert(-53 === $m->getRepeatedInt64()[1]);
+        assert(53  === $m->getRepeatedUint64()[1]);
+        assert(-54 === $m->getRepeatedSint32()[1]);
+        assert(-55 === $m->getRepeatedSint64()[1]);
+        assert(56  === $m->getRepeatedFixed32()[1]);
+        assert(57  === $m->getRepeatedFixed64()[1]);
+        assert(-56 === $m->getRepeatedSfixed32()[1]);
+        assert(-57 === $m->getRepeatedSfixed64()[1]);
+        assert(2.5 === $m->getRepeatedFloat()[1]);
+        assert(2.6 === $m->getRepeatedDouble()[1]);
+        assert(false === $m->getRepeatedBool()[1]);
+        assert('c' === $m->getRepeatedString()[1]);
+        assert('d' === $m->getRepeatedBytes()[1]);
+        assert(TestEnum::ONE === $m->getRepeatedEnum()[1]);
+        assert(35  === $m->getRepeatedMessage()[1]->getA());
+
+        assert(-62 === $m->getMapInt32Int32()[-62]);
+        assert(-63 === $m->getMapInt64Int64()[-63]);
+        assert(62  === $m->getMapUint32Uint32()[62]);
+        assert(63  === $m->getMapUint64Uint64()[63]);
+        assert(-64 === $m->getMapSint32Sint32()[-64]);
+        assert(-65 === $m->getMapSint64Sint64()[-65]);
+        assert(66  === $m->getMapFixed32Fixed32()[66]);
+        assert(67  === $m->getMapFixed64Fixed64()[67]);
+        assert(3.5 === $m->getMapInt32Float()[1]);
+        assert(3.6 === $m->getMapInt32Double()[1]);
+        assert(true === $m->getMapBoolBool()[true]);
+        assert('e' === $m->getMapStringString()['e']);
+        assert('f' === $m->getMapInt32Bytes()[1]);
+        assert(TestEnum::ONE === $m->getMapInt32Enum()[1]);
+        assert(36  === $m->getMapInt32Message()[1]->GetA());
+    }
+
+    public static function getGoldenTestMessage()
+    {
+        return hex2bin(
+            "08D6FFFFFF0F" .
+            "10D5FFFFFFFFFFFFFFFF01" .
+            "182A" .
+            "202B" .
+            "2857" .
+            "3059" .
+            "3D2E000000" .
+            "412F00000000000000" .
+            "4DD2FFFFFF" .
+            "51D1FFFFFFFFFFFFFF" .
+            "5D0000C03F" .
+            "619A9999999999F93F" .
+            "6801" .
+            "720161" .
+            "7A0162" .
+            "800101" .
+            "8A01020821" .
+
+            "F801D6FFFFFF0F" .
+            "F801CCFFFFFF0F" .
+            "8002D5FFFFFFFFFFFFFFFF01" .
+            "8002CBFFFFFFFFFFFFFFFF01" .
+            "88022A" .
+            "880234" .
+            "90022B" .
+            "900235" .
+            "980257" .
+            "98026B" .
+            "A00259" .
+            "A0026D" .
+            "AD022E000000" .
+            "AD0238000000" .
+            "B1022F00000000000000" .
+            "B1023900000000000000" .
+            "BD02D2FFFFFF" .
+            "BD02C8FFFFFF" .
+            "C102D1FFFFFFFFFFFFFF" .
+            "C102C7FFFFFFFFFFFFFF" .
+            "CD020000C03F" .
+            "CD0200002040" .
+            "D1029A9999999999F93F" .
+            "D102CDCCCCCCCCCC0440" .
+            "D80201" .
+            "D80200" .
+            "E2020161" .
+            "E2020163" .
+            "EA020162" .
+            "EA020164" .
+            "F00200" .
+            "F00201" .
+            "FA02020822" .
+            "FA02020823" .
+
+            "BA040C08C2FFFFFF0F10C2FFFFFF0F" .
+            "C2041608C1FFFFFFFFFFFFFFFF0110C1FFFFFFFFFFFFFFFF01" .
+            "CA0404083E103E" .
+            "D20404083F103F" .
+            "DA0404087f107F" .
+            "E20406088101108101" .
+            "EA040A0D420000001542000000" .
+            "F20412094300000000000000114300000000000000" .
+            "8A050708011500006040" .
+            "92050B080111CDCCCCCCCCCC0C40" .
+            "9A050408011001" .
+            "A205060a0165120165" .
+            "AA05050801120166" .
+            "B2050408011001" .
+            "Ba0506080112020824"
+        );
+    }
+
+    public static function setTestPackedMessage($m)
+    {
+        $m->getRepeatedInt32()[] = -42;
+        $m->getRepeatedInt32()[] = -52;
+        $m->getRepeatedInt64()[] = -43;
+        $m->getRepeatedInt64()[] = -53;
+        $m->getRepeatedUint32()[] = 42;
+        $m->getRepeatedUint32()[] = 52;
+        $m->getRepeatedUint64()[] = 43;
+        $m->getRepeatedUint64()[] = 53;
+        $m->getRepeatedSint32()[] = -44;
+        $m->getRepeatedSint32()[] = -54;
+        $m->getRepeatedSint64()[] = -45;
+        $m->getRepeatedSint64()[] = -55;
+        $m->getRepeatedFixed32()[] = 46;
+        $m->getRepeatedFixed32()[] = 56;
+        $m->getRepeatedFixed64()[] = 47;
+        $m->getRepeatedFixed64()[] = 57;
+        $m->getRepeatedSfixed32()[] = -46;
+        $m->getRepeatedSfixed32()[] = -56;
+        $m->getRepeatedSfixed64()[] = -47;
+        $m->getRepeatedSfixed64()[] = -57;
+        $m->getRepeatedFloat()[] = 1.5;
+        $m->getRepeatedFloat()[] = 2.5;
+        $m->getRepeatedDouble()[] = 1.6;
+        $m->getRepeatedDouble()[] = 2.6;
+        $m->getRepeatedBool()[] = true;
+        $m->getRepeatedBool()[] = false;
+        $m->getRepeatedEnum()[] = TestEnum::ONE;
+        $m->getRepeatedEnum()[] = TestEnum::ZERO;
+    }
+
+    public static function assertTestPackedMessage($m)
+    {
+        assert(2 === count($m->getRepeatedInt32()));
+        assert(2 === count($m->getRepeatedInt64()));
+        assert(2 === count($m->getRepeatedUint32()));
+        assert(2 === count($m->getRepeatedUint64()));
+        assert(2 === count($m->getRepeatedSint32()));
+        assert(2 === count($m->getRepeatedSint64()));
+        assert(2 === count($m->getRepeatedFixed32()));
+        assert(2 === count($m->getRepeatedFixed64()));
+        assert(2 === count($m->getRepeatedSfixed32()));
+        assert(2 === count($m->getRepeatedSfixed64()));
+        assert(2 === count($m->getRepeatedFloat()));
+        assert(2 === count($m->getRepeatedDouble()));
+        assert(2 === count($m->getRepeatedBool()));
+        assert(2 === count($m->getRepeatedEnum()));
+
+        assert(-42 === $m->getRepeatedInt32()[0]);
+        assert(-52 === $m->getRepeatedInt32()[1]);
+        assert(-43 === $m->getRepeatedInt64()[0]);
+        assert(-53 === $m->getRepeatedInt64()[1]);
+        assert(42  === $m->getRepeatedUint32()[0]);
+        assert(52  === $m->getRepeatedUint32()[1]);
+        assert(43  === $m->getRepeatedUint64()[0]);
+        assert(53  === $m->getRepeatedUint64()[1]);
+        assert(-44 === $m->getRepeatedSint32()[0]);
+        assert(-54 === $m->getRepeatedSint32()[1]);
+        assert(-45 === $m->getRepeatedSint64()[0]);
+        assert(-55 === $m->getRepeatedSint64()[1]);
+        assert(46  === $m->getRepeatedFixed32()[0]);
+        assert(56  === $m->getRepeatedFixed32()[1]);
+        assert(47  === $m->getRepeatedFixed64()[0]);
+        assert(57  === $m->getRepeatedFixed64()[1]);
+        assert(-46 === $m->getRepeatedSfixed32()[0]);
+        assert(-56 === $m->getRepeatedSfixed32()[1]);
+        assert(-47 === $m->getRepeatedSfixed64()[0]);
+        assert(-57 === $m->getRepeatedSfixed64()[1]);
+        assert(1.5 === $m->getRepeatedFloat()[0]);
+        assert(2.5 === $m->getRepeatedFloat()[1]);
+        assert(1.6 === $m->getRepeatedDouble()[0]);
+        assert(2.6 === $m->getRepeatedDouble()[1]);
+        assert(true  === $m->getRepeatedBool()[0]);
+        assert(false === $m->getRepeatedBool()[1]);
+        assert(TestEnum::ONE  === $m->getRepeatedEnum()[0]);
+        assert(TestEnum::ZERO === $m->getRepeatedEnum()[1]);
+    }
+
+    public static function getGoldenTestPackedMessage()
+    {
+        return hex2bin(
+            "D2050AD6FFFFFF0FCCFFFFFF0F" .
+            "DA0514D5FFFFFFFFFFFFFFFF01CBFFFFFFFFFFFFFFFF01" .
+            "E205022A34" .
+            "EA05022B35" .
+            "F20502576B" .
+            "FA0502596D" .
+            "8206082E00000038000000" .
+            "8A06102F000000000000003900000000000000" .
+            "920608D2FFFFFFC8FFFFFF" .
+            "9A0610D1FFFFFFFFFFFFFFC7FFFFFFFFFFFFFF" .
+            "A206080000C03F00002040" .
+            "AA06109A9999999999F93FCDCCCCCCCCCC0440" .
+            "B206020100" .
+            "BA06020100"
+        );
+    }
+
+    public static function getGoldenTestUnpackedMessage()
+    {
+        return hex2bin(
+            "D005D6FFFFFF0FD005CCFFFFFF0F" .
+            "D805D5FFFFFFFFFFFFFFFF01D805CBFFFFFFFFFFFFFFFF01" .
+            "E0052AE00534" .
+            "E8052BE80535" .
+            "F00557F0056B" .
+            "F80559F8056D" .
+            "85062E000000850638000000" .
+            "89062F0000000000000089063900000000000000" .
+            "9506D2FFFFFF9506C8FFFFFF" .
+            "9906D1FFFFFFFFFFFFFF9906C7FFFFFFFFFFFFFF" .
+            "A5060000C03FA50600002040" .
+            "A9069A9999999999F93FA906CDCCCCCCCCCC0440" .
+            "B00601B00600" .
+            "B80601B80600"
+        );
+    }
+}

+ 13 - 0
phpunit.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="./vendor/autoload.php"
+         colors="true">
+  <testsuites>
+    <testsuite name="protobuf-tests">
+      <file>php/tests/php_implementation_test.php</file>
+      <file>php/tests/array_test.php</file>
+      <file>php/tests/encode_decode_test.php</file>
+      <file>php/tests/generated_class_test.php</file>
+      <file>php/tests/map_field_test.php</file>
+    </testsuite>
+  </testsuites>
+</phpunit>

+ 2 - 0
src/Makefile.am

@@ -162,6 +162,7 @@ nobase_include_HEADERS =                                        \
   google/protobuf/compiler/js/js_generator.h                    \
   google/protobuf/compiler/objectivec/objectivec_generator.h    \
   google/protobuf/compiler/objectivec/objectivec_helpers.h      \
+  google/protobuf/compiler/php/php_generator.h                  \
   google/protobuf/compiler/python/python_generator.h            \
   google/protobuf/compiler/ruby/ruby_generator.h                \
   google/protobuf/util/type_resolver.h                          \
@@ -443,6 +444,7 @@ libprotoc_la_SOURCES =                                         \
   google/protobuf/compiler/objectivec/objectivec_oneof.h       \
   google/protobuf/compiler/objectivec/objectivec_primitive_field.cc \
   google/protobuf/compiler/objectivec/objectivec_primitive_field.h \
+  google/protobuf/compiler/php/php_generator.cc                \
   google/protobuf/compiler/python/python_generator.cc          \
   google/protobuf/compiler/ruby/ruby_generator.cc              \
   google/protobuf/compiler/csharp/csharp_doc_comment.cc        \

+ 4 - 6
src/google/protobuf/compiler/main.cc

@@ -35,8 +35,7 @@
 #include <google/protobuf/compiler/python/python_generator.h>
 #include <google/protobuf/compiler/java/java_generator.h>
 #include <google/protobuf/compiler/javanano/javanano_generator.h>
-// TODO(teboring): Add it back when php implementation is ready
-// #include <google/protobuf/compiler/php/php_generator.h>
+#include <google/protobuf/compiler/php/php_generator.h>
 #include <google/protobuf/compiler/ruby/ruby_generator.h>
 #include <google/protobuf/compiler/csharp/csharp_generator.h>
 #include <google/protobuf/compiler/objectivec/objectivec_generator.h>
@@ -68,11 +67,10 @@ int main(int argc, char* argv[]) {
   cli.RegisterGenerator("--javanano_out", &javanano_generator,
                         "Generate Java Nano source file.");
 
-  // TODO(teboring): Add it back when php implementation is ready
   // PHP
-  // google::protobuf::compiler::php::Generator php_generator;
-  // cli.RegisterGenerator("--php_out", &php_generator,
-  //                      "Generate PHP source file.");
+  google::protobuf::compiler::php::Generator php_generator;
+  cli.RegisterGenerator("--php_out", &php_generator,
+                        "Generate PHP source file.");
 
   // Ruby
   google::protobuf::compiler::ruby::Generator rb_generator;

+ 781 - 0
src/google/protobuf/compiler/php/php_generator.cc

@@ -0,0 +1,781 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <google/protobuf/compiler/php/php_generator.h>
+
+#include <google/protobuf/compiler/code_generator.h>
+#include <google/protobuf/compiler/plugin.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/descriptor.pb.h>
+#include <google/protobuf/io/printer.h>
+#include <google/protobuf/io/zero_copy_stream.h>
+#include <google/protobuf/stubs/strutil.h>
+
+#include <sstream>
+
+using google::protobuf::internal::scoped_ptr;
+
+const std::string kDescriptorFile = "google/protobuf/descriptor.proto";
+const std::string kDescriptorPackageName = "Google\\Protobuf\\Internal";
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace php {
+
+// Forward decls.
+std::string PhpName(const std::string& full_name, bool is_descriptor);
+std::string DefaultForField(google::protobuf::FieldDescriptor* field);
+std::string IntToString(int32 value);
+std::string GeneratedFileName(const std::string& proto_file,
+                              bool is_descriptor);
+std::string LabelForField(google::protobuf::FieldDescriptor* field);
+std::string TypeName(google::protobuf::FieldDescriptor* field);
+std::string UnderscoresToCamelCase(const string& name, bool cap_first_letter);
+std::string EscapeDollor(const string& to_escape);
+std::string BinaryToHex(const string& binary);
+void GenerateMessage(const string& name_prefix,
+                     const google::protobuf::Descriptor* message,
+                     bool is_descriptor,
+                     google::protobuf::io::Printer* printer);
+void GenerateEnum(const google::protobuf::EnumDescriptor* en,
+                  google::protobuf::io::Printer* printer);
+void Indent(google::protobuf::io::Printer* printer);
+void Outdent(google::protobuf::io::Printer* printer);
+
+std::string MessageName(const google::protobuf::Descriptor* message,
+                        bool is_descriptor) {
+  string message_name = message->name();
+  const google::protobuf::Descriptor* descriptor = message->containing_type();
+  while (descriptor != NULL) {
+    message_name = descriptor->name() + '_' + message_name;
+    descriptor = descriptor->containing_type();
+  }
+  return PhpName(message->file()->package(), is_descriptor) + '\\' +
+         message_name;
+}
+
+std::string MessageFullName(const google::protobuf::Descriptor* message,
+                            bool is_descriptor) {
+  if (is_descriptor) {
+    return StringReplace(message->full_name(),
+                         "google.protobuf",
+                         "google.protobuf.internal", false);
+  } else {
+    return message->full_name();
+  }
+}
+
+std::string EnumFullName(const google::protobuf::EnumDescriptor* envm,
+                         bool is_descriptor) {
+  if (is_descriptor) {
+    return StringReplace(envm->full_name(),
+                         "google.protobuf",
+                         "google.protobuf.internal", false);
+  } else {
+    return envm->full_name();
+  }
+}
+
+std::string EnumClassName(const google::protobuf::EnumDescriptor* envm) {
+  string enum_class_name = envm->name();
+  const google::protobuf::Descriptor* descriptor = envm->containing_type();
+  while (descriptor != NULL) {
+    enum_class_name = descriptor->name() + '_' + enum_class_name;
+    descriptor = descriptor->containing_type();
+  }
+  return enum_class_name;
+}
+
+std::string EnumName(const google::protobuf::EnumDescriptor* envm,
+                     bool is_descriptor) {
+  string enum_name = EnumClassName(envm);
+  return PhpName(envm->file()->package(), is_descriptor) + '\\' + enum_name;
+}
+
+std::string PhpName(const std::string& full_name, bool is_descriptor) {
+  if (is_descriptor) {
+    return kDescriptorPackageName;
+  }
+
+  std::string result;
+  bool cap_next_letter = true;
+  for (int i = 0; i < full_name.size(); i++) {
+    if ('a' <= full_name[i] && full_name[i] <= 'z' && cap_next_letter) {
+      result += full_name[i] + ('A' - 'a');
+      cap_next_letter = false;
+    } else if (full_name[i] == '.') {
+      result += '\\';
+      cap_next_letter = true;
+    } else {
+      result += full_name[i];
+      cap_next_letter = false;
+    }
+  }
+  return result;
+}
+
+std::string DefaultForField(const google::protobuf::FieldDescriptor* field) {
+  switch (field->type()) {
+    case FieldDescriptor::TYPE_INT32:
+    case FieldDescriptor::TYPE_INT64:
+    case FieldDescriptor::TYPE_UINT32:
+    case FieldDescriptor::TYPE_UINT64:
+    case FieldDescriptor::TYPE_SINT32:
+    case FieldDescriptor::TYPE_SINT64:
+    case FieldDescriptor::TYPE_FIXED32:
+    case FieldDescriptor::TYPE_FIXED64:
+    case FieldDescriptor::TYPE_SFIXED32:
+    case FieldDescriptor::TYPE_SFIXED64:
+    case FieldDescriptor::TYPE_ENUM: return "0";
+    case FieldDescriptor::TYPE_DOUBLE:
+    case FieldDescriptor::TYPE_FLOAT: return "0.0";
+    case FieldDescriptor::TYPE_BOOL: return "false";
+    case FieldDescriptor::TYPE_STRING:
+    case FieldDescriptor::TYPE_BYTES: return "''";
+    case FieldDescriptor::TYPE_MESSAGE:
+    case FieldDescriptor::TYPE_GROUP: return "null";
+    default: assert(false); return "";
+  }
+}
+
+std::string GeneratedFileName(const std::string& proto_file,
+                              bool is_descriptor) {
+  if (is_descriptor) {
+    return "descriptor_internal.pb.php";
+  } else {
+    int lastindex = proto_file.find_last_of(".");
+    return proto_file.substr(0, lastindex) + ".pb.php";
+  }
+}
+
+std::string IntToString(int32 value) {
+  std::ostringstream os;
+  os << value;
+  return os.str();
+}
+
+std::string LabelForField(const google::protobuf::FieldDescriptor* field) {
+  switch (field->label()) {
+    case FieldDescriptor::LABEL_OPTIONAL: return "optional";
+    case FieldDescriptor::LABEL_REQUIRED: return "required";
+    case FieldDescriptor::LABEL_REPEATED: return "repeated";
+    default: assert(false); return "";
+  }
+}
+
+std::string TypeName(const google::protobuf::FieldDescriptor* field) {
+  switch (field->type()) {
+    case FieldDescriptor::TYPE_INT32: return "int32";
+    case FieldDescriptor::TYPE_INT64: return "int64";
+    case FieldDescriptor::TYPE_UINT32: return "uint32";
+    case FieldDescriptor::TYPE_UINT64: return "uint64";
+    case FieldDescriptor::TYPE_SINT32: return "sint32";
+    case FieldDescriptor::TYPE_SINT64: return "sint64";
+    case FieldDescriptor::TYPE_FIXED32: return "fixed32";
+    case FieldDescriptor::TYPE_FIXED64: return "fixed64";
+    case FieldDescriptor::TYPE_SFIXED32: return "sfixed32";
+    case FieldDescriptor::TYPE_SFIXED64: return "sfixed64";
+    case FieldDescriptor::TYPE_DOUBLE: return "double";
+    case FieldDescriptor::TYPE_FLOAT: return "float";
+    case FieldDescriptor::TYPE_BOOL: return "bool";
+    case FieldDescriptor::TYPE_ENUM: return "enum";
+    case FieldDescriptor::TYPE_STRING: return "string";
+    case FieldDescriptor::TYPE_BYTES: return "bytes";
+    case FieldDescriptor::TYPE_MESSAGE: return "message";
+    case FieldDescriptor::TYPE_GROUP: return "group";
+    default: assert(false); return "";
+  }
+}
+
+std::string EnumOrMessageSuffix(
+    const google::protobuf::FieldDescriptor* field, bool is_descriptor) {
+  if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+    return ", '" + MessageFullName(field->message_type(), is_descriptor) + "'";
+  }
+  if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
+    return ", '" + EnumFullName(field->enum_type(), is_descriptor) + "'";
+  }
+  return "";
+}
+
+// Converts a name to camel-case. If cap_first_letter is true, capitalize the
+// first letter.
+std::string UnderscoresToCamelCase(const string& input, bool cap_first_letter) {
+  std::string result;
+  for (int i = 0; i < input.size(); i++) {
+    if ('a' <= input[i] && input[i] <= 'z') {
+      if (cap_first_letter) {
+        result += input[i] + ('A' - 'a');
+      } else {
+        result += input[i];
+      }
+      cap_first_letter = false;
+    } else if ('A' <= input[i] && input[i] <= 'Z') {
+      if (i == 0 && !cap_first_letter) {
+        // Force first letter to lower-case unless explicitly told to
+        // capitalize it.
+        result += input[i] + ('a' - 'A');
+      } else {
+        // Capital letters after the first are left as-is.
+        result += input[i];
+      }
+      cap_first_letter = false;
+    } else if ('0' <= input[i] && input[i] <= '9') {
+      result += input[i];
+      cap_first_letter = true;
+    } else {
+      cap_first_letter = true;
+    }
+  }
+  // Add a trailing "_" if the name should be altered.
+  if (input[input.size() - 1] == '#') {
+    result += '_';
+  }
+  return result;
+}
+
+std::string EscapeDollor(const string& to_escape) {
+  return StringReplace(to_escape, "$", "\\$", true);
+}
+
+std::string BinaryToHex(const string& src) {
+  string dest;
+  size_t i;
+  unsigned char symbol[16] = {
+    '0', '1', '2', '3',
+    '4', '5', '6', '7',
+    '8', '9', 'a', 'b',
+    'c', 'd', 'e', 'f',
+  };
+
+  dest.resize(src.size() * 2);
+  char* append_ptr = &dest[0];
+
+  for (i = 0; i < src.size(); i++) {
+    *append_ptr++ = symbol[(src[i] & 0xf0) >> 4];
+    *append_ptr++ = symbol[src[i] & 0x0f];
+  }
+
+  return dest;
+}
+
+void Indent(google::protobuf::io::Printer* printer) {
+  printer->Indent();
+  printer->Indent();
+}
+void Outdent(google::protobuf::io::Printer* printer) {
+  printer->Outdent();
+  printer->Outdent();
+}
+
+void GenerateField(const google::protobuf::FieldDescriptor* field,
+                   google::protobuf::io::Printer* printer, bool is_descriptor) {
+  if (field->is_repeated()) {
+    printer->Print(
+        "private $@name@;\n",
+        "name", field->name());
+  } else if (field->containing_oneof()) {
+    // Oneof fields are handled by GenerateOneofField.
+    return;
+  } else {
+    printer->Print(
+        "private $@name@ = @default@;\n",
+        "name", field->name(),
+        "default", DefaultForField(field));
+  }
+
+  if (is_descriptor) {
+    printer->Print(
+        "private $has_@name@ = false;\n",
+        "name", field->name());
+  }
+}
+
+void GenerateOneofField(const google::protobuf::OneofDescriptor* oneof,
+                        google::protobuf::io::Printer* printer) {
+  // Oneof property needs to be protected in order to be accessed by parent
+  // class in implementation.
+  printer->Print(
+      "protected $@name@;\n",
+      "name", oneof->name());
+}
+
+void GenerateFieldAccessor(const google::protobuf::FieldDescriptor* field,
+                           bool is_descriptor,
+                           google::protobuf::io::Printer* printer) {
+  const OneofDescriptor* oneof = field->containing_oneof();
+
+  // Generate getter.
+  if (oneof != NULL) {
+    printer->Print(
+        "public function get@camel_name@()\n"
+        "{\n"
+        "    return $this->readOneof(@number@);\n"
+        "}\n\n",
+        "camel_name", UnderscoresToCamelCase(field->name(), true),
+        "number", IntToString(field->number()));
+  } else {
+    printer->Print(
+        "public function get@camel_name@()\n"
+        "{\n"
+        "    return $this->@name@;\n"
+        "}\n\n",
+        "camel_name", UnderscoresToCamelCase(field->name(), true), "name",
+        field->name());
+  }
+
+  // Generate setter.
+  printer->Print(
+      "public function set@camel_name@(@var@)\n"
+      "{\n",
+      "camel_name", UnderscoresToCamelCase(field->name(), true),
+      "var", (field->is_repeated() ||
+              field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
+             "&$var": "$var");
+
+  Indent(printer);
+
+  // Type check.
+  if (field->is_map()) {
+  } else if (field->is_repeated()) {
+    printer->Print(
+        "GPBUtil::checkRepeatedField($var, GPBType::@type@",
+        "type", ToUpper(field->type_name()));
+    if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+      printer->Print(
+          ", \\@class_name@);\n",
+          "class_name",
+          MessageName(field->message_type(), is_descriptor) + "::class");
+    } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
+      printer->Print(
+          ", @class_name@);\n",
+          "class_name",
+          EnumName(field->enum_type(), is_descriptor) + "::class");
+    } else {
+      printer->Print(");\n");
+    }
+  } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+    printer->Print(
+        "GPBUtil::checkMessage($var, \\@class_name@::class);\n",
+        "class_name", MessageName(field->message_type(), is_descriptor));
+  } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
+    printer->Print(
+        "GPBUtil::checkEnum($var, \\@class_name@::class);\n",
+        "class_name", EnumName(field->enum_type(), is_descriptor));
+  } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_STRING) {
+    printer->Print(
+        "GPBUtil::checkString($var, @utf8@);\n",
+        "utf8",
+        field->type() == FieldDescriptor::TYPE_STRING ? "True": "False");
+  } else {
+    printer->Print(
+        "GPBUtil::check@type@($var);\n",
+        "type", UnderscoresToCamelCase(field->cpp_type_name(), true));
+  }
+
+  if (oneof != NULL) {
+    printer->Print(
+        "$this->writeOneof(@number@, $var);\n",
+        "number", IntToString(field->number()));
+  } else {
+    printer->Print(
+        "$this->@name@ = $var;\n",
+        "name", field->name());
+  }
+
+  // Set has bit for proto2 only.
+  if (is_descriptor) {
+    printer->Print(
+        "$this->has_@field_name@ = true;\n",
+        "field_name", field->name());
+  }
+
+  Outdent(printer);
+
+  printer->Print(
+      "}\n\n");
+
+  // Generate has method for proto2 only.
+  if (is_descriptor) {
+    printer->Print(
+        "public function has@camel_name@()\n"
+        "{\n"
+        "    return $this->has_@field_name@;\n"
+        "}\n\n",
+        "camel_name", UnderscoresToCamelCase(field->name(), true),
+        "field_name", field->name());
+  }
+}
+
+void GenerateRepeatedFieldDecode(
+    const google::protobuf::FieldDescriptor* field,
+    google::protobuf::io::Printer* printer) {
+  printer->Print(
+      "if ($input->read@cap_wire_type@($var)) return False;\n"
+      "$this->get@cap_field_name@() []= $var;\n",
+      "cap_field_name", UnderscoresToCamelCase(field->name(), true),
+      "cap_wire_type", UnderscoresToCamelCase(field->type_name(), true));
+}
+
+void GeneratePrimitiveFieldDecode(
+    const google::protobuf::FieldDescriptor* field,
+    google::protobuf::io::Printer* printer) {
+  printer->Print(
+      "if ($input->read@cap_wire_type@($var)) return False;\n"
+      "$this->set@cap_field_name@($var);\n",
+      "cap_field_name", UnderscoresToCamelCase(field->name(), true),
+      "cap_wire_type", UnderscoresToCamelCase(field->type_name(), true));
+}
+
+void GenerateFieldDecode(const google::protobuf::FieldDescriptor* field,
+                         google::protobuf::io::Printer* printer) {
+  printer->Print(
+      "case @number@:\n",
+      "number", IntToString(field->number()));
+  Indent(printer);
+
+  if (field->is_repeated()) {
+    GenerateRepeatedFieldDecode(field, printer);
+  } else {
+    GeneratePrimitiveFieldDecode(field, printer);
+  }
+
+  printer->Print(
+      "break;\n");
+  Outdent(printer);
+}
+
+void GenerateMessage(const string& name_prefix,
+                     const google::protobuf::Descriptor* message,
+                     bool is_descriptor,
+                     google::protobuf::io::Printer* printer) {
+  // Don't generate MapEntry messages -- we use the PHP extension's native
+  // support for map fields instead.
+  if (message->options().map_entry()) {
+    return;
+  }
+
+  string message_name = name_prefix.empty()?
+      message->name() : name_prefix + "_" + message->name();
+
+  printer->Print(
+      "class @name@ extends \\Google\\Protobuf\\Internal\\Message\n"
+      "{\n",
+      "name", message_name);
+  Indent(printer);
+
+  // Field and oneof definitions.
+  for (int i = 0; i < message->field_count(); i++) {
+    const FieldDescriptor* field = message->field(i);
+    GenerateField(field, printer, is_descriptor);
+  }
+  for (int i = 0; i < message->oneof_decl_count(); i++) {
+    const OneofDescriptor* oneof = message->oneof_decl(i);
+    GenerateOneofField(oneof, printer);
+  }
+  printer->Print("\n");
+
+  // Field and oneof accessors.
+  for (int i = 0; i < message->field_count(); i++) {
+    const FieldDescriptor* field = message->field(i);
+    GenerateFieldAccessor(field, is_descriptor, printer);
+  }
+  for (int i = 0; i < message->oneof_decl_count(); i++) {
+    const google::protobuf::OneofDescriptor* oneof = message->oneof_decl(i);
+    printer->Print(
+      "public function get@camel_name@()\n"
+      "{\n"
+      "    return $this->@name@;\n"
+      "}\n\n",
+      "camel_name", UnderscoresToCamelCase(oneof->name(), true), "name",
+      oneof->name());
+  }
+
+  Outdent(printer);
+  printer->Print("}\n\n");
+
+  // Nested messages and enums.
+  for (int i = 0; i < message->nested_type_count(); i++) {
+    GenerateMessage(message_name, message->nested_type(i), is_descriptor,
+                    printer);
+  }
+  for (int i = 0; i < message->enum_type_count(); i++) {
+    GenerateEnum(message->enum_type(i), printer);
+  }
+}
+
+void GenerateEnumToPool(const google::protobuf::EnumDescriptor* en,
+                        bool is_descriptor,
+                        google::protobuf::io::Printer* printer) {
+  printer->Print(
+      "$pool->addEnum('@name@', @class_name@::class)\n",
+      "name", EnumFullName(en, is_descriptor),
+      "class_name", en->name());
+  Indent(printer);
+
+  for (int i = 0; i < en->value_count(); i++) {
+    const EnumValueDescriptor* value = en->value(i);
+    printer->Print(
+        "->value(\"@name@\", @number@)\n",
+        "name", value->name(),
+        "number", IntToString(value->number()));
+  }
+  printer->Print("->finalizeToPool();\n\n");
+  Outdent(printer);
+}
+
+void GenerateMessageToPool(const string& name_prefix,
+                           const google::protobuf::Descriptor* message,
+                           bool is_descriptor,
+                           google::protobuf::io::Printer* printer) {
+  // Don't generate MapEntry messages -- we use the PHP extension's native
+  // support for map fields instead.
+  if (message->options().map_entry()) {
+    return;
+  }
+  string class_name = name_prefix.empty()?
+      message->name() : name_prefix + "_" + message->name();
+
+  printer->Print(
+      "$pool->addMessage('@message@', @class_name@::class)\n",
+      "message", MessageFullName(message, is_descriptor),
+      "class_name", class_name);
+
+  Indent(printer);
+
+  for (int i = 0; i < message->field_count(); i++) {
+    const FieldDescriptor* field = message->field(i);
+    if (field->is_map()) {
+      const FieldDescriptor* key =
+          field->message_type()->FindFieldByName("key");
+      const FieldDescriptor* val =
+          field->message_type()->FindFieldByName("value");
+      printer->Print(
+          "->map('@field@', GPBType::@key@, "
+          "GPBType::@value@, @number@@other@)\n",
+          "field", field->name(),
+          "key", ToUpper(key->type_name()),
+          "value", ToUpper(val->type_name()),
+          "number", SimpleItoa(field->number()),
+          "other", EnumOrMessageSuffix(val, is_descriptor));
+    } else if (!field->containing_oneof()) {
+      printer->Print(
+          "->@label@('@field@', GPBType::@type@, @number@@other@)\n",
+          "field", field->name(),
+          "label", LabelForField(field),
+          "type", ToUpper(field->type_name()),
+          "number", SimpleItoa(field->number()),
+          "other", EnumOrMessageSuffix(field, is_descriptor));
+    }
+  }
+
+  // oneofs.
+  for (int i = 0; i < message->oneof_decl_count(); i++) {
+    const OneofDescriptor* oneof = message->oneof_decl(i);
+    printer->Print("->oneof(@name@)\n",
+                   "name", oneof->name());
+    Indent(printer);
+    for (int index = 0; index < oneof->field_count(); index++) {
+      const FieldDescriptor* field = oneof->field(index);
+      printer->Print(
+          "->value('@field@', GPBType::@type@, @number@@other@)\n",
+          "field", field->name(),
+          "type", ToUpper(field->type_name()),
+          "number", SimpleItoa(field->number()),
+          "other", EnumOrMessageSuffix(field, is_descriptor));
+    }
+    printer->Print("->finish()\n");
+    Outdent(printer);
+  }
+
+  printer->Print(
+      "->finalizeToPool();\n");
+
+  Outdent(printer);
+
+  printer->Print(
+      "\n");
+
+  for (int i = 0; i < message->nested_type_count(); i++) {
+    GenerateMessageToPool(class_name, message->nested_type(i), is_descriptor,
+                          printer);
+  }
+  for (int i = 0; i < message->enum_type_count(); i++) {
+    GenerateEnumToPool(message->enum_type(i), is_descriptor, printer);
+  }
+}
+
+void GenerateAddFileToPool(const google::protobuf::FileDescriptor* file,
+                           bool is_descriptor,
+                           google::protobuf::io::Printer* printer) {
+  if (is_descriptor) {
+    printer->Print("$pool = DescriptorPool::getGeneratedPool();\n\n");
+
+    for (int i = 0; i < file->message_type_count(); i++) {
+      GenerateMessageToPool("", file->message_type(i), is_descriptor, printer);
+    }
+    for (int i = 0; i < file->enum_type_count(); i++) {
+      GenerateEnumToPool(file->enum_type(i), is_descriptor, printer);
+    }
+
+    printer->Print(
+        "$pool->finish();\n");
+  } else {
+    // Add messages and enums to descriptor pool.
+    printer->Print("$pool = DescriptorPool::getGeneratedPool();\n\n");
+
+    FileDescriptorSet files;
+    FileDescriptorProto* file_proto = files.add_file();
+    file->CopyTo(file_proto);
+    string files_data;
+    files.SerializeToString(&files_data);
+
+    printer->Print("$pool->internalAddGeneratedFile(hex2bin(\n");
+    Indent(printer);
+
+    // Only write 30 bytes per line.
+    static const int kBytesPerLine = 30;
+    for (int i = 0; i < files_data.size(); i += kBytesPerLine) {
+      printer->Print(
+          "\"@data@\"@dot@\n",
+          "data", BinaryToHex(files_data.substr(i, kBytesPerLine)),
+          "dot", i + kBytesPerLine < files_data.size() ? " ." : "");
+    }
+
+    Outdent(printer);
+    printer->Print(
+        "));\n\n");
+  }
+
+}
+
+void GenerateEnum(const google::protobuf::EnumDescriptor* en,
+                  google::protobuf::io::Printer* printer) {
+  printer->Print(
+      "class @name@\n"
+      "{\n",
+      "name", EnumClassName(en));
+  Indent(printer);
+
+  for (int i = 0; i < en->value_count(); i++) {
+    const EnumValueDescriptor* value = en->value(i);
+    printer->Print("const @name@ = @number@;\n",
+                   "name", value->name(),
+                   "number", IntToString(value->number()));
+  }
+  Outdent(printer);
+  printer->Print("}\n\n");
+}
+
+void GenerateUseDeclaration(bool is_descriptor,
+                            google::protobuf::io::Printer* printer) {
+  if (!is_descriptor) {
+    printer->Print(
+        "use Google\\Protobuf\\Internal\\DescriptorPool;\n"
+        "use Google\\Protobuf\\Internal\\GPBType;\n"
+        "use Google\\Protobuf\\Internal\\RepeatedField;\n"
+        "use Google\\Protobuf\\Internal\\GPBUtil;\n\n");
+  } else {
+    printer->Print(
+        "use Google\\Protobuf\\Internal\\DescriptorPool;\n"
+        "use Google\\Protobuf\\Internal\\GPBType;\n"
+        "use Google\\Protobuf\\Internal\\GPBWire;\n"
+        "use Google\\Protobuf\\Internal\\RepeatedField;\n"
+        "use Google\\Protobuf\\Internal\\InputStream;\n\n"
+        "use Google\\Protobuf\\Internal\\GPBUtil;\n\n");
+  }
+}
+
+void GenerateFile(const google::protobuf::FileDescriptor* file,
+                  bool is_descriptor, google::protobuf::io::Printer* printer) {
+  printer->Print(
+    "<?php\n"
+    "# Generated by the protocol buffer compiler.  DO NOT EDIT!\n"
+    "# source: @filename@\n"
+    "\n",
+    "filename", file->name());
+  if (!file->package().empty()) {
+    printer->Print("namespace @name@;\n\n",
+                   "name", PhpName(file->package(), is_descriptor));
+  }
+
+  for (int i = 0; i < file->dependency_count(); i++) {
+    const std::string& name = file->dependency(i)->name();
+    printer->Print("require_once('@name@');\n", "name",
+                   GeneratedFileName(name, is_descriptor));
+  }
+
+  GenerateUseDeclaration(is_descriptor, printer);
+
+  for (int i = 0; i < file->message_type_count(); i++) {
+    GenerateMessage("", file->message_type(i), is_descriptor, printer);
+  }
+  for (int i = 0; i < file->enum_type_count(); i++) {
+    GenerateEnum(file->enum_type(i), printer);
+  }
+
+  GenerateAddFileToPool(file, is_descriptor, printer);
+}
+
+bool Generator::Generate(
+    const FileDescriptor* file,
+    const string& parameter,
+    GeneratorContext* generator_context,
+    string* error) const {
+  bool is_descriptor = parameter == "internal";
+
+  if (is_descriptor && file->name() != kDescriptorFile) {
+    *error =
+        "Can only generate PHP code for google/protobuf/descriptor.proto.\n";
+    return false;
+  }
+
+  if (!is_descriptor && file->syntax() != FileDescriptor::SYNTAX_PROTO3) {
+    *error =
+        "Can only generate PHP code for proto3 .proto files.\n"
+        "Please add 'syntax = \"proto3\";' to the top of your .proto file.\n";
+    return false;
+  }
+
+  std::string filename = GeneratedFileName(file->name(), is_descriptor);
+  scoped_ptr<io::ZeroCopyOutputStream> output(
+      generator_context->Open(filename));
+  io::Printer printer(output.get(), '@');
+
+  GenerateFile(file, is_descriptor, &printer);
+
+  return true;
+}
+
+}  // namespace php
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google

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

@@ -0,0 +1,57 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef GOOGLE_PROTOBUF_COMPILER_PHP_GENERATOR_H__
+#define GOOGLE_PROTOBUF_COMPILER_PHP_GENERATOR_H__
+
+#include <google/protobuf/compiler/code_generator.h>
+
+#include <string>
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace php {
+
+class LIBPROTOC_EXPORT Generator
+    : public google::protobuf::compiler::CodeGenerator {
+  virtual bool Generate(
+      const FileDescriptor* file,
+      const string& parameter,
+      GeneratorContext* generator_context,
+      string* error) const;
+};
+
+}  // namespace php
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // GOOGLE_PROTOBUF_COMPILER_PHP_GENERATOR_H__

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä