Import cobalt 25.master.0.1034729
diff --git a/third_party/protobuf/php/tests/array_test.php b/third_party/protobuf/php/tests/array_test.php
new file mode 100644
index 0000000..b251404
--- /dev/null
+++ b/third_party/protobuf/php/tests/array_test.php
@@ -0,0 +1,593 @@
+<?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]);
+
+        // Test foreach.
+        $arr = new RepeatedField(GPBType::INT32);
+        for ($i = 0; $i < 3; $i++) {
+          $arr[] = $i;
+        }
+        $i = 0;
+        foreach ($arr as $val) {
+          $this->assertSame($i++, $val);
+        }
+        $this->assertSame(3, $i);
+    }
+
+    #########################################################
+    # 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]);
+    }
+
+    #########################################################
+    # Test int64 field.
+    #########################################################
+
+    public function testInt64()
+    {
+        $arr = new RepeatedField(GPBType::INT64);
+
+        // Test append.
+        $arr[] = MAX_INT64;
+        $arr[] = MIN_INT64;
+        $arr[] = 1.1;
+        $arr[] = '2';
+        $arr[] = '3.1';
+        $arr[] = MAX_INT64_STRING;
+        $arr[] = MIN_INT64_STRING;
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_INT64, $arr[0]);
+            $this->assertSame(MIN_INT64, $arr[1]);
+            $this->assertSame('1', $arr[2]);
+            $this->assertSame('2', $arr[3]);
+            $this->assertSame('3', $arr[4]);
+            $this->assertSame(MAX_INT64_STRING, $arr[5]);
+            $this->assertSame(MIN_INT64_STRING, $arr[6]);
+        } else {
+            $this->assertSame(MAX_INT64, $arr[0]);
+            $this->assertSame(MIN_INT64, $arr[1]);
+            $this->assertSame(1, $arr[2]);
+            $this->assertSame(2, $arr[3]);
+            $this->assertSame(3, $arr[4]);
+            $this->assertSame(MAX_INT64, $arr[5]);
+            $this->assertSame(MIN_INT64, $arr[6]);
+        }
+
+
+        $this->assertEquals(7, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            if (PHP_INT_SIZE == 4) {
+                $this->assertSame('0', $arr[$i]);
+            } else {
+                $this->assertSame(0, $arr[$i]);
+            }
+        }
+
+        // Test set.
+        $arr[0] = MAX_INT64;
+        $arr[1] = MIN_INT64;
+        $arr[2] = 1.1;
+        $arr[3] = '2';
+        $arr[4] = '3.1';
+        $arr[5] = MAX_INT64_STRING;
+        $arr[6] = MIN_INT64_STRING;
+
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_INT64_STRING, $arr[0]);
+            $this->assertSame(MIN_INT64_STRING, $arr[1]);
+            $this->assertSame('1', $arr[2]);
+            $this->assertSame('2', $arr[3]);
+            $this->assertSame('3', $arr[4]);
+            $this->assertSame(MAX_INT64_STRING, $arr[5]);
+            $this->assertEquals(MIN_INT64_STRING, $arr[6]);
+        } else {
+            $this->assertSame(MAX_INT64, $arr[0]);
+            $this->assertSame(MIN_INT64, $arr[1]);
+            $this->assertSame(1, $arr[2]);
+            $this->assertSame(2, $arr[3]);
+            $this->assertSame(3, $arr[4]);
+            $this->assertSame(MAX_INT64, $arr[5]);
+            $this->assertEquals(MIN_INT64, $arr[6]);
+        }
+    }
+
+    #########################################################
+    # Test uint64 field.
+    #########################################################
+
+    public function testUint64()
+    {
+        $arr = new RepeatedField(GPBType::UINT64);
+
+        // Test append.
+        $arr[] = MAX_UINT64;
+        $arr[] = 1.1;
+        $arr[] = '2';
+        $arr[] = '3.1';
+        $arr[] = MAX_UINT64_STRING;
+
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_UINT64_STRING, $arr[0]);
+            $this->assertSame('1', $arr[1]);
+            $this->assertSame('2', $arr[2]);
+            $this->assertSame('3', $arr[3]);
+            $this->assertSame(MAX_UINT64_STRING, $arr[4]);
+        } else {
+            $this->assertSame(MAX_UINT64, $arr[0]);
+            $this->assertSame(1, $arr[1]);
+            $this->assertSame(2, $arr[2]);
+            $this->assertSame(3, $arr[3]);
+            $this->assertSame(MAX_UINT64, $arr[4]);
+            $this->assertSame(5, count($arr));
+        }
+
+        $this->assertSame(5, count($arr));
+
+        for ($i = 0; $i < count($arr); $i++) {
+            $arr[$i] = 0;
+            if (PHP_INT_SIZE == 4) {
+                $this->assertSame('0', $arr[$i]);
+            } else {
+                $this->assertSame(0, $arr[$i]);
+            }
+        }
+
+        // Test set.
+        $arr[0] = MAX_UINT64;
+        $arr[1] = 1.1;
+        $arr[2] = '2';
+        $arr[3] = '3.1';
+        $arr[4] = MAX_UINT64_STRING;
+
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_UINT64_STRING, $arr[0]);
+            $this->assertSame('1', $arr[1]);
+            $this->assertSame('2', $arr[2]);
+            $this->assertSame('3', $arr[3]);
+            $this->assertSame(MAX_UINT64_STRING, $arr[4]);
+        } else {
+            $this->assertSame(MAX_UINT64, $arr[0]);
+            $this->assertSame(1, $arr[1]);
+            $this->assertSame(2, $arr[2]);
+            $this->assertSame(3, $arr[3]);
+            $this->assertSame(MAX_UINT64, $arr[4]);
+        }
+    }
+
+    #########################################################
+    # 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);
+    }
+
+    #########################################################
+    # 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);
+    }
+
+    #########################################################
+    # 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]);
+    }
+
+    #########################################################
+    # 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]);
+    }
+
+    #########################################################
+    # Test message field.
+    #########################################################
+
+    public function testMessage()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+
+        // Test append.
+        $sub_m = new Sub();
+        $sub_m->setA(1);
+        $arr[] = $sub_m;
+        $this->assertSame(1, $arr[0]->getA());
+
+        $this->assertEquals(1, count($arr));
+
+        // Test set.
+        $sub_m = new Sub();
+        $sub_m->setA(2);
+        $arr[0] = $sub_m;
+        $this->assertSame(2, $arr[0]->getA());
+
+        // Test foreach.
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+        for ($i = 0; $i < 3; $i++) {
+          $arr[] = new Sub();
+          $arr[$i]->setA($i);
+        }
+        $i = 0;
+        foreach ($arr as $val) {
+          $this->assertSame($i++, $val->getA());
+        }
+        $this->assertSame(3, $i);
+    }
+
+    #########################################################
+    # 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]);
+    }
+
+    #########################################################
+    # Test reference in array
+    #########################################################
+
+    public function testArrayElementIsReferenceInSetters()
+    {
+        // Bool elements
+        $values = [true];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setRepeatedBool($values);
+
+        // Int32 elements
+        $values = [1];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setRepeatedInt32($values);
+
+        // Double elements
+        $values = [1.0];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setRepeatedDouble($values);
+
+        // String elements
+        $values = ['a'];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setRepeatedString($values);
+
+        // Message elements
+        $m = new TestMessage();
+        $subs = [1, 2];
+        foreach ($subs as &$sub) {
+            $sub = new Sub(['a' => $sub]);
+        }
+        $m->setRepeatedMessage($subs);
+    }
+
+    #########################################################
+    # Test memory leak
+    #########################################################
+
+    public function testCycleLeak()
+    {
+        gc_collect_cycles();
+        $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);
+    }
+}
diff --git a/third_party/protobuf/php/tests/autoload.php b/third_party/protobuf/php/tests/autoload.php
old mode 100644
new mode 100755
index af88ba0..b98b13a
--- a/third_party/protobuf/php/tests/autoload.php
+++ b/third_party/protobuf/php/tests/autoload.php
@@ -1,4 +1,27 @@
 <?php
 
-require_once('test.pb.php');
-require_once('test_util.php');
+error_reporting(E_ALL);
+
+function getGeneratedFiles($dir, &$results = array())
+{
+    $files = scandir($dir);
+
+    foreach ($files as $key => $value) {
+        $path = realpath($dir.DIRECTORY_SEPARATOR.$value);
+        if (!is_dir($path)) {
+            $results[] = $path;
+        } else if ($value != "." && $value != "..") {
+            getGeneratedFiles($path, $results);
+        }
+    }
+    return $results;
+}
+
+foreach (getGeneratedFiles("generated") as $filename)
+{
+    if (!is_dir($filename)) {
+        include_once $filename;
+    }
+
+}
+
diff --git a/third_party/protobuf/php/tests/bootstrap_phpunit.php b/third_party/protobuf/php/tests/bootstrap_phpunit.php
new file mode 100644
index 0000000..8452f15
--- /dev/null
+++ b/third_party/protobuf/php/tests/bootstrap_phpunit.php
@@ -0,0 +1,5 @@
+<?php
+
+require_once("vendor/autoload.php");
+
+error_reporting(E_ALL);
diff --git a/third_party/protobuf/php/tests/compatibility_test.sh b/third_party/protobuf/php/tests/compatibility_test.sh
new file mode 100755
index 0000000..f4af524
--- /dev/null
+++ b/third_party/protobuf/php/tests/compatibility_test.sh
@@ -0,0 +1,164 @@
+#!/bin/bash
+
+function use_php() {
+  VERSION=$1
+
+  OLD_PATH=$PATH
+  OLD_CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH
+  OLD_C_INCLUDE_PATH=$C_INCLUDE_PATH
+
+  export PATH=/usr/local/php-${VERSION}/bin:$OLD_PATH
+  export CPLUS_INCLUDE_PATH=/usr/local/php-${VERSION}/include/php/main:/usr/local/php-${VERSION}/include/php/:$OLD_CPLUS_INCLUDE_PATH
+  export C_INCLUDE_PATH=/usr/local/php-${VERSION}/include/php/main:/usr/local/php-${VERSION}/include/php/:$OLD_C_INCLUDE_PATH
+}
+
+function generate_proto() {
+  PROTOC1=$1
+  PROTOC2=$2
+
+  rm -rf generated
+  mkdir generated
+
+  $PROTOC1 --php_out=generated proto/test_include.proto
+  $PROTOC2 --php_out=generated                 \
+    proto/test.proto                           \
+    proto/test_no_namespace.proto              \
+    proto/test_prefix.proto                    \
+    proto/test_php_namespace.proto             \
+    proto/test_empty_php_namespace.proto       \
+    proto/test_reserved_enum_lower.proto       \
+    proto/test_reserved_enum_upper.proto       \
+    proto/test_reserved_enum_value_lower.proto \
+    proto/test_reserved_enum_value_upper.proto \
+    proto/test_reserved_message_lower.proto    \
+    proto/test_reserved_message_upper.proto    \
+    proto/test_service.proto                   \
+    proto/test_service_namespace.proto         \
+    proto/test_descriptors.proto
+
+  pushd ../../src
+  $PROTOC2 --php_out=../php/tests/generated -I../php/tests -I. ../php/tests/proto/test_import_descriptor_proto.proto
+  popd
+}
+
+# Remove tests to expect error. These were added to API tests by mistake.
+function remove_error_test() {
+  local TEMPFILE=`tempfile`
+  cat $1 | \
+  awk -v file=`basename $1` -v dir=`basename $(dirname $1)` '
+    BEGIN {
+      show = 1
+    }
+    /@expectedException PHPUnit_Framework_Error/ { show = 0; next; }
+    / *\*\//                                     { print; next; }
+    / *}/ {
+      if (!show) {
+        show = 1;
+        next;
+      }
+    }
+    show { print }
+  ' > $TEMPFILE
+  cp $TEMPFILE $1
+}
+
+set -ex
+
+# Change to the script's directory.
+cd $(dirname $0)
+
+# The old version of protobuf that we are testing compatibility against.
+case "$1" in
+  ""|3.5.0)
+    OLD_VERSION=3.5.0
+    OLD_VERSION_PROTOC=http://repo1.maven.org/maven2/com/google/protobuf/protoc/$OLD_VERSION/protoc-$OLD_VERSION-linux-x86_64.exe
+    ;;
+  *)
+    echo "[ERROR]: Unknown version number: $1"
+    exit 1
+    ;;
+esac
+
+# Extract the latest protobuf version number.
+VERSION_NUMBER=`grep "PHP_PROTOBUF_VERSION" ../ext/google/protobuf/protobuf.h | sed "s|#define PHP_PROTOBUF_VERSION \"\(.*\)\"|\1|"`
+
+echo "Running compatibility tests between $VERSION_NUMBER and $OLD_VERSION"
+
+# Check protoc
+[ -f ../../src/protoc ] || {
+  echo "[ERROR]: Please build protoc first."
+  exit 1
+}
+
+# Download old test.
+rm -rf protobuf
+git clone https://github.com/protocolbuffers/protobuf.git
+pushd protobuf
+git checkout v$OLD_VERSION
+popd
+
+# Build and copy the new runtime
+use_php 7.1
+pushd ../ext/google/protobuf
+make clean || true
+phpize && ./configure && make
+popd
+
+rm -rf protobuf/php/ext
+rm -rf protobuf/php/src
+cp -r ../ext protobuf/php/ext/
+cp -r ../src protobuf/php/src/
+
+# Download old version protoc compiler (for linux)
+wget $OLD_VERSION_PROTOC -O old_protoc
+chmod +x old_protoc
+
+NEW_PROTOC=`pwd`/../../src/protoc
+OLD_PROTOC=`pwd`/old_protoc
+cd protobuf/php
+composer install
+
+# Remove implementation detail tests.
+tests=( array_test.php encode_decode_test.php generated_class_test.php map_field_test.php well_known_test.php )
+sed -i.bak '/php_implementation_test.php/d' phpunit.xml
+sed -i.bak '/generated_phpdoc_test.php/d' phpunit.xml
+sed -i.bak 's/generated_phpdoc_test.php//g' tests/test.sh
+sed -i.bak 's/generated_service_test.php//g' tests/test.sh
+sed -i.bak '/memory_leak_test.php/d' tests/test.sh
+sed -i.bak '/^    public function testTimestamp()$/,/^    }$/d' tests/well_known_test.php
+sed -i.bak 's/PHPUnit_Framework_TestCase/\\PHPUnit\\Framework\\TestCase/g' tests/array_test.php
+sed -i.bak 's/PHPUnit_Framework_TestCase/\\PHPUnit\\Framework\\TestCase/g' tests/map_field_test.php
+sed -i.bak 's/PHPUnit_Framework_TestCase/\\PHPUnit\\Framework\\TestCase/g' tests/test_base.php
+for t in "${tests[@]}"
+do
+  remove_error_test tests/$t
+done
+
+cd tests
+
+# Test A.1:
+#   proto set 1: use old version
+#   proto set 2 which may import protos in set 1: use old version
+generate_proto $OLD_PROTOC $OLD_PROTOC
+./test.sh
+pushd ..
+./vendor/bin/phpunit
+popd
+
+# Test A.2:
+#   proto set 1: use new version
+#   proto set 2 which may import protos in set 1: use old version
+generate_proto $NEW_PROTOC $OLD_PROTOC
+./test.sh
+pushd ..
+./vendor/bin/phpunit
+popd
+
+# Test A.3:
+#   proto set 1: use old version
+#   proto set 2 which may import protos in set 1: use new version
+generate_proto $OLD_PROTOC $NEW_PROTOC
+./test.sh
+pushd ..
+./vendor/bin/phpunit
+popd
diff --git a/third_party/protobuf/php/tests/compile_extension.sh b/third_party/protobuf/php/tests/compile_extension.sh
new file mode 100755
index 0000000..bbd6696
--- /dev/null
+++ b/third_party/protobuf/php/tests/compile_extension.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+EXTENSION_PATH=$1
+
+pushd $EXTENSION_PATH
+make clean || true
+set -e
+# Add following in configure for debug: --enable-debug CFLAGS='-g -O0'
+phpize && ./configure CFLAGS='-g -O0' && make
+popd
diff --git a/third_party/protobuf/php/tests/descriptors_test.php b/third_party/protobuf/php/tests/descriptors_test.php
new file mode 100644
index 0000000..93683b8
--- /dev/null
+++ b/third_party/protobuf/php/tests/descriptors_test.php
@@ -0,0 +1,246 @@
+<?php
+
+require_once('generated/Descriptors/TestDescriptorsEnum.php');
+require_once('generated/Descriptors/TestDescriptorsMessage.php');
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Google\Protobuf\DescriptorPool;
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\MapField;
+use Descriptors\TestDescriptorsEnum;
+use Descriptors\TestDescriptorsMessage;
+use Descriptors\TestDescriptorsMessage\Sub;
+
+class DescriptorsTest extends TestBase
+{
+
+    // Redefine these here for compatibility with c extension
+    const GPBLABEL_OPTIONAL = 1;
+    const GPBLABEL_REQUIRED = 2;
+    const GPBLABEL_REPEATED = 3;
+
+    const GPBTYPE_DOUBLE   =  1;
+    const GPBTYPE_FLOAT    =  2;
+    const GPBTYPE_INT64    =  3;
+    const GPBTYPE_UINT64   =  4;
+    const GPBTYPE_INT32    =  5;
+    const GPBTYPE_FIXED64  =  6;
+    const GPBTYPE_FIXED32  =  7;
+    const GPBTYPE_BOOL     =  8;
+    const GPBTYPE_STRING   =  9;
+    const GPBTYPE_GROUP    = 10;
+    const GPBTYPE_MESSAGE  = 11;
+    const GPBTYPE_BYTES    = 12;
+    const GPBTYPE_UINT32   = 13;
+    const GPBTYPE_ENUM     = 14;
+    const GPBTYPE_SFIXED32 = 15;
+    const GPBTYPE_SFIXED64 = 16;
+    const GPBTYPE_SINT32   = 17;
+    const GPBTYPE_SINT64   = 18;
+
+    #########################################################
+    # Test descriptor pool.
+    #########################################################
+
+    public function testDescriptorPool()
+    {
+        $pool = DescriptorPool::getGeneratedPool();
+
+        $desc = $pool->getDescriptorByClassName(get_class(new TestDescriptorsMessage()));
+        $this->assertInstanceOf('\Google\Protobuf\Descriptor', $desc);
+
+        $enumDesc = $pool->getEnumDescriptorByClassName(get_class(new TestDescriptorsEnum()));
+        $this->assertInstanceOf('\Google\Protobuf\EnumDescriptor', $enumDesc);
+    }
+
+    public function testDescriptorPoolIncorrectArgs()
+    {
+        $pool = DescriptorPool::getGeneratedPool();
+
+        $desc = $pool->getDescriptorByClassName('NotAClass');
+        $this->assertNull($desc);
+
+        $desc = $pool->getDescriptorByClassName(get_class(new TestDescriptorsEnum()));
+        $this->assertNull($desc);
+
+        $enumDesc = $pool->getEnumDescriptorByClassName(get_class(new TestDescriptorsMessage()));
+        $this->assertNull($enumDesc);
+    }
+
+    #########################################################
+    # Test descriptor.
+    #########################################################
+
+    public function testDescriptor()
+    {
+        $pool = DescriptorPool::getGeneratedPool();
+        $class = get_class(new TestDescriptorsMessage());
+        $this->assertSame('Descriptors\TestDescriptorsMessage', $class);
+        $desc = $pool->getDescriptorByClassName($class);
+
+        $this->assertSame('descriptors.TestDescriptorsMessage', $desc->getFullName());
+        $this->assertSame($class, $desc->getClass());
+
+        $this->assertInstanceOf('\Google\Protobuf\FieldDescriptor', $desc->getField(0));
+        $this->assertSame(7, $desc->getFieldCount());
+
+        $this->assertInstanceOf('\Google\Protobuf\OneofDescriptor', $desc->getOneofDecl(0));
+        $this->assertSame(1, $desc->getOneofDeclCount());
+    }
+
+    #########################################################
+    # Test enum descriptor.
+    #########################################################
+
+    public function testEnumDescriptor()
+    {
+        // WARNINIG - we need to do this so that TestDescriptorsEnum is registered!!?
+        new TestDescriptorsMessage();
+
+        $pool = DescriptorPool::getGeneratedPool();
+
+        $enumDesc = $pool->getEnumDescriptorByClassName(get_class(new TestDescriptorsEnum()));
+
+        // Build map of enum values
+        $enumDescMap = [];
+        for ($i = 0; $i < $enumDesc->getValueCount(); $i++) {
+            $enumValueDesc = $enumDesc->getValue($i);
+            $this->assertInstanceOf('\Google\Protobuf\EnumValueDescriptor', $enumValueDesc);
+            $enumDescMap[$enumValueDesc->getNumber()] = $enumValueDesc->getName();
+        }
+
+        $this->assertSame('ZERO', $enumDescMap[0]);
+        $this->assertSame('ONE', $enumDescMap[1]);
+
+        $this->assertSame(2, $enumDesc->getValueCount());
+    }
+
+    #########################################################
+    # Test field descriptor.
+    #########################################################
+
+    public function testFieldDescriptor()
+    {
+        $pool = DescriptorPool::getGeneratedPool();
+        $desc = $pool->getDescriptorByClassName(get_class(new TestDescriptorsMessage()));
+
+        $fieldDescMap = $this->buildFieldMap($desc);
+
+        // Optional int field
+        $fieldDesc = $fieldDescMap[1];
+        $this->assertSame('optional_int32', $fieldDesc->getName());
+        $this->assertSame(1, $fieldDesc->getNumber());
+        $this->assertSame(self::GPBLABEL_OPTIONAL, $fieldDesc->getLabel());
+        $this->assertSame(self::GPBTYPE_INT32, $fieldDesc->getType());
+        $this->assertFalse($fieldDesc->isMap());
+
+        // Optional enum field
+        $fieldDesc = $fieldDescMap[16];
+        $this->assertSame('optional_enum', $fieldDesc->getName());
+        $this->assertSame(16, $fieldDesc->getNumber());
+        $this->assertSame(self::GPBLABEL_OPTIONAL, $fieldDesc->getLabel());
+        $this->assertSame(self::GPBTYPE_ENUM, $fieldDesc->getType());
+        $this->assertInstanceOf('\Google\Protobuf\EnumDescriptor', $fieldDesc->getEnumType());
+        $this->assertFalse($fieldDesc->isMap());
+
+        // Optional message field
+        $fieldDesc = $fieldDescMap[17];
+        $this->assertSame('optional_message', $fieldDesc->getName());
+        $this->assertSame(17, $fieldDesc->getNumber());
+        $this->assertSame(self::GPBLABEL_OPTIONAL, $fieldDesc->getLabel());
+        $this->assertSame(self::GPBTYPE_MESSAGE, $fieldDesc->getType());
+        $this->assertInstanceOf('\Google\Protobuf\Descriptor', $fieldDesc->getMessageType());
+        $this->assertFalse($fieldDesc->isMap());
+
+        // Repeated int field
+        $fieldDesc = $fieldDescMap[31];
+        $this->assertSame('repeated_int32', $fieldDesc->getName());
+        $this->assertSame(31, $fieldDesc->getNumber());
+        $this->assertSame(self::GPBLABEL_REPEATED, $fieldDesc->getLabel());
+        $this->assertSame(self::GPBTYPE_INT32, $fieldDesc->getType());
+        $this->assertFalse($fieldDesc->isMap());
+
+        // Repeated message field
+        $fieldDesc = $fieldDescMap[47];
+        $this->assertSame('repeated_message', $fieldDesc->getName());
+        $this->assertSame(47, $fieldDesc->getNumber());
+        $this->assertSame(self::GPBLABEL_REPEATED, $fieldDesc->getLabel());
+        $this->assertSame(self::GPBTYPE_MESSAGE, $fieldDesc->getType());
+        $this->assertInstanceOf('\Google\Protobuf\Descriptor', $fieldDesc->getMessageType());
+        $this->assertFalse($fieldDesc->isMap());
+
+        // Oneof int field
+        // Tested further in testOneofDescriptor()
+        $fieldDesc = $fieldDescMap[51];
+        $this->assertSame('oneof_int32', $fieldDesc->getName());
+        $this->assertSame(51, $fieldDesc->getNumber());
+        $this->assertSame(self::GPBLABEL_OPTIONAL, $fieldDesc->getLabel());
+        $this->assertSame(self::GPBTYPE_INT32, $fieldDesc->getType());
+        $this->assertFalse($fieldDesc->isMap());
+
+        // Map int-enum field
+        $fieldDesc = $fieldDescMap[71];
+        $this->assertSame('map_int32_enum', $fieldDesc->getName());
+        $this->assertSame(71, $fieldDesc->getNumber());
+        $this->assertSame(self::GPBLABEL_REPEATED, $fieldDesc->getLabel());
+        $this->assertSame(self::GPBTYPE_MESSAGE, $fieldDesc->getType());
+        $this->assertTrue($fieldDesc->isMap());
+        $mapDesc = $fieldDesc->getMessageType();
+        $this->assertSame('descriptors.TestDescriptorsMessage.MapInt32EnumEntry', $mapDesc->getFullName());
+        $this->assertSame(self::GPBTYPE_INT32, $mapDesc->getField(0)->getType());
+        $this->assertSame(self::GPBTYPE_ENUM, $mapDesc->getField(1)->getType());
+    }
+
+    /**
+     * @expectedException \Exception
+     */
+    public function testFieldDescriptorEnumException()
+    {
+        $pool = DescriptorPool::getGeneratedPool();
+        $desc = $pool->getDescriptorByClassName(get_class(new TestDescriptorsMessage()));
+        $fieldDesc = $desc->getField(0);
+        $fieldDesc->getEnumType();
+    }
+
+    /**
+     * @expectedException \Exception
+     */
+    public function testFieldDescriptorMessageException()
+    {
+        $pool = DescriptorPool::getGeneratedPool();
+        $desc = $pool->getDescriptorByClassName(get_class(new TestDescriptorsMessage()));
+        $fieldDesc = $desc->getField(0);
+        $fieldDesc->getMessageType();
+    }
+
+    #########################################################
+    # Test oneof descriptor.
+    #########################################################
+
+    public function testOneofDescriptor()
+    {
+        $pool = DescriptorPool::getGeneratedPool();
+        $desc = $pool->getDescriptorByClassName(get_class(new TestDescriptorsMessage()));
+
+        $fieldDescMap = $this->buildFieldMap($desc);
+        $fieldDesc = $fieldDescMap[51];
+
+        $oneofDesc = $desc->getOneofDecl(0);
+
+        $this->assertSame('my_oneof', $oneofDesc->getName());
+        $fieldDescFromOneof = $oneofDesc->getField(0);
+        $this->assertSame($fieldDesc, $fieldDescFromOneof);
+        $this->assertSame(1, $oneofDesc->getFieldCount());
+    }
+
+    private function buildFieldMap($desc)
+    {
+        $fieldDescMap = [];
+        for ($i = 0; $i < $desc->getFieldCount(); $i++) {
+            $fieldDesc = $desc->getField($i);
+            $fieldDescMap[$fieldDesc->getNumber()] = $fieldDesc;
+        }
+        return $fieldDescMap;
+    }
+}
diff --git a/third_party/protobuf/php/tests/encode_decode_test.php b/third_party/protobuf/php/tests/encode_decode_test.php
new file mode 100644
index 0000000..5b373bb
--- /dev/null
+++ b/third_party/protobuf/php/tests/encode_decode_test.php
@@ -0,0 +1,1178 @@
+<?php
+
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Google\Protobuf\RepeatedField;
+use Google\Protobuf\GPBType;
+use Foo\TestAny;
+use Foo\TestEnum;
+use Foo\TestMessage;
+use Foo\TestMessage\Sub;
+use Foo\TestPackedMessage;
+use Foo\TestRandomFieldOrder;
+use Foo\TestUnpackedMessage;
+use Google\Protobuf\Any;
+use Google\Protobuf\DoubleValue;
+use Google\Protobuf\FieldMask;
+use Google\Protobuf\FloatValue;
+use Google\Protobuf\Int32Value;
+use Google\Protobuf\UInt32Value;
+use Google\Protobuf\Int64Value;
+use Google\Protobuf\UInt64Value;
+use Google\Protobuf\BoolValue;
+use Google\Protobuf\StringValue;
+use Google\Protobuf\BytesValue;
+use Google\Protobuf\Value;
+use Google\Protobuf\ListValue;
+use Google\Protobuf\Struct;
+use Google\Protobuf\GPBEmpty;
+
+class EncodeDecodeTest extends TestBase
+{
+    public function testDecodeJsonSimple()
+    {
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"optionalInt32\":1}");
+        $this->assertEquals(1, $m->getOptionalInt32());
+    }
+
+    public function testDecodeTopLevelBoolValue()
+    {
+        $m = new BoolValue();
+
+        $m->mergeFromJsonString("true");
+        $this->assertEquals(true, $m->getValue());
+
+        $m->mergeFromJsonString("false");
+        $this->assertEquals(false, $m->getValue());
+    }
+
+    public function testEncodeTopLevelBoolValue()
+    {
+        $m = new BoolValue();
+        $m->setValue(true);
+        $this->assertSame("true", $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelDoubleValue()
+    {
+        $m = new DoubleValue();
+        $m->mergeFromJsonString("1.5");
+        $this->assertEquals(1.5, $m->getValue());
+    }
+
+    public function testEncodeTopLevelDoubleValue()
+    {
+        $m = new DoubleValue();
+        $m->setValue(1.5);
+        $this->assertSame("1.5", $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelFloatValue()
+    {
+        $m = new FloatValue();
+        $m->mergeFromJsonString("1.5");
+        $this->assertEquals(1.5, $m->getValue());
+    }
+
+    public function testEncodeTopLevelFloatValue()
+    {
+        $m = new FloatValue();
+        $m->setValue(1.5);
+        $this->assertSame("1.5", $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelInt32Value()
+    {
+        $m = new Int32Value();
+        $m->mergeFromJsonString("1");
+        $this->assertEquals(1, $m->getValue());
+    }
+
+    public function testEncodeTopLevelInt32Value()
+    {
+        $m = new Int32Value();
+        $m->setValue(1);
+        $this->assertSame("1", $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelUInt32Value()
+    {
+        $m = new UInt32Value();
+        $m->mergeFromJsonString("1");
+        $this->assertEquals(1, $m->getValue());
+    }
+
+    public function testEncodeTopLevelUInt32Value()
+    {
+        $m = new UInt32Value();
+        $m->setValue(1);
+        $this->assertSame("1", $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelInt64Value()
+    {
+        $m = new Int64Value();
+        $m->mergeFromJsonString("1");
+        $this->assertEquals(1, $m->getValue());
+    }
+
+    # public function testEncodeTopLevelInt64Value()
+    # {
+    #     $m = new Int64Value();
+    #     $m->setValue(1);
+    #     $this->assertSame("\"1\"", $m->serializeToJsonString());
+    # }
+
+    public function testDecodeTopLevelUInt64Value()
+    {
+        $m = new UInt64Value();
+        $m->mergeFromJsonString("1");
+        $this->assertEquals(1, $m->getValue());
+    }
+
+    # public function testEncodeTopLevelUInt64Value()
+    # {
+    #     $m = new UInt64Value();
+    #     $m->setValue(1);
+    #     $this->assertSame("\"1\"", $m->serializeToJsonString());
+    # }
+
+    public function testDecodeTopLevelStringValue()
+    {
+        $m = new StringValue();
+        $m->mergeFromJsonString("\"a\"");
+        $this->assertSame("a", $m->getValue());
+    }
+
+    public function testEncodeTopLevelStringValue()
+    {
+        $m = new StringValue();
+        $m->setValue("a");
+        $this->assertSame("\"a\"", $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelBytesValue()
+    {
+        $m = new BytesValue();
+        $m->mergeFromJsonString("\"YQ==\"");
+        $this->assertSame("a", $m->getValue());
+    }
+
+    public function testEncodeTopLevelBytesValue()
+    {
+        $m = new BytesValue();
+        $m->setValue("a");
+        $this->assertSame("\"YQ==\"", $m->serializeToJsonString());
+    }
+
+    public function generateRandomString($length = 10) {
+        $randomString = str_repeat("+", $length);
+        for ($i = 0; $i < $length; $i++) {
+            $randomString[$i] = rand(0, 255);
+        }
+        return $randomString;
+    }
+
+    public function testEncodeTopLevelLongBytesValue()
+    {
+        $m = new BytesValue();
+        $data = $this->generateRandomString(12007);
+        $m->setValue($data);
+        $expected = "\"" . base64_encode($data) . "\"";
+        $this->assertSame(strlen($expected), strlen($m->serializeToJsonString()));
+    }
+
+    public function testEncode()
+    {
+        $from = new TestMessage();
+        $this->expectEmptyFields($from);
+        $this->setFields($from);
+        $this->expectFields($from);
+
+        $data = $from->serializeToString();
+        $this->assertSame(bin2hex(TestUtil::getGoldenTestMessage()),
+                          bin2hex($data));
+    }
+
+    public function testDecode()
+    {
+        $to = new TestMessage();
+        $to->mergeFromString(TestUtil::getGoldenTestMessage());
+        $this->expectFields($to);
+    }
+
+    public function testEncodeDecode()
+    {
+        $from = new TestMessage();
+        $this->expectEmptyFields($from);
+        $this->setFields($from);
+        $this->expectFields($from);
+
+        $data = $from->serializeToString();
+
+        $to = new TestMessage();
+        $to->mergeFromString($data);
+        $this->expectFields($to);
+    }
+
+    public function testEncodeDecodeEmpty()
+    {
+        $from = new TestMessage();
+        $this->expectEmptyFields($from);
+
+        $data = $from->serializeToString();
+
+        $to = new TestMessage();
+        $to->mergeFromString($data);
+        $this->expectEmptyFields($to);
+    }
+
+    public function testEncodeDecodeOneof()
+    {
+        $m = new TestMessage();
+
+        $m->setOneofInt32(1);
+        $data = $m->serializeToString();
+        $n = new TestMessage();
+        $n->mergeFromString($data);
+        $this->assertSame(1, $n->getOneofInt32());
+
+        $m->setOneofFloat(2.0);
+        $data = $m->serializeToString();
+        $n = new TestMessage();
+        $n->mergeFromString($data);
+        $this->assertSame(2.0, $n->getOneofFloat());
+
+        $m->setOneofString('abc');
+        $data = $m->serializeToString();
+        $n = new TestMessage();
+        $n->mergeFromString($data);
+        $this->assertSame('abc', $n->getOneofString());
+
+        $sub_m = new Sub();
+        $sub_m->setA(1);
+        $m->setOneofMessage($sub_m);
+        $data = $m->serializeToString();
+        $n = new TestMessage();
+        $n->mergeFromString($data);
+        $this->assertSame(1, $n->getOneofMessage()->getA());
+
+        // Encode default value
+        $m->setOneofEnum(TestEnum::ZERO);
+        $data = $m->serializeToString();
+        $n = new TestMessage();
+        $n->mergeFromString($data);
+        $this->assertSame("oneof_enum", $n->getMyOneof());
+        $this->assertSame(TestEnum::ZERO, $n->getOneofEnum());
+
+        $m->setOneofString("");
+        $data = $m->serializeToString();
+        $n = new TestMessage();
+        $n->mergeFromString($data);
+        $this->assertSame("oneof_string", $n->getMyOneof());
+        $this->assertSame("", $n->getOneofString());
+
+        $sub_m = new Sub();
+        $m->setOneofMessage($sub_m);
+        $data = $m->serializeToString();
+        $n = new TestMessage();
+        $n->mergeFromString($data);
+        $this->assertSame("oneof_message", $n->getMyOneof());
+        $this->assertFalse(is_null($n->getOneofMessage()));
+
+    }
+
+    public function testJsonEncodeDecodeOneof()
+    {
+        $m = new TestMessage();
+
+        $m->setOneofEnum(TestEnum::ONE);
+        $data = $m->serializeToJsonString();
+        $n = new TestMessage();
+        $n->mergeFromJsonString($data);
+        $this->assertSame("oneof_enum", $n->getMyOneof());
+        $this->assertSame(TestEnum::ONE, $n->getOneofEnum());
+
+        $m->setOneofString("a");
+        $data = $m->serializeToJsonString();
+        $n = new TestMessage();
+        $n->mergeFromJsonString($data);
+        $this->assertSame("oneof_string", $n->getMyOneof());
+        $this->assertSame("a", $n->getOneofString());
+
+        $m->setOneofBytes("bbbb");
+        $data = $m->serializeToJsonString();
+        $n = new TestMessage();
+        $n->mergeFromJsonString($data);
+        $this->assertSame("oneof_bytes", $n->getMyOneof());
+        $this->assertSame("bbbb", $n->getOneofBytes());
+
+        $sub_m = new Sub();
+        $m->setOneofMessage($sub_m);
+        $data = $m->serializeToJsonString();
+        $n = new TestMessage();
+        $n->mergeFromJsonString($data);
+        $this->assertSame("oneof_message", $n->getMyOneof());
+        $this->assertFalse(is_null($n->getOneofMessage()));
+    }
+
+    public function testPackedEncode()
+    {
+        $from = new TestPackedMessage();
+        TestUtil::setTestPackedMessage($from);
+        $this->assertSame(TestUtil::getGoldenTestPackedMessage(),
+                          $from->serializeToString());
+    }
+
+    public function testPackedDecodePacked()
+    {
+        $to = new TestPackedMessage();
+        $to->mergeFromString(TestUtil::getGoldenTestPackedMessage());
+        TestUtil::assertTestPackedMessage($to);
+        $this->assertTrue(true);
+    }
+
+    public function testPackedDecodeUnpacked()
+    {
+        $to = new TestPackedMessage();
+        $to->mergeFromString(TestUtil::getGoldenTestUnpackedMessage());
+        TestUtil::assertTestPackedMessage($to);
+        $this->assertTrue(true);
+    }
+
+    public function testUnpackedEncode()
+    {
+        $from = new TestUnpackedMessage();
+        TestUtil::setTestPackedMessage($from);
+        $this->assertSame(TestUtil::getGoldenTestUnpackedMessage(),
+                          $from->serializeToString());
+    }
+
+    public function testUnpackedDecodePacked()
+    {
+        $to = new TestUnpackedMessage();
+        $to->mergeFromString(TestUtil::getGoldenTestPackedMessage());
+        TestUtil::assertTestPackedMessage($to);
+        $this->assertTrue(true);
+    }
+
+    public function testUnpackedDecodeUnpacked()
+    {
+        $to = new TestUnpackedMessage();
+        $to->mergeFromString(TestUtil::getGoldenTestUnpackedMessage());
+        TestUtil::assertTestPackedMessage($to);
+        $this->assertTrue(true);
+    }
+
+    public function testDecodeInt64()
+    {
+        // Read 64 testing
+        $testVals = array(
+            '10'                 => '100a',
+            '100'                => '1064',
+            '800'                => '10a006',
+            '6400'               => '108032',
+            '70400'              => '1080a604',
+            '774400'             => '1080a22f',
+            '9292800'            => '108098b704',
+            '74342400'           => '1080c0b923',
+            '743424000'          => '108080bfe202',
+            '8177664000'         => '108080b5bb1e',
+            '65421312000'        => '108080a8dbf301',
+            '785055744000'       => '108080e0c7ec16',
+            '9420668928000'      => '10808080dd969202',
+            '103627358208000'    => '10808080fff9c717',
+            '1139900940288000'   => '10808080f5bd978302',
+            '13678811283456000'  => '10808080fce699a618',
+            '109430490267648000' => '10808080e0b7ceb1c201',
+            '984874412408832000' => '10808080e0f5c1bed50d',
+        );
+
+        $msg = new TestMessage();
+        foreach ($testVals as $original => $encoded) {
+            $msg->setOptionalInt64($original);
+            $data = $msg->serializeToString();
+            $this->assertSame($encoded, bin2hex($data));
+            $msg->setOptionalInt64(0);
+            $msg->mergeFromString($data);
+            $this->assertEquals($original, $msg->getOptionalInt64());
+        }
+    }
+
+    public function testDecodeToExistingMessage()
+    {
+        $m1 = new TestMessage();
+        $this->setFields($m1);
+        $this->expectFields($m1);
+
+        $m2 = new TestMessage();
+        $this->setFields2($m2);
+        $data = $m2->serializeToString();
+
+        $m1->mergeFromString($data);
+        $this->expectFieldsMerged($m1);
+    }
+
+    public function testDecodeFieldNonExist()
+    {
+        $data = hex2bin('c80501');
+        $m = new TestMessage();
+        $m->mergeFromString($data);
+        $this->assertTrue(true);
+    }
+
+    public function testEncodeNegativeInt32()
+    {
+        $m = new TestMessage();
+        $m->setOptionalInt32(-1);
+        $data = $m->serializeToString();
+        $this->assertSame("08ffffffffffffffffff01", bin2hex($data));
+    }
+
+    public function testDecodeNegativeInt32()
+    {
+        $m = new TestMessage();
+        $this->assertEquals(0, $m->getOptionalInt32());
+        $m->mergeFromString(hex2bin("08ffffffffffffffffff01"));
+        $this->assertEquals(-1, $m->getOptionalInt32());
+
+        $m = new TestMessage();
+        $this->assertEquals(0, $m->getOptionalInt32());
+        $m->mergeFromString(hex2bin("08ffffffff0f"));
+        $this->assertEquals(-1, $m->getOptionalInt32());
+    }
+
+    public function testRandomFieldOrder()
+    {
+        $m = new TestRandomFieldOrder();
+        $data = $m->serializeToString();
+        $this->assertSame("", $data);
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidInt32()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('08'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidSubMessage()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('9A010108'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidInt64()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('10'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidUInt32()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('18'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidUInt64()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('20'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidSInt32()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('28'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidSInt64()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('30'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidFixed32()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('3D'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidFixed64()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('41'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidSFixed32()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('4D'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidSFixed64()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('51'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidFloat()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('5D'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidDouble()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('61'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidBool()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('68'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidStringLengthMiss()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('72'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidStringDataMiss()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('7201'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidBytesLengthMiss()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('7A'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidBytesDataMiss()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('7A01'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidEnum()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('8001'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidMessageLengthMiss()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('8A01'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidMessageDataMiss()
+    {
+        $m = new TestMessage();
+        $m->mergeFromString(hex2bin('8A0101'));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeInvalidPackedMessageLength()
+    {
+        $m = new TestPackedMessage();
+        $m->mergeFromString(hex2bin('D205'));
+    }
+
+    public function testUnknown()
+    {
+        // Test preserve unknown for varint.
+        $m = new TestMessage();
+        $from = hex2bin('F80601');  // TODO(teboring): Add a util to encode
+                                    // varint for better readability
+        $m->mergeFromString($from);
+        $to = $m->serializeToString();
+        $this->assertSame(bin2hex($from), bin2hex($to));
+
+        // Test preserve unknown for 64-bit.
+        $m = new TestMessage();
+        $from = hex2bin('F9060000000000000000');
+        $m->mergeFromString($from);
+        $to = $m->serializeToString();
+        $this->assertSame(bin2hex($from), bin2hex($to));
+
+        // Test preserve unknown for length delimited.
+        $m = new TestMessage();
+        $from = hex2bin('FA0600');
+        $m->mergeFromString($from);
+        $to = $m->serializeToString();
+        $this->assertSame(bin2hex($from), bin2hex($to));
+
+        // Test preserve unknown for 32-bit.
+        $m = new TestMessage();
+        $from = hex2bin('FD0600000000');
+        $m->mergeFromString($from);
+        $to = $m->serializeToString();
+        $this->assertSame(bin2hex($from), bin2hex($to));
+
+        // Test discard unknown in message.
+        $m = new TestMessage();
+        $from = hex2bin('F80601');
+        $m->mergeFromString($from);
+        $m->discardUnknownFields();
+        $to = $m->serializeToString();
+        $this->assertSame("", bin2hex($to));
+
+        // Test discard unknown for singular message field.
+        $m = new TestMessage();
+        $from = hex2bin('8A0103F80601');
+        $m->mergeFromString($from);
+        $m->discardUnknownFields();
+        $to = $m->serializeToString();
+        $this->assertSame("8a0100", bin2hex($to));
+
+        // Test discard unknown for repeated message field.
+        $m = new TestMessage();
+        $from = hex2bin('FA0203F80601');
+        $m->mergeFromString($from);
+        $m->discardUnknownFields();
+        $to = $m->serializeToString();
+        $this->assertSame("fa0200", bin2hex($to));
+
+        // Test discard unknown for map message value field.
+        $m = new TestMessage();
+        $from = hex2bin("BA050708011203F80601");
+        $m->mergeFromString($from);
+        $m->discardUnknownFields();
+        $to = $m->serializeToString();
+        $this->assertSame("ba050408011200", bin2hex($to));
+
+        // Test discard unknown for singular message field.
+        $m = new TestMessage();
+        $from = hex2bin('9A0403F80601');
+        $m->mergeFromString($from);
+        $m->discardUnknownFields();
+        $to = $m->serializeToString();
+        $this->assertSame("9a0400", bin2hex($to));
+    }
+
+    public function testJsonUnknown()
+    {
+        // Test unknown number
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":1,
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown bool
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":true,
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown string
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":\"abc\",
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown null
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":null,
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown array
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":[],
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown number array
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":[1],
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown bool array
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":[true],
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown string array
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":[\"a\"],
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown null array
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":[null],
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown array array
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":[[]],
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown object array
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":[{}],
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown double value array
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":[1, 2],
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown object
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":{},
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown number object
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":{\"a\":1},
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown bool object
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":{\"a\":true},
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown string object
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":{\"a\":\"a\"},
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown null object
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":{\"a\":null},
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown array object
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":{\"a\":[]},
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown object object
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":{\"a\":{}},
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+
+        // Test unknown double value object
+        $m = new TestMessage();
+        $m->mergeFromJsonString("{\"unknown\":{\"a\":1, \"b\":1},
+                                \"optionalInt32\":1}", true);
+        $this->assertSame(1, $m->getOptionalInt32());
+    }
+
+    public function testJsonEncode()
+    {
+        $from = new TestMessage();
+        $this->setFields($from);
+        $data = $from->serializeToJsonString();
+        $to = new TestMessage();
+        $to->mergeFromJsonString($data);
+        $this->expectFields($to);
+    }
+
+    public function testDecodeDuration()
+    {
+        $m = new Google\Protobuf\Duration();
+        $m->mergeFromJsonString("\"1234.5678s\"");
+        $this->assertEquals(1234, $m->getSeconds());
+        $this->assertEquals(567800000, $m->getNanos());
+    }
+
+    public function testEncodeDuration()
+    {
+        $m = new Google\Protobuf\Duration();
+        $m->setSeconds(1234);
+        $m->setNanos(999999999);
+        $this->assertEquals("\"1234.999999999s\"", $m->serializeToJsonString());
+    }
+
+    public function testDecodeTimestamp()
+    {
+        $m = new Google\Protobuf\Timestamp();
+        $m->mergeFromJsonString("\"2000-01-01T00:00:00.123456789Z\"");
+        $this->assertEquals(946684800, $m->getSeconds());
+        $this->assertEquals(123456789, $m->getNanos());
+    }
+
+    public function testEncodeTimestamp()
+    {
+        $m = new Google\Protobuf\Timestamp();
+        $m->setSeconds(946684800);
+        $m->setNanos(123456789);
+        $this->assertEquals("\"2000-01-01T00:00:00.123456789Z\"",
+                            $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelValue()
+    {
+        $m = new Value();
+        $m->mergeFromJsonString("\"a\"");
+        $this->assertSame("a", $m->getStringValue());
+
+        $m = new Value();
+        $m->mergeFromJsonString("1.5");
+        $this->assertSame(1.5, $m->getNumberValue());
+
+        $m = new Value();
+        $m->mergeFromJsonString("true");
+        $this->assertSame(true, $m->getBoolValue());
+
+        $m = new Value();
+        $m->mergeFromJsonString("null");
+        $this->assertSame("null_value", $m->getKind());
+
+        $m = new Value();
+        $m->mergeFromJsonString("[1]");
+        $this->assertSame("list_value", $m->getKind());
+
+        $m = new Value();
+        $m->mergeFromJsonString("{\"a\":1}");
+        $this->assertSame("struct_value", $m->getKind());
+    }
+
+    public function testEncodeTopLevelValue()
+    {
+        $m = new Value();
+        $m->setStringValue("a");
+        $this->assertSame("\"a\"", $m->serializeToJsonString());
+
+        $m = new Value();
+        $m->setNumberValue(1.5);
+        $this->assertSame("1.5", $m->serializeToJsonString());
+
+        $m = new Value();
+        $m->setBoolValue(true);
+        $this->assertSame("true", $m->serializeToJsonString());
+
+        $m = new Value();
+        $m->setNullValue(0);
+        $this->assertSame("null", $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelListValue()
+    {
+        $m = new ListValue();
+        $m->mergeFromJsonString("[1]");
+        $this->assertSame(1.0, $m->getValues()[0]->getNumberValue());
+    }
+
+    public function testEncodeTopLevelListValue()
+    {
+        $m = new ListValue();
+        $arr = $m->getValues();
+        $sub = new Value();
+        $sub->setNumberValue(1.5);
+        $arr[] = $sub;
+        $this->assertSame("[1.5]", $m->serializeToJsonString());
+    }
+
+    public function testEncodeEmptyListValue()
+    {
+        $m = new Struct();
+        $m->setFields(['test' => (new Value())->setListValue(new ListValue())]);
+        $this->assertSame('{"test":[]}', $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelStruct()
+    {
+        $m = new Struct();
+        $m->mergeFromJsonString("{\"a\":{\"b\":1}}");
+        $this->assertSame(1.0, $m->getFields()["a"]
+                                 ->getStructValue()
+                                 ->getFields()["b"]->getNumberValue());
+    }
+
+    public function testEncodeTopLevelStruct()
+    {
+        $m = new Struct();
+        $map = $m->getFields();
+        $sub = new Value();
+        $sub->setNumberValue(1.5);
+        $map["a"] = $sub;
+        $this->assertSame("{\"a\":1.5}", $m->serializeToJsonString());
+    }
+
+    public function testEncodeEmptyStruct()
+    {
+        $m = new Struct();
+        $m->setFields(['test' => (new Value())->setStructValue(new Struct())]);
+        $this->assertSame('{"test":{}}', $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelAny()
+    {
+        // Make sure packed message has been created at least once.
+        $packed = new TestMessage();
+
+        $m1 = new Any();
+        $m1->mergeFromJsonString(
+            "{\"optionalInt32\": 1, " .
+            "\"@type\":\"type.googleapis.com/foo.TestMessage\"}");
+        $this->assertSame("type.googleapis.com/foo.TestMessage",
+                          $m1->getTypeUrl());
+        $this->assertSame("0801", bin2hex($m1->getValue()));
+
+        $m2 = new Any();
+        $m2->mergeFromJsonString(
+            "{\"@type\":\"type.googleapis.com/foo.TestMessage\", " .
+            "\"optionalInt32\": 1}");
+        $this->assertSame("type.googleapis.com/foo.TestMessage",
+                          $m2->getTypeUrl());
+        $this->assertSame("0801", bin2hex($m2->getValue()));
+
+        $m3 = new Any();
+        $m3->mergeFromJsonString(
+            "{\"optionalInt32\": 1, " .
+            "\"@type\":\"type.googleapis.com/foo.TestMessage\", " .
+            "\"optionalInt64\": 2}");
+        $this->assertSame("type.googleapis.com/foo.TestMessage",
+                          $m3->getTypeUrl());
+        $this->assertSame("08011002", bin2hex($m3->getValue()));
+    }
+
+    public function testDecodeAny()
+    {
+        // Make sure packed message has been created at least once.
+        $packed = new TestMessage();
+
+        $m1 = new TestAny();
+        $m1->mergeFromJsonString(
+            "{\"any\": {\"optionalInt32\": 1, " .
+            "\"@type\":\"type.googleapis.com/foo.TestMessage\"}}");
+        $this->assertSame("type.googleapis.com/foo.TestMessage",
+                          $m1->getAny()->getTypeUrl());
+        $this->assertSame("0801", bin2hex($m1->getAny()->getValue()));
+
+        $m2 = new TestAny();
+        $m2->mergeFromJsonString(
+            "{\"any\":{\"@type\":\"type.googleapis.com/foo.TestMessage\", " .
+            "\"optionalInt32\": 1}}");
+        $this->assertSame("type.googleapis.com/foo.TestMessage",
+                          $m2->getAny()->getTypeUrl());
+        $this->assertSame("0801", bin2hex($m2->getAny()->getValue()));
+
+        $m3 = new TestAny();
+        $m3->mergeFromJsonString(
+            "{\"any\":{\"optionalInt32\": 1, " .
+            "\"@type\":\"type.googleapis.com/foo.TestMessage\", " .
+            "\"optionalInt64\": 2}}");
+        $this->assertSame("type.googleapis.com/foo.TestMessage",
+                          $m3->getAny()->getTypeUrl());
+        $this->assertSame("08011002", bin2hex($m3->getAny()->getValue()));
+    }
+
+    public function testDecodeAnyWithWellKnownPacked()
+    {
+        // Make sure packed message has been created at least once.
+        $packed = new Int32Value();
+
+        $m1 = new TestAny();
+        $m1->mergeFromJsonString(
+            "{\"any\":" .
+            "  {\"@type\":\"type.googleapis.com/google.protobuf.Int32Value\"," .
+            "   \"value\":1}}");
+        $this->assertSame("type.googleapis.com/google.protobuf.Int32Value",
+                          $m1->getAny()->getTypeUrl());
+        $this->assertSame("0801", bin2hex($m1->getAny()->getValue()));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testDecodeAnyWithUnknownPacked()
+    {
+        $m = new TestAny();
+        $m->mergeFromJsonString(
+            "{\"any\":" .
+            "  {\"@type\":\"type.googleapis.com/unknown\"," .
+            "   \"value\":1}}");
+    }
+
+    public function testEncodeTopLevelAny()
+    {
+        // Test a normal message.
+        $packed = new TestMessage();
+        $packed->setOptionalInt32(123);
+        $packed->setOptionalString("abc");
+
+        $m = new Any();
+        $m->pack($packed);
+        $expected1 =
+            "{\"@type\":\"type.googleapis.com/foo.TestMessage\"," .
+            "\"optional_int32\":123,\"optional_string\":\"abc\"}";
+        $expected2 =
+            "{\"@type\":\"type.googleapis.com/foo.TestMessage\"," .
+            "\"optionalInt32\":123,\"optionalString\":\"abc\"}";
+        $result = $m->serializeToJsonString();
+        $this->assertTrue($expected1 === $result || $expected2 === $result);
+
+        // Test a well known message.
+        $packed = new Int32Value();
+        $packed->setValue(123);
+
+        $m = new Any();
+        $m->pack($packed);
+        $this->assertSame(
+            "{\"@type\":\"type.googleapis.com/google.protobuf.Int32Value\"," .
+            "\"value\":123}",
+            $m->serializeToJsonString());
+
+        // Test an Any message.
+        $outer = new Any();
+        $outer->pack($m);
+        $this->assertSame(
+            "{\"@type\":\"type.googleapis.com/google.protobuf.Any\"," .
+            "\"value\":{\"@type\":\"type.googleapis.com/google.protobuf.Int32Value\"," .
+            "\"value\":123}}",
+            $outer->serializeToJsonString());
+
+        // Test a Timestamp message.
+        $packed = new Google\Protobuf\Timestamp();
+        $packed->setSeconds(946684800);
+        $packed->setNanos(123456789);
+        $m = new Any();
+        $m->pack($packed);
+        $this->assertSame(
+            "{\"@type\":\"type.googleapis.com/google.protobuf.Timestamp\"," .
+            "\"value\":\"2000-01-01T00:00:00.123456789Z\"}",
+            $m->serializeToJsonString());
+    }
+
+    public function testDecodeTopLevelFieldMask()
+    {
+        $m = new TestMessage();
+        $m->setMapStringString(['a'=>'abcdefg']);
+        $data1 = $m->serializeToJsonString();
+        $n = new TestMessage();
+        $n->mergeFromJsonString($data1);
+        $data2 = $n->serializeToJsonString();
+        $this->assertSame($data1, $data2);
+
+        $m = new FieldMask();
+        $m->mergeFromJsonString("\"foo.barBaz,qux\"");
+        $this->assertSame("foo.bar_baz", $m->getPaths()[0]);
+        $this->assertSame("qux", $m->getPaths()[1]);
+    }
+
+    public function testEncodeTopLevelFieldMask()
+    {
+        $m = new FieldMask();
+        $m->setPaths(["foo.bar_baz", "qux"]);
+        $this->assertSame("\"foo.barBaz,qux\"", $m->serializeToJsonString());
+    }
+
+    public function testDecodeEmptyFieldMask()
+    {
+        $m = new FieldMask();
+        $m->mergeFromJsonString("\"\"");
+        $this->assertEquals("", $m->serializeToString());
+    }
+
+    public function testJsonDecodeMapWithDefaultValueKey()
+    {
+        $m = new TestMessage();
+        $m->getMapInt32Int32()[0] = 0;
+        $this->assertSame("{\"mapInt32Int32\":{\"0\":0}}",
+                          $m->serializeToJsonString());
+
+        $m = new TestMessage();
+        $m->getMapStringString()[""] = "";
+        $this->assertSame("{\"mapStringString\":{\"\":\"\"}}",
+                          $m->serializeToJsonString());
+    }
+
+    public function testJsonDecodeNumericStringMapKey()
+    {
+        $m = new TestMessage();
+        $m->getMapStringString()["1"] = "1";
+        $data = $m->serializeToJsonString();
+        $this->assertSame("{\"mapStringString\":{\"1\":\"1\"}}", $data);
+        $n = new TestMessage();
+        $n->mergeFromJsonString($data);
+    }
+
+}
diff --git a/third_party/protobuf/php/tests/gdb_test.sh b/third_party/protobuf/php/tests/gdb_test.sh
new file mode 100755
index 0000000..da5f3f3
--- /dev/null
+++ b/third_party/protobuf/php/tests/gdb_test.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+VERSION=$1
+
+export PATH=/usr/local/php-$VERSION/bin:$PATH
+export C_INCLUDE_PATH=/usr/local/php-$VERSION/include/php/main:/usr/local/php-$VERSION/include/php:$C_INCLUDE_PATH
+export CPLUS_INCLUDE_PATH=/usr/local/php-$VERSION/include/php/main:/usr/local/php-$VERSION/include/php:$CPLUS_INCLUDE_PATH
+
+php -i | grep "Configuration"
+
+# gdb --args php -dextension=../ext/google/protobuf/modules/protobuf.so `which
+# phpunit` --bootstrap autoload.php tmp_test.php
+#
+# gdb --args php -dextension=../ext/google/protobuf/modules/protobuf.so `which phpunit` --bootstrap autoload.php generated_class_test.php
+gdb --args php -dextension=../ext/google/protobuf/modules/protobuf.so `which phpunit` --bootstrap autoload.php encode_decode_test.php
+#
+# gdb --args 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
diff --git a/third_party/protobuf/php/tests/generated_class_test.php b/third_party/protobuf/php/tests/generated_class_test.php
new file mode 100644
index 0000000..d808e3f
--- /dev/null
+++ b/third_party/protobuf/php/tests/generated_class_test.php
@@ -0,0 +1,1509 @@
+<?php
+
+require_once('generated/NoNamespaceEnum.php');
+require_once('generated/NoNamespaceMessage.php');
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\MapField;
+use Google\Protobuf\Internal\GPBType;
+use Bar\TestLegacyMessage;
+use Bar\TestLegacyMessage_NestedEnum;
+use Bar\TestLegacyMessage_NestedMessage;
+use Foo\TestEnum;
+use Foo\TestIncludeNamespaceMessage;
+use Foo\TestIncludePrefixMessage;
+use Foo\TestMessage;
+use Foo\TestMessage\Sub;
+use Foo\TestMessage_Sub;
+use Foo\TestMessage\NestedEnum;
+use Foo\TestReverseFieldOrder;
+use Foo\testLowerCaseMessage;
+use Foo\testLowerCaseEnum;
+use PBEmpty\PBEcho\TestEmptyPackage;
+use Php\Test\TestNamespace;
+
+class GeneratedClassTest extends TestBase
+{
+
+    #########################################################
+    # 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());
+    }
+
+    #########################################################
+    # 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());
+    }
+
+    #########################################################
+    # 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);
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('1', $m->getOptionalInt64());
+        } else {
+            $this->assertSame(1, $m->getOptionalInt64());
+        }
+
+        // Set string.
+        $m->setOptionalInt64('2');
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('2', $m->getOptionalInt64());
+        } else {
+            $this->assertSame(2, $m->getOptionalInt64());
+        }
+
+        $m->setOptionalInt64('3.1');
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('3', $m->getOptionalInt64());
+        } else {
+            $this->assertSame(3, $m->getOptionalInt64());
+        }
+
+        $m->setOptionalInt64(MAX_INT64_STRING);
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_INT64_STRING, $m->getOptionalInt64());
+        } else {
+            $this->assertSame(MAX_INT64, $m->getOptionalInt64());
+        }
+
+        $m->setOptionalInt64(MIN_INT64_STRING);
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MIN_INT64_STRING, $m->getOptionalInt64());
+        } else {
+            $this->assertSame(MIN_INT64, $m->getOptionalInt64());
+        }
+    }
+
+    #########################################################
+    # Test uint64 field.
+    #########################################################
+
+    public function testUint64Field()
+    {
+        $m = new TestMessage();
+
+        // Set integer.
+        $m->setOptionalUint64(MAX_UINT64);
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_UINT64_STRING, $m->getOptionalUint64());
+        } else {
+            $this->assertSame(MAX_UINT64, $m->getOptionalUint64());
+        }
+
+        // Set float.
+        $m->setOptionalUint64(1.1);
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('1', $m->getOptionalUint64());
+        } else {
+            $this->assertSame(1, $m->getOptionalUint64());
+        }
+
+        // Set string.
+        $m->setOptionalUint64('2');
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('2', $m->getOptionalUint64());
+        } else {
+            $this->assertSame(2, $m->getOptionalUint64());
+        }
+
+        $m->setOptionalUint64('3.1');
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('3', $m->getOptionalUint64());
+        } else {
+            $this->assertSame(3, $m->getOptionalUint64());
+        }
+
+        $m->setOptionalUint64(MAX_UINT64_STRING);
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_UINT64_STRING, $m->getOptionalUint64());
+        } else {
+            $this->assertSame(MAX_UINT64, $m->getOptionalUint64());
+        }
+    }
+
+    #########################################################
+    # 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 Enum methods
+        $this->assertEquals('ONE', TestEnum::name(1));
+        $this->assertEquals(1, TestEnum::value('ONE'));
+    }
+
+    /**
+     * @expectedException UnexpectedValueException
+     * @expectedExceptionMessage Enum Foo\TestEnum has no name defined for value -1
+     */
+    public function testInvalidEnumValueThrowsException()
+    {
+        TestEnum::name(-1);
+    }
+
+    /**
+     * @expectedException UnexpectedValueException
+     * @expectedExceptionMessage Enum Foo\TestEnum has no value defined for name DOES_NOT_EXIST
+     */
+    public function testInvalidEnumNameThrowsException()
+    {
+        TestEnum::value('DOES_NOT_EXIST');
+    }
+
+    public function testNestedEnum()
+    {
+        $m = new TestMessage();
+        $m->setOptionalNestedEnum(NestedEnum::ZERO);
+        $this->assertTrue(true);
+    }
+
+    public function testLegacyNestedEnum()
+    {
+        $m = new TestMessage();
+        $m->setOptionalNestedEnum(\Foo\TestMessage_NestedEnum::ZERO);
+        $this->assertTrue(true);
+    }
+
+    public function testLegacyTypehintWithNestedEnums()
+    {
+        $this->legacyEnum(new TestLegacyMessage\NestedEnum);
+    }
+
+    private function legacyEnum(TestLegacyMessage_NestedEnum $enum)
+    {
+        // If we made it here without a PHP Fatal error, the typehint worked
+        $this->assertTrue(true);
+    }
+
+    #########################################################
+    # 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);
+    }
+
+    #########################################################
+    # 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);
+    }
+
+    #########################################################
+    # 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());
+    }
+
+    #########################################################
+    # 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());
+    }
+
+    #########################################################
+    # 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);
+          $this->assertTrue(true);
+      }
+
+    #########################################################
+    # Test message field.
+    #########################################################
+
+    public function testMessageField()
+    {
+        $m = new TestMessage();
+
+        $sub_m = new Sub();
+        $sub_m->setA(1);
+        $m->setOptionalMessage($sub_m);
+        $this->assertSame(1, $m->getOptionalMessage()->getA());
+
+        $null = null;
+        $m->setOptionalMessage($null);
+        $this->assertNull($m->getOptionalMessage());
+    }
+
+    public function testLegacyMessageField()
+    {
+        $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());
+    }
+
+    public function testLegacyTypehintWithNestedMessages()
+    {
+        $this->legacyMessage(new TestLegacyMessage\NestedMessage);
+    }
+
+    private function legacyMessage(TestLegacyMessage_NestedMessage $sub)
+    {
+        // If we made it here without a PHP Fatal error, the typehint worked
+        $this->assertTrue(true);
+    }
+
+    #########################################################
+    # 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());
+    }
+
+    public function testRepeatedFieldViaArray()
+    {
+        $m = new TestMessage();
+
+        $arr = array();
+        $m->setRepeatedInt32($arr);
+        $this->assertSame(0, count($m->getRepeatedInt32()));
+
+        $arr = array(1, 2.1, "3");
+        $m->setRepeatedInt32($arr);
+        $this->assertTrue($m->getRepeatedInt32() instanceof RepeatedField);
+        $this->assertSame("Google\Protobuf\Internal\RepeatedField",
+                          get_class($m->getRepeatedInt32()));
+        $this->assertSame(3, count($m->getRepeatedInt32()));
+        $this->assertSame(1, $m->getRepeatedInt32()[0]);
+        $this->assertSame(2, $m->getRepeatedInt32()[1]);
+        $this->assertSame(3, $m->getRepeatedInt32()[2]);
+        $this->assertFalse($arr instanceof RepeatedField);
+    }
+
+    #########################################################
+    # Test map field.
+    #########################################################
+
+    public function testMapField()
+    {
+        $m = new TestMessage();
+
+        $map_int32_int32 = new MapField(GPBType::INT32, GPBType::INT32);
+        $m->setMapInt32Int32($map_int32_int32);
+        $this->assertSame($map_int32_int32, $m->getMapInt32Int32());
+    }
+
+    public function testMapFieldViaArray()
+    {
+        $m = new TestMessage();
+
+        $dict = array();
+        $m->setMapInt32Int32($dict);
+        $this->assertSame(0, count($m->getMapInt32Int32()));
+
+        $dict = array(5 => 5, 6.1 => 6.1, "7" => "7");
+        $m->setMapInt32Int32($dict);
+        $this->assertTrue($m->getMapInt32Int32() instanceof MapField);
+        $this->assertSame(3, count($m->getMapInt32Int32()));
+        $this->assertSame(5, $m->getMapInt32Int32()[5]);
+        $this->assertSame(6, $m->getMapInt32Int32()[6]);
+        $this->assertSame(7, $m->getMapInt32Int32()[7]);
+        $this->assertFalse($dict instanceof MapField);
+    }
+
+    #########################################################
+    # Test oneof field.
+    #########################################################
+
+    public function testOneofField() {
+        $m = new TestMessage();
+
+        $this->assertSame("", $m->getMyOneof());
+
+        $m->setOneofInt32(1);
+        $this->assertSame(1, $m->getOneofInt32());
+        $this->assertSame(0.0, $m->getOneofFloat());
+        $this->assertSame('', $m->getOneofString());
+        $this->assertSame(NULL, $m->getOneofMessage());
+        $this->assertSame("oneof_int32", $m->getMyOneof());
+
+        $m->setOneofFloat(2.0);
+        $this->assertSame(0, $m->getOneofInt32());
+        $this->assertSame(2.0, $m->getOneofFloat());
+        $this->assertSame('', $m->getOneofString());
+        $this->assertSame(NULL, $m->getOneofMessage());
+        $this->assertSame("oneof_float", $m->getMyOneof());
+
+        $m->setOneofString('abc');
+        $this->assertSame(0, $m->getOneofInt32());
+        $this->assertSame(0.0, $m->getOneofFloat());
+        $this->assertSame('abc', $m->getOneofString());
+        $this->assertSame(NULL, $m->getOneofMessage());
+        $this->assertSame("oneof_string", $m->getMyOneof());
+
+        $sub_m = new 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());
+        $this->assertSame("oneof_message", $m->getMyOneof());
+    }
+
+    #########################################################
+    # Test clear method.
+    #########################################################
+
+    public function testMessageClear()
+    {
+        $m = new TestMessage();
+        $this->setFields($m);
+        $this->expectFields($m);
+        $m->clear();
+        $this->expectEmptyFields($m);
+    }
+
+    #########################################################
+    # Test mergeFrom method.
+    #########################################################
+
+    public function testMessageMergeFrom()
+    {
+        $m = new TestMessage();
+        $this->setFields($m);
+        $this->expectFields($m);
+        $arr = $m->getOptionalMessage()->getB();
+        $arr[] = 1;
+
+        $n = new TestMessage();
+
+        // Singular
+        $n->setOptionalInt32(100);
+        $sub1 = new Sub();
+        $sub1->setA(101);
+
+        $b = $sub1->getB();
+        $b[] = 102;
+        $sub1->setB($b);
+
+        $n->setOptionalMessage($sub1);
+
+        // Repeated
+        $repeatedInt32 = $n->getRepeatedInt32();
+        $repeatedInt32[] = 200;
+        $n->setRepeatedInt32($repeatedInt32);
+
+        $repeatedString = $n->getRepeatedString();
+        $repeatedString[] = 'abc';
+        $n->setRepeatedString($repeatedString);
+
+        $sub2 = new Sub();
+        $sub2->setA(201);
+        $repeatedMessage = $n->getRepeatedMessage();
+        $repeatedMessage[] = $sub2;
+        $n->setRepeatedMessage($repeatedMessage);
+
+        // Map
+        $mapInt32Int32 = $n->getMapInt32Int32();
+        $mapInt32Int32[1] = 300;
+        $mapInt32Int32[-62] = 301;
+        $n->setMapInt32Int32($mapInt32Int32);
+
+        $mapStringString = $n->getMapStringString();
+        $mapStringString['def'] = 'def';
+        $n->setMapStringString($mapStringString);
+
+        $mapInt32Message = $n->getMapInt32Message();
+        $mapInt32Message[1] = new Sub();
+        $mapInt32Message[1]->setA(302);
+        $mapInt32Message[2] = new Sub();
+        $mapInt32Message[2]->setA(303);
+        $n->setMapInt32Message($mapInt32Message);
+
+        $m->mergeFrom($n);
+
+        $this->assertSame(100, $m->getOptionalInt32());
+        $this->assertSame(42, $m->getOptionalUint32());
+        $this->assertSame(101, $m->getOptionalMessage()->getA());
+        $this->assertSame(2, count($m->getOptionalMessage()->getB()));
+        $this->assertSame(1, $m->getOptionalMessage()->getB()[0]);
+        $this->assertSame(102, $m->getOptionalMessage()->getB()[1]);
+
+        $this->assertSame(3, count($m->getRepeatedInt32()));
+        $this->assertSame(200, $m->getRepeatedInt32()[2]);
+        $this->assertSame(2, count($m->getRepeatedUint32()));
+        $this->assertSame(3, count($m->getRepeatedString()));
+        $this->assertSame('abc', $m->getRepeatedString()[2]);
+        $this->assertSame(3, count($m->getRepeatedMessage()));
+        $this->assertSame(201, $m->getRepeatedMessage()[2]->getA());
+
+        $this->assertSame(2, count($m->getMapInt32Int32()));
+        $this->assertSame(300, $m->getMapInt32Int32()[1]);
+        $this->assertSame(301, $m->getMapInt32Int32()[-62]);
+        $this->assertSame(1, count($m->getMapUint32Uint32()));
+        $this->assertSame(2, count($m->getMapStringString()));
+        $this->assertSame('def', $m->getMapStringString()['def']);
+
+        $this->assertSame(2, count($m->getMapInt32Message()));
+        $this->assertSame(302, $m->getMapInt32Message()[1]->getA());
+        $this->assertSame(303, $m->getMapInt32Message()[2]->getA());
+
+        $this->assertSame("", $m->getMyOneof());
+
+        // Check sub-messages are copied by value.
+        $n->getOptionalMessage()->setA(-101);
+        $this->assertSame(101, $m->getOptionalMessage()->getA());
+
+        $repeatedMessage = $n->getRepeatedMessage();
+        $repeatedMessage[0]->setA(-201);
+        $n->setRepeatedMessage($repeatedMessage);
+        $this->assertSame(201, $m->getRepeatedMessage()[2]->getA());
+
+        $mapInt32Message = $n->getMapInt32Message();
+        $mapInt32Message[1]->setA(-302);
+        $n->setMapInt32Message($mapInt32Message);
+
+        $this->assertSame(302, $m->getMapInt32Message()[1]->getA());
+
+        // Test merge oneof.
+        $m = new TestMessage();
+
+        $n = new TestMessage();
+        $n->setOneofInt32(1);
+        $m->mergeFrom($n);
+        $this->assertSame(1, $m->getOneofInt32());
+
+        $sub = new Sub();
+        $n->setOneofMessage($sub);
+        $n->getOneofMessage()->setA(400);
+        $m->mergeFrom($n);
+        $this->assertSame(400, $m->getOneofMessage()->getA());
+        $n->getOneofMessage()->setA(-400);
+        $this->assertSame(400, $m->getOneofMessage()->getA());
+
+        // Test all fields
+        $m = new TestMessage();
+        $n = new TestMessage();
+        $this->setFields($m);
+        $n->mergeFrom($m);
+        $this->expectFields($n);
+    }
+
+    #########################################################
+    # Test message/enum without namespace.
+    #########################################################
+
+    public function testMessageWithoutNamespace()
+    {
+        $m = new TestMessage();
+        $n = new NoNameSpaceMessage();
+        $m->setOptionalNoNamespaceMessage($n);
+        $repeatedNoNamespaceMessage = $m->getRepeatedNoNamespaceMessage();
+        $repeatedNoNamespaceMessage[] = new NoNameSpaceMessage();
+        $m->setRepeatedNoNamespaceMessage($repeatedNoNamespaceMessage);
+
+        // test nested messages
+        $sub = new NoNamespaceMessage\NestedMessage();
+        $n->setNestedMessage($sub);
+
+        $this->assertTrue(true);
+    }
+
+    public function testEnumWithoutNamespace()
+    {
+        $m = new TestMessage();
+        $m->setOptionalNoNamespaceEnum(NoNameSpaceEnum::VALUE_A);
+        $repeatedNoNamespaceEnum = $m->getRepeatedNoNamespaceEnum();
+        $repeatedNoNamespaceEnum[] = NoNameSpaceEnum::VALUE_A;
+        $m->setRepeatedNoNamespaceEnum($repeatedNoNamespaceEnum);
+        $this->assertTrue(true);
+    }
+
+    #########################################################
+    # Test message with given namespace.
+    #########################################################
+
+    public function testNestedMessagesAndEnums()
+    {
+        $m = new TestMessage();
+        $n = new TestMessage\Sub();
+        $m->setOptionalMessage($n);
+        $m->setOptionalNestedEnum(TestMessage\NestedEnum::ZERO);
+        $this->assertSame($n, $m->getOptionalMessage());
+        $this->assertSame(TestMessage\NestedEnum::ZERO, $m->getOptionalNestedEnum());
+    }
+
+    public function testMessagesAndEnumsWithPrefix()
+    {
+        // Test message prefix
+        $m = new TestIncludePrefixMessage();
+        $n = new PrefixTestPrefix();
+        $n->setA(1);
+        $m->setPrefixMessage($n);
+        $this->assertSame(1, $m->getPrefixMessage()->getA());
+
+        // Test nested message prefix
+        $o = new PrefixTestPrefix();
+        $p = new PrefixTestPrefix\PrefixNestedMessage();
+        $o->setNestedMessage($p);
+        $o->setNestedEnum(PrefixTestPrefix\PrefixNestedEnum::ZERO);
+        $this->assertSame($p, $o->getNestedMessage());
+        $this->assertSame(PrefixTestPrefix\PrefixNestedEnum::ZERO, $o->getNestedEnum());
+    }
+
+    public function testMessagesAndEnumsWithPhpNamespace()
+    {
+        $m = new TestNamespace();
+        $n = new TestNamespace\NestedMessage();
+        $m->setNestedMessage($n);
+        $m->setNestedEnum(TestNamespace\NestedEnum::ZERO);
+        $this->assertSame($n, $m->getNestedMessage());
+        $this->assertSame(TestNamespace\NestedEnum::ZERO, $m->getNestedEnum());
+    }
+
+    public function testMesssagesAndEnumsWithEmptyPhpNamespace()
+    {
+        $m = new TestEmptyNamespace();
+        $n = new TestEmptyNamespace\NestedMessage();
+        $m->setNestedMessage($n);
+        $m->setNestedEnum(TestEmptyNamespace\NestedEnum::ZERO);
+        $this->assertSame($n, $m->getNestedMessage());
+        $this->assertSame(TestEmptyNamespace\NestedEnum::ZERO, $m->getNestedEnum());
+    }
+
+    public function testMessagesAndEnumsWithNoNamespace()
+    {
+        $m = new NoNamespaceMessage();
+        $n = new NoNamespaceMessage\NestedMessage();
+        $m->setNestedMessage($n);
+        $m->setNestedEnum(NoNamespaceMessage\NestedEnum::ZERO);
+        $this->assertSame($n, $m->getNestedMessage());
+        $this->assertSame(NoNamespaceMessage\NestedEnum::ZERO, $m->getNestedEnum());
+    }
+
+    public function testReservedWordsInPackageName()
+    {
+        $m = new TestEmptyPackage();
+        $n = new TestEmptyPackage\NestedMessage();
+        $m->setNestedMessage($n);
+        $m->setNestedEnum(TestEmptyPackage\NestedEnum::ZERO);
+        $this->assertSame($n, $m->getNestedMessage());
+        $this->assertSame(TestEmptyPackage\NestedEnum::ZERO, $m->getNestedEnum());
+    }
+
+    public function testReservedWordsInNamespace()
+    {
+        $m = new TestNamespace();
+        $n = new TestNamespace\PBEmpty();
+        $o = new TestNamespace\PBEmpty\NestedMessage();
+        $n->setNestedMessage($o);
+        $n->setNestedEnum(TestNamespace\PBEmpty\NestedEnum::ZERO);
+        $m->setReservedName($n);
+        $this->assertSame($n, $m->getReservedName());
+        $this->assertSame($o, $n->getNestedMessage());
+        $this->assertSame(
+            TestNamespace\PBEmpty\NestedEnum::ZERO,
+            $n->getNestedEnum()
+        );
+    }
+
+    #########################################################
+    # Test prefix for reserved words.
+    #########################################################
+
+    public function testPrefixForReservedWords()
+    {
+        $m = new \Foo\TestMessage\PBEmpty();
+        $m = new \Foo\PBEmpty();
+        $m = new \PrefixEmpty();
+        $m = new \Foo\PBARRAY();
+
+        $m = new \Lower\PBabstract();
+        $m = new \Lower\PBand();
+        $m = new \Lower\PBarray();
+        $m = new \Lower\PBas();
+        $m = new \Lower\PBbreak();
+        $m = new \Lower\PBcallable();
+        $m = new \Lower\PBcase();
+        $m = new \Lower\PBcatch();
+        $m = new \Lower\PBclass();
+        $m = new \Lower\PBclone();
+        $m = new \Lower\PBconst();
+        $m = new \Lower\PBcontinue();
+        $m = new \Lower\PBdeclare();
+        $m = new \Lower\PBdefault();
+        $m = new \Lower\PBdie();
+        $m = new \Lower\PBdo();
+        $m = new \Lower\PBecho();
+        $m = new \Lower\PBelse();
+        $m = new \Lower\PBelseif();
+        $m = new \Lower\PBempty();
+        $m = new \Lower\PBenddeclare();
+        $m = new \Lower\PBendfor();
+        $m = new \Lower\PBendforeach();
+        $m = new \Lower\PBendif();
+        $m = new \Lower\PBendswitch();
+        $m = new \Lower\PBendwhile();
+        $m = new \Lower\PBeval();
+        $m = new \Lower\PBexit();
+        $m = new \Lower\PBextends();
+        $m = new \Lower\PBfinal();
+        $m = new \Lower\PBfor();
+        $m = new \Lower\PBforeach();
+        $m = new \Lower\PBfunction();
+        $m = new \Lower\PBglobal();
+        $m = new \Lower\PBgoto();
+        $m = new \Lower\PBif();
+        $m = new \Lower\PBimplements();
+        $m = new \Lower\PBinclude();
+        $m = new \Lower\PBinclude_once();
+        $m = new \Lower\PBinstanceof();
+        $m = new \Lower\PBinsteadof();
+        $m = new \Lower\PBinterface();
+        $m = new \Lower\PBisset();
+        $m = new \Lower\PBlist();
+        $m = new \Lower\PBnamespace();
+        $m = new \Lower\PBnew();
+        $m = new \Lower\PBor();
+        $m = new \Lower\PBprint();
+        $m = new \Lower\PBprivate();
+        $m = new \Lower\PBprotected();
+        $m = new \Lower\PBpublic();
+        $m = new \Lower\PBrequire();
+        $m = new \Lower\PBrequire_once();
+        $m = new \Lower\PBreturn();
+        $m = new \Lower\PBstatic();
+        $m = new \Lower\PBswitch();
+        $m = new \Lower\PBthrow();
+        $m = new \Lower\PBtrait();
+        $m = new \Lower\PBtry();
+        $m = new \Lower\PBunset();
+        $m = new \Lower\PBuse();
+        $m = new \Lower\PBvar();
+        $m = new \Lower\PBwhile();
+        $m = new \Lower\PBxor();
+        $m = new \Lower\PBint();
+        $m = new \Lower\PBfloat();
+        $m = new \Lower\PBbool();
+        $m = new \Lower\PBstring();
+        $m = new \Lower\PBtrue();
+        $m = new \Lower\PBfalse();
+        $m = new \Lower\PBnull();
+        $m = new \Lower\PBvoid();
+        $m = new \Lower\PBiterable();
+
+        $m = new \Upper\PBABSTRACT();
+        $m = new \Upper\PBAND();
+        $m = new \Upper\PBARRAY();
+        $m = new \Upper\PBAS();
+        $m = new \Upper\PBBREAK();
+        $m = new \Upper\PBCALLABLE();
+        $m = new \Upper\PBCASE();
+        $m = new \Upper\PBCATCH();
+        $m = new \Upper\PBCLASS();
+        $m = new \Upper\PBCLONE();
+        $m = new \Upper\PBCONST();
+        $m = new \Upper\PBCONTINUE();
+        $m = new \Upper\PBDECLARE();
+        $m = new \Upper\PBDEFAULT();
+        $m = new \Upper\PBDIE();
+        $m = new \Upper\PBDO();
+        $m = new \Upper\PBECHO();
+        $m = new \Upper\PBELSE();
+        $m = new \Upper\PBELSEIF();
+        $m = new \Upper\PBEMPTY();
+        $m = new \Upper\PBENDDECLARE();
+        $m = new \Upper\PBENDFOR();
+        $m = new \Upper\PBENDFOREACH();
+        $m = new \Upper\PBENDIF();
+        $m = new \Upper\PBENDSWITCH();
+        $m = new \Upper\PBENDWHILE();
+        $m = new \Upper\PBEVAL();
+        $m = new \Upper\PBEXIT();
+        $m = new \Upper\PBEXTENDS();
+        $m = new \Upper\PBFINAL();
+        $m = new \Upper\PBFOR();
+        $m = new \Upper\PBFOREACH();
+        $m = new \Upper\PBFUNCTION();
+        $m = new \Upper\PBGLOBAL();
+        $m = new \Upper\PBGOTO();
+        $m = new \Upper\PBIF();
+        $m = new \Upper\PBIMPLEMENTS();
+        $m = new \Upper\PBINCLUDE();
+        $m = new \Upper\PBINCLUDE_ONCE();
+        $m = new \Upper\PBINSTANCEOF();
+        $m = new \Upper\PBINSTEADOF();
+        $m = new \Upper\PBINTERFACE();
+        $m = new \Upper\PBISSET();
+        $m = new \Upper\PBLIST();
+        $m = new \Upper\PBNAMESPACE();
+        $m = new \Upper\PBNEW();
+        $m = new \Upper\PBOR();
+        $m = new \Upper\PBPRINT();
+        $m = new \Upper\PBPRIVATE();
+        $m = new \Upper\PBPROTECTED();
+        $m = new \Upper\PBPUBLIC();
+        $m = new \Upper\PBREQUIRE();
+        $m = new \Upper\PBREQUIRE_ONCE();
+        $m = new \Upper\PBRETURN();
+        $m = new \Upper\PBSTATIC();
+        $m = new \Upper\PBSWITCH();
+        $m = new \Upper\PBTHROW();
+        $m = new \Upper\PBTRAIT();
+        $m = new \Upper\PBTRY();
+        $m = new \Upper\PBUNSET();
+        $m = new \Upper\PBUSE();
+        $m = new \Upper\PBVAR();
+        $m = new \Upper\PBWHILE();
+        $m = new \Upper\PBXOR();
+        $m = new \Upper\PBINT();
+        $m = new \Upper\PBFLOAT();
+        $m = new \Upper\PBBOOL();
+        $m = new \Upper\PBSTRING();
+        $m = new \Upper\PBTRUE();
+        $m = new \Upper\PBFALSE();
+        $m = new \Upper\PBNULL();
+        $m = new \Upper\PBVOID();
+        $m = new \Upper\PBITERABLE();
+
+        $m = new \Lower_enum\PBabstract();
+        $m = new \Lower_enum\PBand();
+        $m = new \Lower_enum\PBarray();
+        $m = new \Lower_enum\PBas();
+        $m = new \Lower_enum\PBbreak();
+        $m = new \Lower_enum\PBcallable();
+        $m = new \Lower_enum\PBcase();
+        $m = new \Lower_enum\PBcatch();
+        $m = new \Lower_enum\PBclass();
+        $m = new \Lower_enum\PBclone();
+        $m = new \Lower_enum\PBconst();
+        $m = new \Lower_enum\PBcontinue();
+        $m = new \Lower_enum\PBdeclare();
+        $m = new \Lower_enum\PBdefault();
+        $m = new \Lower_enum\PBdie();
+        $m = new \Lower_enum\PBdo();
+        $m = new \Lower_enum\PBecho();
+        $m = new \Lower_enum\PBelse();
+        $m = new \Lower_enum\PBelseif();
+        $m = new \Lower_enum\PBempty();
+        $m = new \Lower_enum\PBenddeclare();
+        $m = new \Lower_enum\PBendfor();
+        $m = new \Lower_enum\PBendforeach();
+        $m = new \Lower_enum\PBendif();
+        $m = new \Lower_enum\PBendswitch();
+        $m = new \Lower_enum\PBendwhile();
+        $m = new \Lower_enum\PBeval();
+        $m = new \Lower_enum\PBexit();
+        $m = new \Lower_enum\PBextends();
+        $m = new \Lower_enum\PBfinal();
+        $m = new \Lower_enum\PBfor();
+        $m = new \Lower_enum\PBforeach();
+        $m = new \Lower_enum\PBfunction();
+        $m = new \Lower_enum\PBglobal();
+        $m = new \Lower_enum\PBgoto();
+        $m = new \Lower_enum\PBif();
+        $m = new \Lower_enum\PBimplements();
+        $m = new \Lower_enum\PBinclude();
+        $m = new \Lower_enum\PBinclude_once();
+        $m = new \Lower_enum\PBinstanceof();
+        $m = new \Lower_enum\PBinsteadof();
+        $m = new \Lower_enum\PBinterface();
+        $m = new \Lower_enum\PBisset();
+        $m = new \Lower_enum\PBlist();
+        $m = new \Lower_enum\PBnamespace();
+        $m = new \Lower_enum\PBnew();
+        $m = new \Lower_enum\PBor();
+        $m = new \Lower_enum\PBprint();
+        $m = new \Lower_enum\PBprivate();
+        $m = new \Lower_enum\PBprotected();
+        $m = new \Lower_enum\PBpublic();
+        $m = new \Lower_enum\PBrequire();
+        $m = new \Lower_enum\PBrequire_once();
+        $m = new \Lower_enum\PBreturn();
+        $m = new \Lower_enum\PBstatic();
+        $m = new \Lower_enum\PBswitch();
+        $m = new \Lower_enum\PBthrow();
+        $m = new \Lower_enum\PBtrait();
+        $m = new \Lower_enum\PBtry();
+        $m = new \Lower_enum\PBunset();
+        $m = new \Lower_enum\PBuse();
+        $m = new \Lower_enum\PBvar();
+        $m = new \Lower_enum\PBwhile();
+        $m = new \Lower_enum\PBxor();
+        $m = new \Lower_enum\PBint();
+        $m = new \Lower_enum\PBfloat();
+        $m = new \Lower_enum\PBbool();
+        $m = new \Lower_enum\PBstring();
+        $m = new \Lower_enum\PBtrue();
+        $m = new \Lower_enum\PBfalse();
+        $m = new \Lower_enum\PBnull();
+        $m = new \Lower_enum\PBvoid();
+        $m = new \Lower_enum\PBiterable();
+
+        $m = new \Upper_enum\PBABSTRACT();
+        $m = new \Upper_enum\PBAND();
+        $m = new \Upper_enum\PBARRAY();
+        $m = new \Upper_enum\PBAS();
+        $m = new \Upper_enum\PBBREAK();
+        $m = new \Upper_enum\PBCALLABLE();
+        $m = new \Upper_enum\PBCASE();
+        $m = new \Upper_enum\PBCATCH();
+        $m = new \Upper_enum\PBCLASS();
+        $m = new \Upper_enum\PBCLONE();
+        $m = new \Upper_enum\PBCONST();
+        $m = new \Upper_enum\PBCONTINUE();
+        $m = new \Upper_enum\PBDECLARE();
+        $m = new \Upper_enum\PBDEFAULT();
+        $m = new \Upper_enum\PBDIE();
+        $m = new \Upper_enum\PBDO();
+        $m = new \Upper_enum\PBECHO();
+        $m = new \Upper_enum\PBELSE();
+        $m = new \Upper_enum\PBELSEIF();
+        $m = new \Upper_enum\PBEMPTY();
+        $m = new \Upper_enum\PBENDDECLARE();
+        $m = new \Upper_enum\PBENDFOR();
+        $m = new \Upper_enum\PBENDFOREACH();
+        $m = new \Upper_enum\PBENDIF();
+        $m = new \Upper_enum\PBENDSWITCH();
+        $m = new \Upper_enum\PBENDWHILE();
+        $m = new \Upper_enum\PBEVAL();
+        $m = new \Upper_enum\PBEXIT();
+        $m = new \Upper_enum\PBEXTENDS();
+        $m = new \Upper_enum\PBFINAL();
+        $m = new \Upper_enum\PBFOR();
+        $m = new \Upper_enum\PBFOREACH();
+        $m = new \Upper_enum\PBFUNCTION();
+        $m = new \Upper_enum\PBGLOBAL();
+        $m = new \Upper_enum\PBGOTO();
+        $m = new \Upper_enum\PBIF();
+        $m = new \Upper_enum\PBIMPLEMENTS();
+        $m = new \Upper_enum\PBINCLUDE();
+        $m = new \Upper_enum\PBINCLUDE_ONCE();
+        $m = new \Upper_enum\PBINSTANCEOF();
+        $m = new \Upper_enum\PBINSTEADOF();
+        $m = new \Upper_enum\PBINTERFACE();
+        $m = new \Upper_enum\PBISSET();
+        $m = new \Upper_enum\PBLIST();
+        $m = new \Upper_enum\PBNAMESPACE();
+        $m = new \Upper_enum\PBNEW();
+        $m = new \Upper_enum\PBOR();
+        $m = new \Upper_enum\PBPRINT();
+        $m = new \Upper_enum\PBPRIVATE();
+        $m = new \Upper_enum\PBPROTECTED();
+        $m = new \Upper_enum\PBPUBLIC();
+        $m = new \Upper_enum\PBREQUIRE();
+        $m = new \Upper_enum\PBREQUIRE_ONCE();
+        $m = new \Upper_enum\PBRETURN();
+        $m = new \Upper_enum\PBSTATIC();
+        $m = new \Upper_enum\PBSWITCH();
+        $m = new \Upper_enum\PBTHROW();
+        $m = new \Upper_enum\PBTRAIT();
+        $m = new \Upper_enum\PBTRY();
+        $m = new \Upper_enum\PBUNSET();
+        $m = new \Upper_enum\PBUSE();
+        $m = new \Upper_enum\PBVAR();
+        $m = new \Upper_enum\PBWHILE();
+        $m = new \Upper_enum\PBXOR();
+        $m = new \Upper_enum\PBINT();
+        $m = new \Upper_enum\PBFLOAT();
+        $m = new \Upper_enum\PBBOOL();
+        $m = new \Upper_enum\PBSTRING();
+        $m = new \Upper_enum\PBTRUE();
+        $m = new \Upper_enum\PBFALSE();
+        $m = new \Upper_enum\PBNULL();
+        $m = new \Upper_enum\PBVOID();
+        $m = new \Upper_enum\PBITERABLE();
+
+        $m = \Lower_enum_value\NotAllowed::PBabstract;
+        $m = \Lower_enum_value\NotAllowed::PBand;
+        $m = \Lower_enum_value\NotAllowed::PBarray;
+        $m = \Lower_enum_value\NotAllowed::PBas;
+        $m = \Lower_enum_value\NotAllowed::PBbreak;
+        $m = \Lower_enum_value\NotAllowed::PBcallable;
+        $m = \Lower_enum_value\NotAllowed::PBcase;
+        $m = \Lower_enum_value\NotAllowed::PBcatch;
+        $m = \Lower_enum_value\NotAllowed::PBclass;
+        $m = \Lower_enum_value\NotAllowed::PBclone;
+        $m = \Lower_enum_value\NotAllowed::PBconst;
+        $m = \Lower_enum_value\NotAllowed::PBcontinue;
+        $m = \Lower_enum_value\NotAllowed::PBdeclare;
+        $m = \Lower_enum_value\NotAllowed::PBdefault;
+        $m = \Lower_enum_value\NotAllowed::PBdie;
+        $m = \Lower_enum_value\NotAllowed::PBdo;
+        $m = \Lower_enum_value\NotAllowed::PBecho;
+        $m = \Lower_enum_value\NotAllowed::PBelse;
+        $m = \Lower_enum_value\NotAllowed::PBelseif;
+        $m = \Lower_enum_value\NotAllowed::PBempty;
+        $m = \Lower_enum_value\NotAllowed::PBenddeclare;
+        $m = \Lower_enum_value\NotAllowed::PBendfor;
+        $m = \Lower_enum_value\NotAllowed::PBendforeach;
+        $m = \Lower_enum_value\NotAllowed::PBendif;
+        $m = \Lower_enum_value\NotAllowed::PBendswitch;
+        $m = \Lower_enum_value\NotAllowed::PBendwhile;
+        $m = \Lower_enum_value\NotAllowed::PBeval;
+        $m = \Lower_enum_value\NotAllowed::PBexit;
+        $m = \Lower_enum_value\NotAllowed::PBextends;
+        $m = \Lower_enum_value\NotAllowed::PBfinal;
+        $m = \Lower_enum_value\NotAllowed::PBfor;
+        $m = \Lower_enum_value\NotAllowed::PBforeach;
+        $m = \Lower_enum_value\NotAllowed::PBfunction;
+        $m = \Lower_enum_value\NotAllowed::PBglobal;
+        $m = \Lower_enum_value\NotAllowed::PBgoto;
+        $m = \Lower_enum_value\NotAllowed::PBif;
+        $m = \Lower_enum_value\NotAllowed::PBimplements;
+        $m = \Lower_enum_value\NotAllowed::PBinclude;
+        $m = \Lower_enum_value\NotAllowed::PBinclude_once;
+        $m = \Lower_enum_value\NotAllowed::PBinstanceof;
+        $m = \Lower_enum_value\NotAllowed::PBinsteadof;
+        $m = \Lower_enum_value\NotAllowed::PBinterface;
+        $m = \Lower_enum_value\NotAllowed::PBisset;
+        $m = \Lower_enum_value\NotAllowed::PBlist;
+        $m = \Lower_enum_value\NotAllowed::PBnamespace;
+        $m = \Lower_enum_value\NotAllowed::PBnew;
+        $m = \Lower_enum_value\NotAllowed::PBor;
+        $m = \Lower_enum_value\NotAllowed::PBprint;
+        $m = \Lower_enum_value\NotAllowed::PBprivate;
+        $m = \Lower_enum_value\NotAllowed::PBprotected;
+        $m = \Lower_enum_value\NotAllowed::PBpublic;
+        $m = \Lower_enum_value\NotAllowed::PBrequire;
+        $m = \Lower_enum_value\NotAllowed::PBrequire_once;
+        $m = \Lower_enum_value\NotAllowed::PBreturn;
+        $m = \Lower_enum_value\NotAllowed::PBstatic;
+        $m = \Lower_enum_value\NotAllowed::PBswitch;
+        $m = \Lower_enum_value\NotAllowed::PBthrow;
+        $m = \Lower_enum_value\NotAllowed::PBtrait;
+        $m = \Lower_enum_value\NotAllowed::PBtry;
+        $m = \Lower_enum_value\NotAllowed::PBunset;
+        $m = \Lower_enum_value\NotAllowed::PBuse;
+        $m = \Lower_enum_value\NotAllowed::PBvar;
+        $m = \Lower_enum_value\NotAllowed::PBwhile;
+        $m = \Lower_enum_value\NotAllowed::PBxor;
+        $m = \Lower_enum_value\NotAllowed::int;
+        $m = \Lower_enum_value\NotAllowed::float;
+        $m = \Lower_enum_value\NotAllowed::bool;
+        $m = \Lower_enum_value\NotAllowed::string;
+        $m = \Lower_enum_value\NotAllowed::true;
+        $m = \Lower_enum_value\NotAllowed::false;
+        $m = \Lower_enum_value\NotAllowed::null;
+        $m = \Lower_enum_value\NotAllowed::void;
+        $m = \Lower_enum_value\NotAllowed::iterable;
+
+        $m = \Upper_enum_value\NotAllowed::PBABSTRACT;
+        $m = \Upper_enum_value\NotAllowed::PBAND;
+        $m = \Upper_enum_value\NotAllowed::PBARRAY;
+        $m = \Upper_enum_value\NotAllowed::PBAS;
+        $m = \Upper_enum_value\NotAllowed::PBBREAK;
+        $m = \Upper_enum_value\NotAllowed::PBCALLABLE;
+        $m = \Upper_enum_value\NotAllowed::PBCASE;
+        $m = \Upper_enum_value\NotAllowed::PBCATCH;
+        $m = \Upper_enum_value\NotAllowed::PBCLASS;
+        $m = \Upper_enum_value\NotAllowed::PBCLONE;
+        $m = \Upper_enum_value\NotAllowed::PBCONST;
+        $m = \Upper_enum_value\NotAllowed::PBCONTINUE;
+        $m = \Upper_enum_value\NotAllowed::PBDECLARE;
+        $m = \Upper_enum_value\NotAllowed::PBDEFAULT;
+        $m = \Upper_enum_value\NotAllowed::PBDIE;
+        $m = \Upper_enum_value\NotAllowed::PBDO;
+        $m = \Upper_enum_value\NotAllowed::PBECHO;
+        $m = \Upper_enum_value\NotAllowed::PBELSE;
+        $m = \Upper_enum_value\NotAllowed::PBELSEIF;
+        $m = \Upper_enum_value\NotAllowed::PBEMPTY;
+        $m = \Upper_enum_value\NotAllowed::PBENDDECLARE;
+        $m = \Upper_enum_value\NotAllowed::PBENDFOR;
+        $m = \Upper_enum_value\NotAllowed::PBENDFOREACH;
+        $m = \Upper_enum_value\NotAllowed::PBENDIF;
+        $m = \Upper_enum_value\NotAllowed::PBENDSWITCH;
+        $m = \Upper_enum_value\NotAllowed::PBENDWHILE;
+        $m = \Upper_enum_value\NotAllowed::PBEVAL;
+        $m = \Upper_enum_value\NotAllowed::PBEXIT;
+        $m = \Upper_enum_value\NotAllowed::PBEXTENDS;
+        $m = \Upper_enum_value\NotAllowed::PBFINAL;
+        $m = \Upper_enum_value\NotAllowed::PBFOR;
+        $m = \Upper_enum_value\NotAllowed::PBFOREACH;
+        $m = \Upper_enum_value\NotAllowed::PBFUNCTION;
+        $m = \Upper_enum_value\NotAllowed::PBGLOBAL;
+        $m = \Upper_enum_value\NotAllowed::PBGOTO;
+        $m = \Upper_enum_value\NotAllowed::PBIF;
+        $m = \Upper_enum_value\NotAllowed::PBIMPLEMENTS;
+        $m = \Upper_enum_value\NotAllowed::PBINCLUDE;
+        $m = \Upper_enum_value\NotAllowed::PBINCLUDE_ONCE;
+        $m = \Upper_enum_value\NotAllowed::PBINSTANCEOF;
+        $m = \Upper_enum_value\NotAllowed::PBINSTEADOF;
+        $m = \Upper_enum_value\NotAllowed::PBINTERFACE;
+        $m = \Upper_enum_value\NotAllowed::PBISSET;
+        $m = \Upper_enum_value\NotAllowed::PBLIST;
+        $m = \Upper_enum_value\NotAllowed::PBNAMESPACE;
+        $m = \Upper_enum_value\NotAllowed::PBNEW;
+        $m = \Upper_enum_value\NotAllowed::PBOR;
+        $m = \Upper_enum_value\NotAllowed::PBPRINT;
+        $m = \Upper_enum_value\NotAllowed::PBPRIVATE;
+        $m = \Upper_enum_value\NotAllowed::PBPROTECTED;
+        $m = \Upper_enum_value\NotAllowed::PBPUBLIC;
+        $m = \Upper_enum_value\NotAllowed::PBREQUIRE;
+        $m = \Upper_enum_value\NotAllowed::PBREQUIRE_ONCE;
+        $m = \Upper_enum_value\NotAllowed::PBRETURN;
+        $m = \Upper_enum_value\NotAllowed::PBSTATIC;
+        $m = \Upper_enum_value\NotAllowed::PBSWITCH;
+        $m = \Upper_enum_value\NotAllowed::PBTHROW;
+        $m = \Upper_enum_value\NotAllowed::PBTRAIT;
+        $m = \Upper_enum_value\NotAllowed::PBTRY;
+        $m = \Upper_enum_value\NotAllowed::PBUNSET;
+        $m = \Upper_enum_value\NotAllowed::PBUSE;
+        $m = \Upper_enum_value\NotAllowed::PBVAR;
+        $m = \Upper_enum_value\NotAllowed::PBWHILE;
+        $m = \Upper_enum_value\NotAllowed::PBXOR;
+        $m = \Upper_enum_value\NotAllowed::INT;
+        $m = \Upper_enum_value\NotAllowed::FLOAT;
+        $m = \Upper_enum_value\NotAllowed::BOOL;
+        $m = \Upper_enum_value\NotAllowed::STRING;
+        $m = \Upper_enum_value\NotAllowed::TRUE;
+        $m = \Upper_enum_value\NotAllowed::FALSE;
+        $m = \Upper_enum_value\NotAllowed::NULL;
+        $m = \Upper_enum_value\NotAllowed::VOID;
+        $m = \Upper_enum_value\NotAllowed::ITERABLE;
+
+        $this->assertTrue(true);
+    }
+
+    #########################################################
+    # Test fluent setters.
+    #########################################################
+
+    public function testFluentSetters()
+    {
+        $m = (new TestMessage())
+            ->setOptionalInt32(1)
+            ->setOptionalUInt32(2);
+        $this->assertSame(1, $m->getOptionalInt32());
+        $this->assertSame(2, $m->getOptionalUInt32());
+    }
+
+    #########################################################
+    # Test Reverse Field Order.
+    #########################################################
+
+    public function testReverseFieldOrder()
+    {
+        $m = new TestReverseFieldOrder();
+        $m->setB("abc");
+        $this->assertSame("abc", $m->getB());
+        $this->assertNotSame("abc", $m->getA());
+    }
+
+    #########################################################
+    # Test Reverse Field Order.
+    #########################################################
+
+    public function testLowerCase()
+    {
+        $m = new testLowerCaseMessage();
+        $n = testLowerCaseEnum::VALUE;
+        $this->assertTrue(true);
+    }
+
+    #########################################################
+    # Test Array Constructor.
+    #########################################################
+
+    public function testArrayConstructor()
+    {
+        $m = new TestMessage([
+            'optional_int32' => -42,
+            'optional_int64' => -43,
+            'optional_uint32' => 42,
+            'optional_uint64' => 43,
+            'optional_sint32' => -44,
+            'optional_sint64' => -45,
+            'optional_fixed32' => 46,
+            'optional_fixed64' => 47,
+            'optional_sfixed32' => -46,
+            'optional_sfixed64' => -47,
+            'optional_float' => 1.5,
+            'optional_double' => 1.6,
+            'optional_bool' => true,
+            'optional_string' => 'a',
+            'optional_bytes' => 'bbbb',
+            'optional_enum' => TestEnum::ONE,
+            'optional_message' => new Sub([
+                'a' => 33
+            ]),
+            'repeated_int32' => [-42, -52],
+            'repeated_int64' => [-43, -53],
+            'repeated_uint32' => [42, 52],
+            'repeated_uint64' => [43, 53],
+            'repeated_sint32' => [-44, -54],
+            'repeated_sint64' => [-45, -55],
+            'repeated_fixed32' => [46, 56],
+            'repeated_fixed64' => [47, 57],
+            'repeated_sfixed32' => [-46, -56],
+            'repeated_sfixed64' => [-47, -57],
+            'repeated_float' => [1.5, 2.5],
+            'repeated_double' => [1.6, 2.6],
+            'repeated_bool' => [true, false],
+            'repeated_string' => ['a', 'c'],
+            'repeated_bytes' => ['bbbb', 'dddd'],
+            'repeated_enum' => [TestEnum::ZERO, TestEnum::ONE],
+            'repeated_message' => [new Sub(['a' => 34]),
+                                   new Sub(['a' => 35])],
+            'map_int32_int32' => [-62 => -62],
+            'map_int64_int64' => [-63 => -63],
+            'map_uint32_uint32' => [62 => 62],
+            'map_uint64_uint64' => [63 => 63],
+            'map_sint32_sint32' => [-64 => -64],
+            'map_sint64_sint64' => [-65 => -65],
+            'map_fixed32_fixed32' => [66 => 66],
+            'map_fixed64_fixed64' => [67 => 67],
+            'map_sfixed32_sfixed32' => [-68 => -68],
+            'map_sfixed64_sfixed64' => [-69 => -69],
+            'map_int32_float' => [1 => 3.5],
+            'map_int32_double' => [1 => 3.6],
+            'map_bool_bool' => [true => true],
+            'map_string_string' => ['e' => 'e'],
+            'map_int32_bytes' => [1 => 'ffff'],
+            'map_int32_enum' => [1 => TestEnum::ONE],
+            'map_int32_message' => [1 => new Sub(['a' => 36])],
+        ]);
+
+        TestUtil::assertTestMessage($m);
+        $this->assertTrue(true);
+    }
+
+    public function testReferenceInArrayConstructor()
+    {
+        $keys = [[
+                    'optional_bool' => true,
+                    'repeated_bool' => [true],
+                    'map_bool_bool' => [true => true],
+                    'optional_double' => 1.0,
+                    'repeated_double' => [1.0],
+                    'map_int32_double' => [1 => 1.0],
+                    'optional_int32' => 1,
+                    'repeated_int32' => [1],
+                    'map_int32_int32' => [1 => 1],
+                    'optional_string' => 'a',
+                    'repeated_string' => ['a'],
+                    'map_string_string' => ['a' => 'a'],
+                    'optional_message' => ['a' => 1],
+                    'repeated_message' => [['a' => 1]],
+                    'map_int32_message' => [1 => ['a' => 1]],
+                ]];
+
+        foreach ($keys as &$key) {
+            foreach ($key as $id => &$value) {
+                if ($id === 'repeated_bool') {
+                    foreach ($value as &$element) {
+                    }
+                }
+                if ($id === 'map_bool_bool') {
+                    foreach ($value as $mapKey => &$element) {
+                    }
+                }
+                if ($id === 'repeated_double') {
+                    foreach ($value as &$element) {
+                    }
+                }
+                if ($id === 'map_int32_double') {
+                    foreach ($value as $mapKey => &$element) {
+                    }
+                }
+                if ($id === 'repeated_int32') {
+                    foreach ($value as &$element) {
+                    }
+                }
+                if ($id === 'map_int32_int32') {
+                    foreach ($value as $mapKey => &$element) {
+                    }
+                }
+                if ($id === 'repeated_string') {
+                    foreach ($value as &$element) {
+                    }
+                }
+                if ($id === 'map_string_string') {
+                    foreach ($value as $mapKey => &$element) {
+                    }
+                }
+                if ($id === 'optional_message') {
+                    $value = new Sub($value);
+                }
+                if ($id === 'repeated_message') {
+                    foreach ($value as &$element) {
+                        $element = new Sub($element);
+                    }
+                }
+                if ($id === 'map_int32_message') {
+                    foreach ($value as $mapKey => &$element) {
+                        $element = new Sub($element);
+                    }
+                }
+            }
+            $key = new TestMessage($key);
+        }
+    }
+
+    public function testOneofMessageInArrayConstructor()
+    {
+        $m = new TestMessage([
+            'oneof_message' => new Sub(),
+        ]);
+        $this->assertSame('oneof_message', $m->getMyOneof());
+        $this->assertNotNull($m->getOneofMessage());
+    }
+
+    public function testOneofStringInArrayConstructor()
+    {
+        $m = new TestMessage([
+            'oneof_string' => 'abc',
+        ]);
+    }
+
+    #########################################################
+    # Test message equals.
+    #########################################################
+
+    public function testMessageEquals()
+    {
+        $m = new TestMessage();
+        TestUtil::setTestMessage($m);
+        $n = new TestMessage();
+        TestUtil::setTestMessage($n);
+        $this->assertEquals($m, $n);
+    }
+
+    #########################################################
+    # Test reference of value
+    #########################################################
+
+    public function testValueIsReference()
+    {
+        // Bool element
+        $values = [true];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setOptionalBool($values[0]);
+
+        // Int32 element
+        $values = [1];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setOptionalInt32($values[0]);
+
+        // Double element
+        $values = [1.0];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setOptionalDouble($values[0]);
+
+        // String element
+        $values = ['a'];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setOptionalString($values[0]);
+    }
+}
diff --git a/third_party/protobuf/php/tests/generated_phpdoc_test.php b/third_party/protobuf/php/tests/generated_phpdoc_test.php
new file mode 100644
index 0000000..526927f
--- /dev/null
+++ b/third_party/protobuf/php/tests/generated_phpdoc_test.php
@@ -0,0 +1,345 @@
+<?php
+
+require_once('generated/NoNamespaceEnum.php');
+require_once('generated/NoNamespaceMessage.php');
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Foo\TestMessage;
+
+class GeneratedPhpdocTest extends TestBase
+{
+    public function testPhpDocForClass()
+    {
+        $class = new ReflectionClass('Foo\TestMessage');
+        $doc = $class->getDocComment();
+        $this->assertContains('foo.TestMessage', $doc);
+    }
+
+    public function testPhpDocForConstructor()
+    {
+        $class = new ReflectionClass('Foo\TestMessage');
+        $doc = $class->getMethod('__construct')->getDocComment();
+        $this->assertContains('@param array $data', $doc);
+        $this->assertContains('@type int $optional_int32', $doc);
+    }
+
+    /**
+     * @dataProvider providePhpDocForGettersAndSetters
+     */
+    public function testPhpDocForIntGetters($methods, $expectedDoc)
+    {
+        $class = new ReflectionClass('Foo\TestMessage');
+        foreach ($methods as $method) {
+            $doc = $class->getMethod($method)->getDocComment();
+            $this->assertContains($expectedDoc, $doc);
+        }
+    }
+
+    public function providePhpDocForGettersAndSetters()
+    {
+        return [
+            [
+                [
+                    'setOptionalInt32',
+                    'setOptionalUint32',
+                    'setOptionalSint32',
+                    'setOptionalFixed32',
+                    'setOptionalSfixed32',
+                    'setOneofInt32',
+                    'setOneofUint32',
+                    'setOneofSint32',
+                    'setOneofFixed32',
+                    'setOneofSfixed32',
+                    'setOptionalEnum',
+                    'setOptionalNoNamespaceEnum',
+                    'setOptionalNestedEnum',
+                    'setOneofEnum'
+                ],
+                '@param int $var'
+            ],
+            [
+                [
+                    'setOptionalInt64',
+                    'setOptionalUint64',
+                    'setOptionalSint64',
+                    'setOptionalFixed64',
+                    'setOptionalSfixed64',
+                    'setOneofInt64',
+                    'setOneofUint64',
+                    'setOneofSint64',
+                    'setOneofFixed64',
+                    'setOneofSfixed64',
+                ],
+                '@param int|string $var'
+            ],
+            [
+                [
+                    'getOptionalInt32',
+                    'getOptionalUint32',
+                    'getOptionalSint32',
+                    'getOptionalFixed32',
+                    'getOptionalSfixed32',
+                    'getOneofInt32',
+                    'getOneofUint32',
+                    'getOneofSint32',
+                    'getOneofFixed32',
+                    'getOneofSfixed32',
+                    'getOptionalEnum',
+                    'getOptionalNoNamespaceEnum',
+                    'getOptionalNestedEnum',
+                    'getOneofEnum',
+                ],
+                '@return int'
+            ],
+            [
+                [
+                    'setOptionalInt64',
+                    'setOptionalUint64',
+                    'setOptionalSint64',
+                    'setOptionalFixed64',
+                    'setOptionalSfixed64',
+                    'setOneofInt64',
+                    'setOneofUint64',
+                    'setOneofSint64',
+                    'setOneofFixed64',
+                    'setOneofSfixed64',
+                ],
+                '@param int|string $var'
+            ],
+            [
+                [
+                    'getRepeatedInt32',
+                    'getRepeatedInt64',
+                    'getRepeatedUint32',
+                    'getRepeatedUint64',
+                    'getRepeatedSint32',
+                    'getRepeatedSint64',
+                    'getRepeatedFixed32',
+                    'getRepeatedFixed64',
+                    'getRepeatedSfixed32',
+                    'getRepeatedSfixed64',
+                    'getRepeatedFloat',
+                    'getRepeatedDouble',
+                    'getRepeatedBool',
+                    'getRepeatedString',
+                    'getRepeatedBytes',
+                    'getRepeatedEnum',
+                    'getRepeatedMessage',
+                    'getRepeatedRecursive',
+                    'getRepeatedNoNamespaceMessage',
+                    'getRepeatedNoNamespaceEnum',
+                ],
+                '@return \Google\Protobuf\Internal\RepeatedField'
+            ],
+            [
+                [
+                    'getMapInt32Int32',
+                    'getMapInt64Int64',
+                    'getMapUint32Uint32',
+                    'getMapUint64Uint64',
+                    'getMapSint32Sint32',
+                    'getMapSint64Sint64',
+                    'getMapFixed32Fixed32',
+                    'getMapFixed64Fixed64',
+                    'getMapSfixed32Sfixed32',
+                    'getMapSfixed64Sfixed64',
+                    'getMapInt32Float',
+                    'getMapInt32Double',
+                    'getMapBoolBool',
+                    'getMapStringString',
+                    'getMapInt32Bytes',
+                    'getMapInt32Enum',
+                    'getMapInt32Message',
+                    'getMapRecursive',
+                ],
+                '@return \Google\Protobuf\Internal\MapField'
+            ],
+            [
+                [
+                    'setRepeatedInt32',
+                    'setRepeatedUint32',
+                    'setRepeatedSint32',
+                    'setRepeatedFixed32',
+                    'setRepeatedSfixed32',
+                    'setRepeatedEnum',
+                    'setRepeatedNoNamespaceEnum',
+                ],
+                '@param int[]|\Google\Protobuf\Internal\RepeatedField $var'
+            ],
+            [
+                [
+                    'setRepeatedInt64',
+                    'setRepeatedUint64',
+                    'setRepeatedSint64',
+                    'setRepeatedFixed64',
+                    'setRepeatedSfixed64',
+                ],
+                '@param int[]|string[]|\Google\Protobuf\Internal\RepeatedField $var'
+            ],
+            [
+                [
+                    'setRepeatedFloat',
+                    'setRepeatedDouble',
+                ],
+                '@param float[]|\Google\Protobuf\Internal\RepeatedField $var'
+            ],
+            [
+                [
+                    'setRepeatedBool',
+                ],
+                '@param bool[]|\Google\Protobuf\Internal\RepeatedField $var'
+            ],
+            [
+                [
+                    'setRepeatedString',
+                    'setRepeatedBytes',
+                ],
+                '@param string[]|\Google\Protobuf\Internal\RepeatedField $var'
+            ],
+            [
+                [
+                    'setRepeatedMessage',
+                ],
+                '@param \Foo\TestMessage\Sub[]|\Google\Protobuf\Internal\RepeatedField $var'
+            ],
+            [
+                [
+                    'setRepeatedRecursive',
+                ],
+                '@param \Foo\TestMessage[]|\Google\Protobuf\Internal\RepeatedField $var'
+            ],
+            [
+                [
+                    'setRepeatedNoNamespaceMessage',
+                ],
+                '@param \NoNamespaceMessage[]|\Google\Protobuf\Internal\RepeatedField $var'
+            ],
+            [
+                [
+                    'setMapInt32Int32',
+                    'setMapInt64Int64',
+                    'setMapUint32Uint32',
+                    'setMapUint64Uint64',
+                    'setMapSint32Sint32',
+                    'setMapSint64Sint64',
+                    'setMapFixed32Fixed32',
+                    'setMapFixed64Fixed64',
+                    'setMapSfixed32Sfixed32',
+                    'setMapSfixed64Sfixed64',
+                    'setMapInt32Float',
+                    'setMapInt32Double',
+                    'setMapBoolBool',
+                    'setMapStringString',
+                    'setMapInt32Bytes',
+                    'setMapInt32Enum',
+                    'setMapInt32Message',
+                    'setMapRecursive',
+                ],
+                '@param array|\Google\Protobuf\Internal\MapField $var'
+            ],
+            [
+                [
+                    'getOptionalFloat',
+                    'getOptionalDouble',
+                    'getOneofDouble',
+                    'getOneofFloat',
+                ],
+                '@return float'
+            ],
+            [
+                [
+                    'setOptionalFloat',
+                    'setOptionalDouble',
+                    'setOneofDouble',
+                    'setOneofFloat',
+                ],
+                '@param float $var'
+            ],
+            [
+                [
+                    'getOptionalBool',
+                    'getOneofBool',
+                ],
+                '@return bool'],
+            [
+                [
+                    'setOptionalBool',
+                    'setOneofBool',
+                ],
+                '@param bool $var'
+            ],
+            [
+                [
+                    'getOptionalString',
+                    'getOptionalBytes',
+                    'getOneofString',
+                    'getOneofBytes',
+                    'getMyOneof',
+                ],
+                '@return string'
+            ],
+            [
+                [
+                    'setOptionalString',
+                    'setOptionalBytes',
+                    'setOneofString',
+                    'setOneofBytes',
+                ],
+                '@param string $var'
+            ],
+
+            [
+                [
+                    'getOptionalMessage',
+                    'getOneofMessage'
+                ],
+                '@return \Foo\TestMessage\Sub'
+            ],
+            [
+                [
+                    'setOptionalMessage',
+                    'setOneofMessage'
+                ],
+                '@param \Foo\TestMessage\Sub $var'
+            ],
+            [
+                [
+                    'getOptionalIncludedMessage'
+                ],
+                '@return \Bar\TestInclude'
+            ],
+            [
+                [
+                    'setOptionalIncludedMessage'
+                ],
+                '@param \Bar\TestInclude $var'
+            ],
+            [
+                [
+                    'getRecursive'
+                ],
+                '@return \Foo\TestMessage'
+            ],
+            [
+                [
+                    'setRecursive'
+                ],
+                '@param \Foo\TestMessage $var'
+            ],
+
+            [
+                [
+                    'getOptionalNoNamespaceMessage'
+                ],
+                '@return \NoNamespaceMessage'
+            ],
+            [
+                [
+                    'setOptionalNoNamespaceMessage'
+                ],
+                '@param \NoNamespaceMessage $var'
+            ],
+        ];
+    }
+}
diff --git a/third_party/protobuf/php/tests/generated_service_test.php b/third_party/protobuf/php/tests/generated_service_test.php
new file mode 100644
index 0000000..5407db9
--- /dev/null
+++ b/third_party/protobuf/php/tests/generated_service_test.php
@@ -0,0 +1,110 @@
+<?php
+
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\MapField;
+use Google\Protobuf\Internal\GPBType;
+use Foo\Greeter;
+use Foo\HelloRequest;
+use Foo\HelloReply;
+
+class GeneratedServiceTest extends TestBase
+{
+    /**
+     * @var \ReflectionClass
+     */
+    private $serviceClass;
+
+    /**
+     * @var \ReflectionClass
+     */
+    private $namespacedServiceClass;
+
+    /**
+     * @var array
+     */
+    private $methodNames = [
+        'sayHello',
+        'sayHelloAgain'
+    ];
+
+    public function setUp()
+    {
+        parent::setUp();
+
+        $this->serviceClass = new ReflectionClass('Foo\GreeterInterface');
+
+        $this->namespacedServiceClass = new ReflectionClass('Bar\OtherGreeterInterface');
+    }
+
+    public function testIsInterface()
+    {
+        $this->assertTrue($this->serviceClass->isInterface());
+    }
+
+    public function testPhpDocForClass()
+    {
+        $this->assertContains('foo.Greeter', $this->serviceClass->getDocComment());
+    }
+
+    public function testPhpDocForNamespacedClass()
+    {
+        $this->assertContains('foo.OtherGreeter', $this->namespacedServiceClass->getDocComment());
+    }
+
+    public function testServiceMethodsAreGenerated()
+    {
+        $this->assertCount(count($this->methodNames), $this->serviceClass->getMethods());
+        foreach ($this->methodNames as $methodName) {
+            $this->assertTrue($this->serviceClass->hasMethod($methodName));
+        }
+    }
+
+    public function testPhpDocForServiceMethod()
+    {
+        foreach ($this->methodNames as $methodName) {
+            $docComment = $this->serviceClass->getMethod($methodName)->getDocComment();
+            $this->assertContains($methodName, $docComment);
+            $this->assertContains('@param \Foo\HelloRequest $request', $docComment);
+            $this->assertContains('@return \Foo\HelloReply', $docComment);
+        }
+    }
+
+    public function testPhpDocForServiceMethodInNamespacedClass()
+    {
+        foreach ($this->methodNames as $methodName) {
+            $docComment = $this->namespacedServiceClass->getMethod($methodName)->getDocComment();
+            $this->assertContains($methodName, $docComment);
+            $this->assertContains('@param \Foo\HelloRequest $request', $docComment);
+            $this->assertContains('@return \Foo\HelloReply', $docComment);
+        }
+    }
+
+    public function testParamForServiceMethod()
+    {
+        foreach ($this->methodNames as $methodName) {
+            $method = $this->serviceClass->getMethod($methodName);
+            $this->assertCount(1, $method->getParameters());
+            $param = $method->getParameters()[0];
+            $this->assertFalse($param->isOptional());
+            $this->assertSame('request', $param->getName());
+            // ReflectionParameter::getType only exists in PHP 7+, so get the type from __toString
+            $this->assertContains('Foo\HelloRequest $request', (string) $param);
+        }
+    }
+
+    public function testParamForServiceMethodInNamespacedClass()
+    {
+        foreach ($this->methodNames as $methodName) {
+            $method = $this->serviceClass->getMethod($methodName);
+            $this->assertCount(1, $method->getParameters());
+            $param = $method->getParameters()[0];
+            $this->assertFalse($param->isOptional());
+            $this->assertSame('request', $param->getName());
+            // ReflectionParameter::getType only exists in PHP 7+, so get the type from __toString
+            $this->assertContains('Foo\HelloRequest $request', (string) $param);
+        }
+    }
+}
diff --git a/third_party/protobuf/php/tests/map_field_test.php b/third_party/protobuf/php/tests/map_field_test.php
new file mode 100644
index 0000000..577be68
--- /dev/null
+++ b/third_party/protobuf/php/tests/map_field_test.php
@@ -0,0 +1,505 @@
+<?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));
+
+        // Test foreach.
+        $arr = new MapField(GPBType::INT32, GPBType::INT32);
+        for ($i = 0; $i < 3; $i++) {
+          $arr[$i] = $i;
+        }
+        $i = 0;
+        $arr_test = [];
+        foreach ($arr as $key => $val) {
+          $this->assertSame($key, $val);
+          $arr_test[] = $key;
+          $i++;
+        }
+        $this->assertTrue(isset($arr_test[0]));
+        $this->assertTrue(isset($arr_test[1]));
+        $this->assertTrue(isset($arr_test[2]));
+        $this->assertSame(3, $i);
+    }
+
+    #########################################################
+    # 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));
+    }
+
+    #########################################################
+    # Test int64 field.
+    #########################################################
+
+    public function testInt64() {
+        $arr = new MapField(GPBType::INT64, GPBType::INT64);
+
+        // Test integer argument.
+        $arr[MAX_INT64] = MAX_INT64;
+        $arr[MIN_INT64] = MIN_INT64;
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_INT64_STRING, $arr[MAX_INT64_STRING]);
+            $this->assertSame(MIN_INT64_STRING, $arr[MIN_INT64_STRING]);
+        } else {
+            $this->assertSame(MAX_INT64, $arr[MAX_INT64]);
+            $this->assertSame(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;
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('1', $arr['1']);
+        } else {
+            $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';
+        $arr['3.1'] = '3.1';
+        $arr[MAX_INT64_STRING] = MAX_INT64_STRING;
+        $arr[MIN_INT64_STRING] = MIN_INT64_STRING;
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('2', $arr['2']);
+            $this->assertSame('3', $arr['3']);
+            $this->assertSame(MAX_INT64_STRING, $arr[MAX_INT64_STRING]);
+            $this->assertSame(MIN_INT64_STRING, $arr[MIN_INT64_STRING]);
+        } else {
+            $this->assertSame(2, $arr[2]);
+            $this->assertSame(3, $arr[3]);
+            $this->assertSame(MAX_INT64, $arr[MAX_INT64]);
+            $this->assertSame(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));
+    }
+
+    #########################################################
+    # Test uint64 field.
+    #########################################################
+
+    public function testUint64() {
+        $arr = new MapField(GPBType::UINT64, GPBType::UINT64);
+
+        // Test integer argument.
+        $arr[MAX_UINT64] = MAX_UINT64;
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(MAX_UINT64_STRING, $arr[MAX_UINT64_STRING]);
+        } else {
+            $this->assertSame(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;
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('1', $arr['1']);
+        } else {
+            $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';
+        $arr['3.1'] = '3.1';
+        $arr[MAX_UINT64_STRING] = MAX_UINT64_STRING;
+
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('2', $arr['2']);
+            $this->assertSame('3', $arr['3']);
+            $this->assertSame(MAX_UINT64_STRING, $arr[MAX_UINT64_STRING]);
+        } else {
+            $this->assertSame(2, $arr[2]);
+            $this->assertSame(3, $arr[3]);
+            $this->assertSame(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));
+    }
+
+    #########################################################
+    # 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));
+    }
+
+    #########################################################
+    # 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));
+    }
+
+    #########################################################
+    # 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));
+    }
+
+    #########################################################
+    # 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));
+
+        // Test foreach.
+        $arr = new MapField(GPBType::STRING, GPBType::STRING);
+        for ($i = 0; $i < 3; $i++) {
+          $arr[$i] = $i;
+        }
+        $i = 0;
+        $arr_test = [];
+        foreach ($arr as $key => $val) {
+          $this->assertSame($key, $val);
+          $arr_test[] = $key;
+          $i++;
+        }
+        $this->assertTrue(isset($arr_test['0']));
+        $this->assertTrue(isset($arr_test['1']));
+        $this->assertTrue(isset($arr_test['2']));
+        $this->assertSame(3, $i);
+    }
+
+    #########################################################
+    # Test message field.
+    #########################################################
+
+    public function testMessage() {
+        $arr = new MapField(GPBType::INT32,
+            GPBType::MESSAGE, Sub::class);
+
+        // Test append.
+        $sub_m = new Sub();
+        $sub_m->setA(1);
+        $arr[0] = $sub_m;
+        $this->assertSame(1, $arr[0]->getA());
+
+        $this->assertEquals(1, count($arr));
+
+        // Test foreach.
+        $arr = new MapField(GPBType::INT32,
+            GPBType::MESSAGE, Sub::class);
+        for ($i = 0; $i < 3; $i++) {
+          $arr[$i] = new Sub();;
+          $arr[$i]->setA($i);
+        }
+        $i = 0;
+        $key_test = [];
+        $value_test = [];
+        foreach ($arr as $key => $val) {
+          $key_test[] = $key;
+          $value_test[] = $val->getA();
+          $i++;
+        }
+        $this->assertTrue(isset($key_test['0']));
+        $this->assertTrue(isset($key_test['1']));
+        $this->assertTrue(isset($key_test['2']));
+        $this->assertTrue(isset($value_test['0']));
+        $this->assertTrue(isset($value_test['1']));
+        $this->assertTrue(isset($value_test['2']));
+        $this->assertSame(3, $i);
+    }
+
+    #########################################################
+    # Test reference in map
+    #########################################################
+
+    public function testMapElementIsReference()
+    {
+        // Bool elements
+        $values = [true => true];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setMapBoolBool($values);
+
+        // Int32 elements
+        $values = [1 => 1];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setMapInt32Int32($values);
+
+        // Double elements
+        $values = [1 => 1.0];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setMapInt32Double($values);
+
+        // String elements
+        $values = ['a' => 'a'];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setMapStringString($values);
+
+        // Message elements
+        $values = [1 => new Sub()];
+        array_walk($values, function (&$value) {});
+        $m = new TestMessage();
+        $m->setMapInt32Message($values);
+    }
+
+    #########################################################
+    # 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);
+    // }
+}
diff --git a/third_party/protobuf/php/tests/memory_leak_test.php b/third_party/protobuf/php/tests/memory_leak_test.php
new file mode 100644
index 0000000..142f467
--- /dev/null
+++ b/third_party/protobuf/php/tests/memory_leak_test.php
@@ -0,0 +1,208 @@
+<?php
+
+# phpunit has memory leak by itself. Thus, it cannot be used to test memory leak.
+
+require_once('generated/NoNamespaceEnum.php');
+require_once('generated/NoNamespaceMessage.php');
+require_once('generated/NoNamespaceMessage/NestedEnum.php');
+require_once('generated/NoNamespaceMessage/NestedMessage.php');
+require_once('generated/PrefixEmpty.php');
+require_once('generated/PrefixTestPrefix.php');
+require_once('generated/PrefixTestPrefix/PrefixNestedEnum.php');
+require_once('generated/PrefixTestPrefix/PrefixNestedMessage.php');
+require_once('generated/TestEmptyNamespace.php');
+require_once('generated/TestEmptyNamespace/NestedEnum.php');
+require_once('generated/TestEmptyNamespace/NestedMessage.php');
+require_once('generated/Bar/TestInclude.php');
+require_once('generated/Bar/TestLegacyMessage.php');
+require_once('generated/Bar/TestLegacyMessage/NestedEnum.php');
+require_once('generated/Bar/TestLegacyMessage/NestedMessage.php');
+require_once('generated/Foo/PBARRAY.php');
+require_once('generated/Foo/PBEmpty.php');
+require_once('generated/Foo/TestAny.php');
+require_once('generated/Foo/TestEnum.php');
+require_once('generated/Foo/TestIncludeNamespaceMessage.php');
+require_once('generated/Foo/TestIncludePrefixMessage.php');
+require_once('generated/Foo/TestMessage.php');
+require_once('generated/Foo/TestMessage/PBEmpty.php');
+require_once('generated/Foo/TestMessage/NestedEnum.php');
+require_once('generated/Foo/TestMessage/Sub.php');
+require_once('generated/Foo/TestPackedMessage.php');
+require_once('generated/Foo/TestPhpDoc.php');
+require_once('generated/Foo/TestRandomFieldOrder.php');
+require_once('generated/Foo/TestReverseFieldOrder.php');
+require_once('generated/Foo/TestUnpackedMessage.php');
+require_once('generated/Foo/testLowerCaseMessage.php');
+require_once('generated/Foo/testLowerCaseEnum.php');
+require_once('generated/GPBMetadata/Proto/Test.php');
+require_once('generated/TestEmptyPhpNamespace.php');
+require_once('generated/GPBMetadata/Proto/TestInclude.php');
+require_once('generated/TestNoNamespace.php');
+require_once('generated/Metadata/Php/Test/TestPhpNamespace.php');
+require_once('generated/GPBMetadata/Proto/TestPrefix.php');
+require_once('generated/Php/Test/TestNamespace.php');
+require_once('generated/Php/Test/TestNamespace/PBEmpty.php');
+require_once('generated/Php/Test/TestNamespace/PBEmpty/NestedEnum.php');
+require_once('generated/Php/Test/TestNamespace/PBEmpty/NestedMessage.php');
+require_once('generated/Php/Test/TestNamespace/NestedEnum.php');
+require_once('generated/Php/Test/TestNamespace/NestedMessage.php');
+require_once('test_util.php');
+
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\GPBType;
+use Foo\TestAny;
+use Foo\TestMessage;
+use Foo\TestMessage\Sub;
+
+$from = new TestMessage();
+TestUtil::setTestMessage($from);
+TestUtil::assertTestMessage($from);
+
+$data = $from->serializeToString();
+
+$to = new TestMessage();
+$to->mergeFromString($data);
+
+TestUtil::assertTestMessage($to);
+
+$from = new TestMessage();
+TestUtil::setTestMessage2($from);
+
+$data = $from->serializeToString();
+
+$to->mergeFromString($data);
+
+// TODO(teboring): This causes following tests fail in php7.
+# $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->serializeToString();
+$n = new TestMessage();
+$n->mergeFromString($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->serializeToString();
+$n = new TestMessage();
+$n->mergeFromString($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->serializeToString();
+$n = new TestMessage();
+$n->mergeFromString($data);
+assert('abc' === $n->getOneofString());
+
+$sub_m = new 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->serializeToString();
+$n = new TestMessage();
+$n->mergeFromString($data);
+assert(1 === $n->getOneofMessage()->getA());
+
+$m = new TestMessage();
+$m->mergeFromString(hex2bin('F80601'));
+assert('f80601' === bin2hex($m->serializeToString()));
+
+// Test create repeated field via array.
+$str_arr = array("abc");
+$m = new TestMessage();
+$m->setRepeatedString($str_arr);
+
+// Test create map field via array.
+$str_arr = array("abc"=>"abc");
+$m = new TestMessage();
+$m->setMapStringString($str_arr);
+
+// Test unset
+$from = new TestMessage();
+TestUtil::setTestMessage($from);
+unset($from);
+
+// Test wellknown
+$from = new \Google\Protobuf\Timestamp();
+$from->setSeconds(1);
+assert(1, $from->getSeconds());
+
+$timestamp = new \Google\Protobuf\Timestamp();
+
+date_default_timezone_set('UTC');
+$from = new DateTime('2011-01-01T15:03:01.012345UTC');
+$timestamp->fromDateTime($from);
+assert($from->format('U') == $timestamp->getSeconds());
+assert(1000 * $from->format('u') == $timestamp->getNanos());
+
+$to = $timestamp->toDateTime();
+assert(\DateTime::class == get_class($to));
+assert($from->format('U') == $to->format('U'));
+
+$from = new \Google\Protobuf\Value();
+$from->setNumberValue(1);
+assert(1, $from->getNumberValue());
+
+// Test discard unknown in message.
+$m = new TestMessage();
+$from = hex2bin('F80601');
+$m->mergeFromString($from);
+$m->discardUnknownFields();
+$to = $m->serializeToString();
+assert("" === bin2hex($to));
+
+// Test clear
+$m = new TestMessage();
+TestUtil::setTestMessage($m);
+$m->clear();
+
+// Test unset map element
+$m = new TestMessage();
+$map = $m->getMapStringString();
+$map[1] = 1;
+unset($map[1]);
+
+// Test descriptor
+$pool = \Google\Protobuf\DescriptorPool::getGeneratedPool();
+$desc = $pool->getDescriptorByClassName("\Foo\TestMessage");
+$field = $desc->getField(1);
+
+$from = new TestMessage();
+$to = new TestMessage();
+TestUtil::setTestMessage($from);
+$to->mergeFrom($from);
+TestUtil::assertTestMessage($to);
+
+// Test decode Any
+// Make sure packed message has been created at least once.
+$packed = new TestMessage();
+
+$m = new TestAny();
+$m->mergeFromJsonString(
+    "{\"any\":" .
+    "  {\"@type\":\"type.googleapis.com/foo.TestMessage\"," .
+    "   \"optionalInt32\":1}}");
+assert("type.googleapis.com/foo.TestMessage" ===
+       $m->getAny()->getTypeUrl());
+assert("0801" === bin2hex($m->getAny()->getValue()));
diff --git a/third_party/protobuf/php/tests/php_implementation_test.php b/third_party/protobuf/php/tests/php_implementation_test.php
new file mode 100644
index 0000000..307b749
--- /dev/null
+++ b/third_party/protobuf/php/tests/php_implementation_test.php
@@ -0,0 +1,598 @@
+<?php
+
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Foo\TestEnum;
+use Foo\TestMessage;
+use Foo\TestMessage\Sub;
+use Foo\TestPackedMessage;
+use Google\Protobuf\Internal\CodedInputStream;
+use Google\Protobuf\Internal\FileDescriptorSet;
+use Google\Protobuf\Internal\GPBLabel;
+use Google\Protobuf\Internal\GPBType;
+use Google\Protobuf\Internal\GPBWire;
+use Google\Protobuf\Internal\CodedOutputStream;
+
+/**
+ * Please note, this test is only intended to be run without the protobuf C
+ * extension.
+ */
+class ImplementationTest extends TestBase
+{
+    public function setUp()
+    {
+        if (extension_loaded('protobuf')) {
+            $this->markTestSkipped();
+        }
+    }
+
+    public function testReadInt32()
+    {
+        $value = null;
+
+        // Positive number.
+        $input = new CodedInputStream(hex2bin("01"));
+        GPBWire::readInt32($input, $value);
+        $this->assertSame(1, $value);
+
+        // Negative number.
+        $input = new CodedInputStream(hex2bin("ffffffff0f"));
+        GPBWire::readInt32($input, $value);
+        $this->assertSame(-1, $value);
+
+        // Discard overflow bits.
+        $input = new CodedInputStream(hex2bin("ffffffff7f"));
+        GPBWire::readInt32($input, $value);
+        $this->assertSame(-1, $value);
+    }
+
+    public function testReadUint32()
+    {
+        $value = null;
+
+        // Positive number.
+        $input = new CodedInputStream(hex2bin("01"));
+        GPBWire::readUint32($input, $value);
+        $this->assertSame(1, $value);
+
+        // Max uint32.
+        $input = new CodedInputStream(hex2bin("ffffffff0f"));
+        GPBWire::readUint32($input, $value);
+        $this->assertSame(-1, $value);
+
+        // Discard overflow bits.
+        $input = new CodedInputStream(hex2bin("ffffffff7f"));
+        GPBWire::readUint32($input, $value);
+        $this->assertSame(-1, $value);
+    }
+
+    public function testReadInt64()
+    {
+        $value = null;
+
+        // Positive number.
+        $input = new CodedInputStream(hex2bin("01"));
+        GPBWire::readInt64($input, $value);
+        $this->assertEquals(1, $value);
+
+        // Negative number.
+        $input = new CodedInputStream(hex2bin("ffffffffffffffffff01"));
+        GPBWire::readInt64($input, $value);
+        $this->assertEquals(-1, $value);
+
+        // Discard overflow bits.
+        $input = new CodedInputStream(hex2bin("ffffffffffffffffff0f"));
+        GPBWire::readInt64($input, $value);
+        $this->assertEquals(-1, $value);
+    }
+
+    public function testReadUint64()
+    {
+        $value = null;
+
+        // Positive number.
+        $input = new CodedInputStream(hex2bin("01"));
+        GPBWire::readUint64($input, $value);
+        $this->assertEquals(1, $value);
+
+        // Negative number.
+        $input = new CodedInputStream(hex2bin("FFFFFFFFFFFFFFFFFF01"));
+        GPBWire::readUint64($input, $value);
+        $this->assertEquals(-1, $value);
+
+        // Discard overflow bits.
+        $input = new CodedInputStream(hex2bin("FFFFFFFFFFFFFFFFFF0F"));
+        GPBWire::readUint64($input, $value);
+        $this->assertEquals(-1, $value);
+    }
+
+    public function testReadSint32()
+    {
+        $value = null;
+
+        $input = new CodedInputStream(hex2bin("00"));
+        GPBWire::readSint32($input, $value);
+        $this->assertSame(0, $value);
+
+        $input = new CodedInputStream(hex2bin("01"));
+        GPBWire::readSint32($input, $value);
+        $this->assertSame(-1, $value);
+
+        $input = new CodedInputStream(hex2bin("02"));
+        GPBWire::readSint32($input, $value);
+        $this->assertSame(1, $value);
+    }
+
+    public function testReadSint64()
+    {
+        $value = null;
+
+        $input = new CodedInputStream(hex2bin("00"));
+        GPBWire::readSint64($input, $value);
+        $this->assertEquals(0, $value);
+
+        $input = new CodedInputStream(hex2bin("01"));
+        GPBWire::readSint64($input, $value);
+        $this->assertEquals(-1, $value);
+
+        $input = new CodedInputStream(hex2bin("02"));
+        GPBWire::readSint64($input, $value);
+        $this->assertEquals(1, $value);
+    }
+
+    public function testReadFixed32()
+    {
+        $value = null;
+        $input = new CodedInputStream(hex2bin("12345678"));
+        GPBWire::readFixed32($input, $value);
+        $this->assertSame(0x78563412, $value);
+    }
+
+    public function testReadFixed64()
+    {
+        $value = null;
+        $input = new CodedInputStream(hex2bin("1234567812345678"));
+        GPBWire::readFixed64($input, $value);
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame("8671175386481439762", $value);
+        } else {
+            $this->assertSame(0x7856341278563412, $value);
+        }
+    }
+
+    public function testReadSfixed32()
+    {
+        $value = null;
+        $input = new CodedInputStream(hex2bin("12345678"));
+        GPBWire::readSfixed32($input, $value);
+        $this->assertSame(0x78563412, $value);
+    }
+
+    public function testReadFloat()
+    {
+        $value = null;
+        $input = new CodedInputStream(hex2bin("0000803F"));
+        GPBWire::readFloat($input, $value);
+        $this->assertSame(1.0, $value);
+    }
+
+    public function testReadBool()
+    {
+        $value = null;
+
+        $input = new CodedInputStream(hex2bin("00"));
+        GPBWire::readBool($input, $value);
+        $this->assertSame(false, $value);
+
+        $input = new CodedInputStream(hex2bin("01"));
+        GPBWire::readBool($input, $value);
+        $this->assertSame(true, $value);
+    }
+
+    public function testReadDouble()
+    {
+        $value = null;
+        $input = new CodedInputStream(hex2bin("000000000000F03F"));
+        GPBWire::readDouble($input, $value);
+        $this->assertSame(1.0, $value);
+    }
+
+    public function testReadSfixed64()
+    {
+        $value = null;
+        $input = new CodedInputStream(hex2bin("1234567812345678"));
+        GPBWire::readSfixed64($input, $value);
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame("8671175386481439762", $value);
+        } else {
+            $this->assertSame(0x7856341278563412, $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(0x7FFFFFFF, GPBWire::zigZagEncode32(-1073741824));
+
+        $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((int)-2147483648,GPBWire::zigZagDecode32(0xFFFFFFFF));
+
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame(-2, GPBWire::zigZagEncode32(0x7FFFFFFF));
+            $this->assertSame(-1, GPBWire::zigZagEncode32(0x80000000));
+            $this->assertSame('0', GPBWire::zigZagEncode64(0));
+            $this->assertSame('1', GPBWire::zigZagEncode64(-1));
+            $this->assertSame('2', GPBWire::zigZagEncode64(1));
+            $this->assertSame('3', GPBWire::zigZagEncode64(-2));
+            $this->assertSame(
+                '2147483646',  // 0x7FFFFFE
+                GPBWire::zigZagEncode64(0x3FFFFFFF));
+            $this->assertSame(
+                '2147483647',  // 0x7FFFFFF
+                GPBWire::zigZagEncode64(-1073741824));  // 0xFFFFFFFFC0000000
+            $this->assertSame(
+                '4294967294',                           // 0xFFFFFFFE
+                GPBWire::zigZagEncode64(2147483647));   // 0x7FFFFFFF
+            $this->assertSame(
+                '4294967295',                           // 0xFFFFFFFF
+                GPBWire::zigZagEncode64(-2147483648));  // 0xFFFFFFFF80000000
+            $this->assertSame(
+                '18446744073709551614',  // 0xFFFFFFFFFFFFFFFE
+                                         // 0x7FFFFFFFFFFFFFFF
+                GPBWire::zigZagEncode64("9223372036854775807"));
+            $this->assertSame(
+                '18446744073709551615',  // 0xFFFFFFFFFFFFFFFF
+                                         // 0x8000000000000000
+                GPBWire::zigZagEncode64("-9223372036854775808"));
+
+            $this->assertSame('0', GPBWire::zigZagDecode64(0));
+            $this->assertSame('-1', GPBWire::zigZagDecode64(1));
+            $this->assertSame('1', GPBWire::zigZagDecode64(2));
+            $this->assertSame('-2', GPBWire::zigZagDecode64(3));
+        } else {
+            $this->assertSame(4294967294, GPBWire::zigZagEncode32(0x7FFFFFFF));
+            $this->assertSame(4294967295, GPBWire::zigZagEncode32(0x80000000));
+            $this->assertSame(0, GPBWire::zigZagEncode64(0));
+            $this->assertSame(1, GPBWire::zigZagEncode64(-1));
+            $this->assertSame(2, GPBWire::zigZagEncode64(1));
+            $this->assertSame(3, GPBWire::zigZagEncode64(-2));
+            $this->assertSame(0x7FFFFFFE, GPBWire::zigZagEncode64(0x3FFFFFFF));
+            $this->assertSame(
+                0x7FFFFFFF,
+                GPBWire::zigZagEncode64(0xFFFFFFFFC0000000));
+            $this->assertSame(
+                0xFFFFFFFE,
+                GPBWire::zigZagEncode64(0x7FFFFFFF));
+            $this->assertSame(
+                0xFFFFFFFF,
+                GPBWire::zigZagEncode64(0xFFFFFFFF80000000));
+            $this->assertSame(
+                -2,  // 0xFFFFFFFFFFFFFFFE
+                GPBWire::zigZagEncode64(0x7FFFFFFFFFFFFFFF));
+            $this->assertSame(
+                -1,  // 0xFFFFFFFFFFFFFFFF
+                GPBWire::zigZagEncode64(0x8000000000000000));
+
+            $this->assertSame(0, GPBWire::zigZagDecode64(0));
+            $this->assertSame(-1, GPBWire::zigZagDecode64(1));
+            $this->assertSame(1, GPBWire::zigZagDecode64(2));
+            $this->assertSame(-2, GPBWire::zigZagDecode64(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->mergeFromString(TestUtil::getGoldenTestMessage());
+        TestUtil::assertTestMessage($m);
+    }
+
+    public function testDescriptorDecode()
+    {
+        $file_desc_set = new FileDescriptorSet();
+        $file_desc_set->mergeFromString(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 CodedInputStream(hex2bin(''));
+        $this->assertFalse($input->readVarint64($var));
+
+        // The largest varint is 10 bytes long.
+        $input = new CodedInputStream(hex2bin('8080808080808080808001'));
+        $this->assertFalse($input->readVarint64($var));
+
+        // Corrupted varint.
+        $input = new CodedInputStream(hex2bin('808080'));
+        $this->assertFalse($input->readVarint64($var));
+
+        // Normal case.
+        $input = new CodedInputStream(hex2bin('808001'));
+        $this->assertTrue($input->readVarint64($var));
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('16384', $var);
+        } else {
+            $this->assertSame(16384, $var);
+        }
+        $this->assertFalse($input->readVarint64($var));
+
+        // Read two varint.
+        $input = new CodedInputStream(hex2bin('808001808002'));
+        $this->assertTrue($input->readVarint64($var));
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('16384', $var);
+        } else {
+            $this->assertSame(16384, $var);
+        }
+        $this->assertTrue($input->readVarint64($var));
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('32768', $var);
+        } else {
+            $this->assertSame(32768, $var);
+        }
+        $this->assertFalse($input->readVarint64($var));
+
+        // Read 64 testing
+        $testVals = array(
+            '10'                 => '0a000000000000000000',
+            '100'                => '64000000000000000000',
+            '800'                => 'a0060000000000000000',
+            '6400'               => '80320000000000000000',
+            '70400'              => '80a60400000000000000',
+            '774400'             => '80a22f00000000000000',
+            '9292800'            => '8098b704000000000000',
+            '74342400'           => '80c0b923000000000000',
+            '743424000'          => '8080bfe2020000000000',
+            '8177664000'         => '8080b5bb1e0000000000',
+            '65421312000'        => '8080a8dbf30100000000',
+            '785055744000'       => '8080e0c7ec1600000000',
+            '9420668928000'      => '808080dd969202000000',
+            '103627358208000'    => '808080fff9c717000000',
+            '1139900940288000'   => '808080f5bd9783020000',
+            '13678811283456000'  => '808080fce699a6180000',
+            '109430490267648000' => '808080e0b7ceb1c20100',
+            '984874412408832000' => '808080e0f5c1bed50d00',
+        );
+
+        foreach ($testVals as $original => $encoded) {
+            $input = new CodedInputStream(hex2bin($encoded));
+            $this->assertTrue($input->readVarint64($var));
+            $this->assertEquals($original, $var);
+        }
+    }
+
+    public function testReadVarint32()
+    {
+        $var = 0;
+
+        // Empty buffer.
+        $input = new CodedInputStream(hex2bin(''));
+        $this->assertFalse($input->readVarint32($var));
+
+        // The largest varint is 10 bytes long.
+        $input = new CodedInputStream(hex2bin('8080808080808080808001'));
+        $this->assertFalse($input->readVarint32($var));
+
+        // Corrupted varint.
+        $input = new CodedInputStream(hex2bin('808080'));
+        $this->assertFalse($input->readVarint32($var));
+
+        // Normal case.
+        $input = new CodedInputStream(hex2bin('808001'));
+        $this->assertTrue($input->readVarint32($var));
+        $this->assertSame(16384, $var);
+        $this->assertFalse($input->readVarint32($var));
+
+        // Read two varint.
+        $input = new CodedInputStream(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 CodedInputStream(hex2bin('808081808001'));
+        $this->assertTrue($input->readVarint32($var));
+        $this->assertSame(16384, $var);
+        $this->assertFalse($input->readVarint32($var));
+    }
+
+    public function testReadTag()
+    {
+        $input = new CodedInputStream(hex2bin('808001'));
+        $tag = $input->readTag();
+        $this->assertSame(16384, $tag);
+        $tag = $input->readTag();
+        $this->assertSame(0, $tag);
+    }
+
+    public function testPushPopLimit()
+    {
+        $input = new CodedInputStream(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 CodedInputStream(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 CodedOutputStream(3);
+        $output->writeVarint32(16384, true);
+        $this->assertSame(hex2bin('808001'), $output->getData());
+
+        // Negative numbers are padded to be compatible with int64.
+        $output = new CodedOutputStream(10);
+        $output->writeVarint32(-43, false);
+        $this->assertSame(hex2bin('D5FFFFFFFFFFFFFFFF01'), $output->getData());
+    }
+
+    public function testWriteVarint64()
+    {
+        $output = new CodedOutputStream(10);
+        $output->writeVarint64(-43);
+        $this->assertSame(hex2bin('D5FFFFFFFFFFFFFFFF01'), $output->getData());
+    }
+
+    public function testWriteLittleEndian32()
+    {
+        $output = new CodedOutputStream(4);
+        $output->writeLittleEndian32(46);
+        $this->assertSame(hex2bin('2E000000'), $output->getData());
+    }
+
+    public function testWriteLittleEndian64()
+    {
+        $output = new CodedOutputStream(8);
+        $output->writeLittleEndian64(47);
+        $this->assertSame(hex2bin('2F00000000000000'), $output->getData());
+    }
+
+    public function testByteSize()
+    {
+        $m = new TestMessage();
+        TestUtil::setTestMessage($m);
+        $this->assertSame(518, $m->byteSize());
+    }
+
+    public function testPackedByteSize()
+    {
+        $m = new TestPackedMessage();
+        TestUtil::setTestPackedMessage($m);
+        $this->assertSame(166, $m->byteSize());
+    }
+
+    /**
+     * @expectedException UnexpectedValueException
+     * @expectedExceptionMessage Invalid message property: optionalInt32
+     */
+    public function testArrayConstructorJsonCaseThrowsException()
+    {
+        $m = new TestMessage([
+            'optionalInt32' => -42,
+        ]);
+    }
+
+    /**
+     * @expectedException Exception
+     * @expectedExceptionMessage Expect Foo\TestMessage_Sub.
+     */
+    public function testArraysForMessagesThrowsException()
+    {
+        $m = new TestMessage([
+            'optional_message' => [
+                'a' => 33
+            ]
+        ]);
+    }
+
+    public function testArrayConstructorWithNullValues()
+    {
+        $requestData = [
+            'optional_bool' => null,
+            'optional_string' => null,
+            'optional_bytes' => null,
+            'optional_message' => null,
+        ];
+
+        $m = new TestMessage($requestData);
+
+        $this->assertSame(false, $m->getOptionalBool());
+        $this->assertSame('', $m->getOptionalString());
+        $this->assertSame('', $m->getOptionalBytes());
+        $this->assertSame(null, $m->getOptionalMessage());
+    }
+
+    /**
+     * @dataProvider provideArrayConstructorWithNullValuesThrowsException
+     * @expectedException Exception
+     */
+    public function testArrayConstructorWithNullValuesThrowsException($requestData)
+    {
+        $m = new TestMessage($requestData);
+    }
+
+    public function provideArrayConstructorWithNullValuesThrowsException()
+    {
+        return [
+            [['optional_int32' => null]],
+            [['optional_int64' => null]],
+            [['optional_uint32' => null]],
+            [['optional_uint64' => null]],
+            [['optional_sint32' => null]],
+            [['optional_sint64' => null]],
+            [['optional_fixed32' => null]],
+            [['optional_fixed64' => null]],
+            [['optional_sfixed32' => null]],
+            [['optional_sfixed64' => null]],
+            [['optional_float' => null]],
+            [['optional_double' => null]],
+            [['optional_enum' => null]],
+            [['repeated_int32' => null]],
+            [['map_int32_int32' => null]],
+        ];
+    }
+}
diff --git a/third_party/protobuf/php/tests/proto/empty/echo.proto b/third_party/protobuf/php/tests/proto/empty/echo.proto
new file mode 100644
index 0000000..1817018
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/empty/echo.proto
@@ -0,0 +1,17 @@
+syntax = "proto3";
+
+package empty.echo;
+
+message TestEmptyPackage {
+  int32 a = 1;
+
+  // Test nested messages, enums, and reserved names
+  NestedMessage nested_message = 2;
+  NestedEnum nested_enum = 3;
+  message NestedMessage {
+    int32 a = 1;
+  }
+  enum NestedEnum {
+      ZERO = 0;
+  };
+}
diff --git a/third_party/protobuf/php/tests/proto/test.proto b/third_party/protobuf/php/tests/proto/test.proto
new file mode 100644
index 0000000..715f08b
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test.proto
@@ -0,0 +1,209 @@
+syntax = "proto3";
+
+import 'google/protobuf/any.proto';
+import 'google/protobuf/struct.proto';
+import 'proto/test_include.proto';
+import 'proto/test_no_namespace.proto';
+import 'proto/test_php_namespace.proto';
+import 'proto/test_empty_php_namespace.proto';
+import 'proto/test_prefix.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;
+    repeated int32 b = 2;
+  }
+
+  // Reserved for non-existing field test.
+  // int32 non_exist = 89;
+
+  NoNamespaceMessage optional_no_namespace_message = 91;
+  NoNamespaceEnum optional_no_namespace_enum = 92;
+  repeated NoNamespaceMessage repeated_no_namespace_message = 93;
+  repeated NoNamespaceEnum repeated_no_namespace_enum = 94;
+
+  enum NestedEnum {
+    ZERO = 0;
+  }
+
+  NestedEnum optional_nested_enum = 101;
+
+  // Test prefix for reserved words.
+  message Empty {
+    int32 a = 1;
+  }
+
+  reserved 111;
+}
+
+enum TestEnum {
+  ZERO = 0;
+  ONE  = 1;
+  TWO  = 2;
+  ECHO = 3;  // Test reserved name.
+}
+
+// Test prefix for reserved words.
+message Empty {
+  int32 a = 1;
+}
+
+message ARRAY {
+  int32 a = 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];
+}
+
+// /**/@<>&\{
+message TestPhpDoc {
+  int32 a = 1;
+}
+
+message TestIncludePrefixMessage {
+  TestPrefix prefix_message = 1;
+}
+
+message TestIncludeNamespaceMessage {
+  TestNamespace namespace_message = 1;
+  TestEmptyNamespace empty_namespace_message = 2;
+}
+
+// This will cause upb fields not ordered by the order in the generated code.
+message TestRandomFieldOrder {
+  int64 tag13 = 150;
+  string tag14 = 160;
+}
+
+message TestReverseFieldOrder {
+  repeated int32 a = 2;
+  string b = 1;
+}
+
+message testLowerCaseMessage {
+}
+
+enum testLowerCaseEnum {
+  VALUE = 0;
+}
+
+message TestAny {
+  google.protobuf.Any any = 1;
+}
diff --git a/third_party/protobuf/php/tests/proto/test_descriptors.proto b/third_party/protobuf/php/tests/proto/test_descriptors.proto
new file mode 100644
index 0000000..d42aec7
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_descriptors.proto
@@ -0,0 +1,35 @@
+syntax = "proto3";
+
+package descriptors;
+
+message TestDescriptorsMessage {
+  int32 optional_int32 = 1;
+  TestDescriptorsEnum optional_enum = 16;
+  Sub optional_message = 17;
+
+  // Repeated
+  repeated int32 repeated_int32 = 31;
+  repeated Sub repeated_message = 47;
+
+  oneof my_oneof {
+    int32 oneof_int32    = 51;
+  }
+
+  map<int32, EnumSub> map_int32_enum = 71;
+
+  message Sub {
+    int32 a = 1;
+    repeated int32 b = 2;
+  }
+
+  enum EnumSub {
+    ZERO = 0;
+    ONE = 1;
+  }
+}
+
+enum TestDescriptorsEnum {
+  ZERO = 0;
+  ONE  = 1;
+}
+
diff --git a/third_party/protobuf/php/tests/proto/test_empty_php_namespace.proto b/third_party/protobuf/php/tests/proto/test_empty_php_namespace.proto
new file mode 100644
index 0000000..c7ed165
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_empty_php_namespace.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+package foo;
+option php_namespace = "";
+option php_metadata_namespace = "";
+
+message TestEmptyNamespace {
+  int32 a = 1;
+
+  // Test nested messages, enums, and reserved names
+  NestedMessage nested_message = 2;
+  NestedEnum nested_enum = 3;
+  message NestedMessage {
+    int32 a = 1;
+  }
+  enum NestedEnum {
+      ZERO = 0;
+  };
+}
diff --git a/third_party/protobuf/php/tests/proto/test_import_descriptor_proto.proto b/third_party/protobuf/php/tests/proto/test_import_descriptor_proto.proto
new file mode 100644
index 0000000..2a19940
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_import_descriptor_proto.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+import "google/protobuf/descriptor.proto";
+
+message TestImportDescriptorProto {
+  extend google.protobuf.MethodOptions {
+    int32 a = 72295727;
+  }
+}
+
+extend google.protobuf.MethodOptions {
+  int32 a = 72295728;
+}
+
diff --git a/third_party/protobuf/php/tests/proto/test_include.proto b/third_party/protobuf/php/tests/proto/test_include.proto
new file mode 100644
index 0000000..a9072fe
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_include.proto
@@ -0,0 +1,18 @@
+syntax = "proto3";
+
+package bar;
+
+message TestInclude {
+  int32 a = 1;
+}
+
+message TestLegacyMessage {
+  NestedMessage message = 1;
+  NestedEnum enum = 2;
+  message NestedMessage {
+    int32 a = 1;
+  }
+  enum NestedEnum {
+    ZERO = 0;
+  }
+}
diff --git a/third_party/protobuf/php/tests/proto/test_no_namespace.proto b/third_party/protobuf/php/tests/proto/test_no_namespace.proto
new file mode 100644
index 0000000..cce42ea
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_no_namespace.proto
@@ -0,0 +1,22 @@
+syntax = "proto3";
+
+option php_metadata_namespace = "\\";
+
+message NoNamespaceMessage {
+  int32 a = 1;
+
+  // Test nested messages, enums, and reserved names
+  NestedMessage nested_message = 2;
+  NestedEnum nested_enum = 3;
+  message NestedMessage {
+    int32 a = 1;
+  }
+  enum NestedEnum {
+      ZERO = 0;
+  };
+}
+
+enum NoNamespaceEnum {
+  VALUE_A = 0;
+  VALUE_B = 1;
+}
diff --git a/third_party/protobuf/php/tests/proto/test_php_namespace.proto b/third_party/protobuf/php/tests/proto/test_php_namespace.proto
new file mode 100644
index 0000000..61085bf
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_php_namespace.proto
@@ -0,0 +1,31 @@
+syntax = "proto3";
+
+package foo;
+option php_namespace = "Php\\Test";
+option php_metadata_namespace = "Metadata\\Php\\Test";
+
+message TestNamespace {
+  int32 a = 1;
+
+  // Test nested messages, enums, and reserved names
+  NestedMessage nested_message = 2;
+  NestedEnum nested_enum = 3;
+  Empty reserved_name = 4;
+  message NestedMessage {
+    int32 a = 1;
+  }
+  enum NestedEnum {
+      ZERO = 0;
+  };
+  // Test reserved name
+  message Empty {
+    NestedMessage nested_message = 1;
+    NestedEnum nested_enum = 2;
+    message NestedMessage {
+      int32 a = 1;
+    }
+    enum NestedEnum {
+      ZERO = 0;
+    };
+  }
+}
diff --git a/third_party/protobuf/php/tests/proto/test_prefix.proto b/third_party/protobuf/php/tests/proto/test_prefix.proto
new file mode 100644
index 0000000..3fa1138
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_prefix.proto
@@ -0,0 +1,20 @@
+syntax = "proto3";
+
+option php_class_prefix = "Prefix";
+
+message TestPrefix {
+  int32 a = 1;
+  NestedMessage nested_message = 2;
+  NestedEnum nested_enum = 3;
+  message NestedMessage {
+    int32 a = 1;
+  }
+  enum NestedEnum {
+      ZERO = 0;
+  };
+}
+
+// Test prefix for reserved words.
+message Empty {
+  int32 a = 1;
+}
diff --git a/third_party/protobuf/php/tests/proto/test_reserved_enum_lower.proto b/third_party/protobuf/php/tests/proto/test_reserved_enum_lower.proto
new file mode 100644
index 0000000..d2daeaf
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_reserved_enum_lower.proto
@@ -0,0 +1,77 @@
+syntax = "proto3";
+
+package lower_enum;
+
+enum abstract { ZERO1 = 0; }
+enum and { ZERO2 = 0; }
+enum array { ZERO3 = 0; }
+enum as { ZERO4 = 0; }
+enum break { ZERO5 = 0; }
+enum callable { ZERO6 = 0; }
+enum case { ZERO7 = 0; }
+enum catch { ZERO8 = 0; }
+enum class { ZERO9 = 0; }
+enum clone { ZERO10 = 0; }
+enum const { ZERO11 = 0; }
+enum continue { ZERO12 = 0; }
+enum declare { ZERO13 = 0; }
+enum default { ZERO14 = 0; }
+enum die { ZERO15 = 0; }
+enum do { ZERO16 = 0; }
+enum echo { ZERO17 = 0; }
+enum else { ZERO18 = 0; }
+enum elseif { ZERO19 = 0; }
+enum empty { ZERO20 = 0; }
+enum enddeclare { ZERO21 = 0; }
+enum endfor { ZERO22 = 0; }
+enum endforeach { ZERO23 = 0; }
+enum endif { ZERO24 = 0; }
+enum endswitch { ZERO25 = 0; }
+enum endwhile { ZERO26 = 0; }
+enum eval { ZERO27 = 0; }
+enum exit { ZERO28 = 0; }
+enum extends { ZERO29 = 0; }
+enum final { ZERO30 = 0; }
+enum for { ZERO31 = 0; }
+enum foreach { ZERO32 = 0; }
+enum function { ZERO33 = 0; }
+enum global { ZERO34 = 0; }
+enum goto { ZERO35 = 0; }
+enum if { ZERO36 = 0; }
+enum implements { ZERO37 = 0; }
+enum include { ZERO38 = 0; }
+enum include_once { ZERO39 = 0; }
+enum instanceof { ZERO40 = 0; }
+enum insteadof { ZERO41 = 0; }
+enum interface { ZERO42 = 0; }
+enum isset { ZERO43 = 0; }
+enum list { ZERO44 = 0; }
+enum namespace { ZERO45 = 0; }
+enum new { ZERO46 = 0; }
+enum or { ZERO47 = 0; }
+enum print { ZERO48 = 0; }
+enum private { ZERO49 = 0; }
+enum protected { ZERO50 = 0; }
+enum public { ZERO51 = 0; }
+enum require { ZERO52 = 0; }
+enum require_once { ZERO53 = 0; }
+enum return { ZERO54 = 0; }
+enum static { ZERO55 = 0; }
+enum switch { ZERO56 = 0; }
+enum throw { ZERO57 = 0; }
+enum trait { ZERO58 = 0; }
+enum try { ZERO59 = 0; }
+enum unset { ZERO60 = 0; }
+enum use { ZERO61 = 0; }
+enum var { ZERO62 = 0; }
+enum while { ZERO63 = 0; }
+enum xor { ZERO64 = 0; }
+enum int { ZERO65 = 0; }
+enum float { ZERO66 = 0; }
+enum bool { ZERO67 = 0; }
+enum string { ZERO68 = 0; }
+enum true { ZERO69 = 0; }
+enum false { ZERO70 = 0; }
+enum null { ZERO71 = 0; }
+enum void { ZERO72 = 0; }
+enum iterable { ZERO73 = 0; }
diff --git a/third_party/protobuf/php/tests/proto/test_reserved_enum_upper.proto b/third_party/protobuf/php/tests/proto/test_reserved_enum_upper.proto
new file mode 100644
index 0000000..a396fea
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_reserved_enum_upper.proto
@@ -0,0 +1,77 @@
+syntax = "proto3";
+
+package upper_enum;
+
+enum ABSTRACT { ZERO1 = 0; }
+enum AND { ZERO2 = 0; }
+enum ARRAY { ZERO3 = 0; }
+enum AS { ZERO4 = 0; }
+enum BREAK { ZERO5 = 0; }
+enum CALLABLE { ZERO6 = 0; }
+enum CASE { ZERO7 = 0; }
+enum CATCH { ZERO8 = 0; }
+enum CLASS { ZERO9 = 0; }
+enum CLONE { ZERO10 = 0; }
+enum CONST { ZERO11 = 0; }
+enum CONTINUE { ZERO12 = 0; }
+enum DECLARE { ZERO13 = 0; }
+enum DEFAULT { ZERO14 = 0; }
+enum DIE { ZERO15 = 0; }
+enum DO { ZERO16 = 0; }
+enum ECHO { ZERO17 = 0; }
+enum ELSE { ZERO18 = 0; }
+enum ELSEIF { ZERO19 = 0; }
+enum EMPTY { ZERO20 = 0; }
+enum ENDDECLARE { ZERO21 = 0; }
+enum ENDFOR { ZERO22 = 0; }
+enum ENDFOREACH { ZERO23 = 0; }
+enum ENDIF { ZERO24 = 0; }
+enum ENDSWITCH { ZERO25 = 0; }
+enum ENDWHILE { ZERO26 = 0; }
+enum EVAL { ZERO27 = 0; }
+enum EXIT { ZERO28 = 0; }
+enum EXTENDS { ZERO29 = 0; }
+enum FINAL { ZERO30 = 0; }
+enum FOR { ZERO31 = 0; }
+enum FOREACH { ZERO32 = 0; }
+enum FUNCTION { ZERO33 = 0; }
+enum GLOBAL { ZERO34 = 0; }
+enum GOTO { ZERO35 = 0; }
+enum IF { ZERO36 = 0; }
+enum IMPLEMENTS { ZERO37 = 0; }
+enum INCLUDE { ZERO38 = 0; }
+enum INCLUDE_ONCE { ZERO39 = 0; }
+enum INSTANCEOF { ZERO40 = 0; }
+enum INSTEADOF { ZERO41 = 0; }
+enum INTERFACE { ZERO42 = 0; }
+enum ISSET { ZERO43 = 0; }
+enum LIST { ZERO44 = 0; }
+enum NAMESPACE { ZERO45 = 0; }
+enum NEW { ZERO46 = 0; }
+enum OR { ZERO47 = 0; }
+enum PRINT { ZERO48 = 0; }
+enum PRIVATE { ZERO49 = 0; }
+enum PROTECTED { ZERO50 = 0; }
+enum PUBLIC { ZERO51 = 0; }
+enum REQUIRE { ZERO52 = 0; }
+enum REQUIRE_ONCE { ZERO53 = 0; }
+enum RETURN { ZERO54 = 0; }
+enum STATIC { ZERO55 = 0; }
+enum SWITCH { ZERO56 = 0; }
+enum THROW { ZERO57 = 0; }
+enum TRAIT { ZERO58 = 0; }
+enum TRY { ZERO59 = 0; }
+enum UNSET { ZERO60 = 0; }
+enum USE { ZERO61 = 0; }
+enum VAR { ZERO62 = 0; }
+enum WHILE { ZERO63 = 0; }
+enum XOR { ZERO64 = 0; }
+enum INT { ZERO65 = 0; }
+enum FLOAT { ZERO66 = 0; }
+enum BOOL { ZERO67 = 0; }
+enum STRING { ZERO68 = 0; }
+enum TRUE { ZERO69 = 0; }
+enum FALSE { ZERO70 = 0; }
+enum NULL { ZERO71 = 0; }
+enum VOID { ZERO72 = 0; }
+enum ITERABLE { ZERO73 = 0; }
diff --git a/third_party/protobuf/php/tests/proto/test_reserved_enum_value_lower.proto b/third_party/protobuf/php/tests/proto/test_reserved_enum_value_lower.proto
new file mode 100644
index 0000000..96da319
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_reserved_enum_value_lower.proto
@@ -0,0 +1,79 @@
+syntax = "proto3";
+
+package lower_enum_value;
+
+enum NotAllowed {
+  abstract = 0;
+  and = 1;
+  array = 2;
+  as = 3;
+  break = 4;
+  callable = 5;
+  case     = 6;
+  catch    = 7;
+  class    = 8;
+  clone    = 9;
+  const    = 10;
+  continue = 11;
+  declare  = 12;
+  default  = 13;
+  die      = 14;
+  do       = 15;
+  echo     = 16;
+  else     = 17;
+  elseif   = 18;
+  empty    = 19;
+  enddeclare = 20;
+  endfor     = 21;
+  endforeach = 22;
+  endif      = 23;
+  endswitch = 24;
+  endwhile = 25;
+  eval = 26;
+  exit = 27;
+  extends = 28;
+  final = 29;
+  for = 30;
+  foreach = 31;
+  function = 32;
+  global = 33;
+  goto = 34;
+  if = 35;
+  implements = 36;
+  include = 37;
+  include_once = 38;
+  instanceof = 39;
+  insteadof = 40;
+  interface = 41;
+  isset = 42;
+  list = 43;
+  namespace = 44;
+  new = 45;
+  or = 46;
+  print = 47;
+  private = 48;
+  protected = 49;
+  public = 50;
+  require = 51;
+  require_once = 52;
+  return = 53;
+  static = 54;
+  switch = 55;
+  throw = 56;
+  trait = 57;
+  try = 58;
+  unset = 59;
+  use = 60;
+  var = 61;
+  while = 62;
+  xor = 63;
+  int = 64;
+  float = 65;
+  bool = 66;
+  string = 67;
+  true = 68;
+  false = 69;
+  null = 70;
+  void = 71;
+  iterable = 72;
+}
diff --git a/third_party/protobuf/php/tests/proto/test_reserved_enum_value_upper.proto b/third_party/protobuf/php/tests/proto/test_reserved_enum_value_upper.proto
new file mode 100644
index 0000000..b026a85
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_reserved_enum_value_upper.proto
@@ -0,0 +1,79 @@
+syntax = "proto3";
+
+package upper_enum_value;
+
+enum NotAllowed {
+  ABSTRACT = 0;
+  AND = 1;
+  ARRAY = 2;
+  AS = 3;
+  BREAK = 4;
+  CALLABLE = 5;
+  CASE     = 6;
+  CATCH    = 7;
+  CLASS    = 8;
+  CLONE    = 9;
+  CONST    = 10;
+  CONTINUE = 11;
+  DECLARE  = 12;
+  DEFAULT  = 13;
+  DIE      = 14;
+  DO       = 15;
+  ECHO     = 16;
+  ELSE     = 17;
+  ELSEIF   = 18;
+  EMPTY    = 19;
+  ENDDECLARE = 20;
+  ENDFOR     = 21;
+  ENDFOREACH = 22;
+  ENDIF      = 23;
+  ENDSWITCH = 24;
+  ENDWHILE = 25;
+  EVAL = 26;
+  EXIT = 27;
+  EXTENDS = 28;
+  FINAL = 29;
+  FOR = 30;
+  FOREACH = 31;
+  FUNCTION = 32;
+  GLOBAL = 33;
+  GOTO = 34;
+  IF = 35;
+  IMPLEMENTS = 36;
+  INCLUDE = 37;
+  INCLUDE_ONCE = 38;
+  INSTANCEOF = 39;
+  INSTEADOF = 40;
+  INTERFACE = 41;
+  ISSET = 42;
+  LIST = 43;
+  NAMESPACE = 44;
+  NEW = 45;
+  OR = 46;
+  PRINT = 47;
+  PRIVATE = 48;
+  PROTECTED = 49;
+  PUBLIC = 50;
+  REQUIRE = 51;
+  REQUIRE_ONCE = 52;
+  RETURN = 53;
+  STATIC = 54;
+  SWITCH = 55;
+  THROW = 56;
+  TRAIT = 57;
+  TRY = 58;
+  UNSET = 59;
+  USE = 60;
+  VAR = 61;
+  WHILE = 62;
+  XOR = 63;
+  INT = 64;
+  FLOAT = 65;
+  BOOL = 66;
+  STRING = 67;
+  TRUE = 68;
+  FALSE = 69;
+  NULL = 70;
+  VOID = 71;
+  ITERABLE = 72;
+}
diff --git a/third_party/protobuf/php/tests/proto/test_reserved_message_lower.proto b/third_party/protobuf/php/tests/proto/test_reserved_message_lower.proto
new file mode 100644
index 0000000..ed12080
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_reserved_message_lower.proto
@@ -0,0 +1,77 @@
+syntax = "proto3";
+
+package lower;
+
+message abstract {}
+message and {}
+message array {}
+message as {}
+message break {}
+message callable {}
+message case {}
+message catch {}
+message class {}
+message clone {}
+message const {}
+message continue {}
+message declare {}
+message default {}
+message die {}
+message do {}
+message echo {}
+message else {}
+message elseif {}
+message empty {}
+message enddeclare {}
+message endfor {}
+message endforeach {}
+message endif {}
+message endswitch {}
+message endwhile {}
+message eval {}
+message exit {}
+message extends {}
+message final {}
+message for {}
+message foreach {}
+message function {}
+message global {}
+message goto {}
+message if {}
+message implements {}
+message include {}
+message include_once {}
+message instanceof {}
+message insteadof {}
+message interface {}
+message isset {}
+message list {}
+message namespace {}
+message new {}
+message or {}
+message print {}
+message private {}
+message protected {}
+message public {}
+message require {}
+message require_once {}
+message return {}
+message static {}
+message switch {}
+message throw {}
+message trait {}
+message try {}
+message unset {}
+message use {}
+message var {}
+message while {}
+message xor {}
+message int {}
+message float {}
+message bool {}
+message string {}
+message true {}
+message false {}
+message null {}
+message void {}
+message iterable {}
diff --git a/third_party/protobuf/php/tests/proto/test_reserved_message_upper.proto b/third_party/protobuf/php/tests/proto/test_reserved_message_upper.proto
new file mode 100644
index 0000000..2917fd1
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_reserved_message_upper.proto
@@ -0,0 +1,77 @@
+syntax = "proto3";
+
+package upper;
+
+message ABSTRACT {}
+message AND {}
+message ARRAY {}
+message AS {}
+message BREAK {}
+message CALLABLE {}
+message CASE {}
+message CATCH {}
+message CLASS {}
+message CLONE {}
+message CONST {}
+message CONTINUE {}
+message DECLARE {}
+message DEFAULT {}
+message DIE {}
+message DO {}
+message ECHO {}
+message ELSE {}
+message ELSEIF {}
+message EMPTY {}
+message ENDDECLARE {}
+message ENDFOR {}
+message ENDFOREACH {}
+message ENDIF {}
+message ENDSWITCH {}
+message ENDWHILE {}
+message EVAL {}
+message EXIT {}
+message EXTENDS {}
+message FINAL {}
+message FOR {}
+message FOREACH {}
+message FUNCTION {}
+message GLOBAL {}
+message GOTO {}
+message IF {}
+message IMPLEMENTS {}
+message INCLUDE {}
+message INCLUDE_ONCE {}
+message INSTANCEOF {}
+message INSTEADOF {}
+message INTERFACE {}
+message ISSET {}
+message LIST {}
+message NAMESPACE {}
+message NEW {}
+message OR {}
+message PRINT {}
+message PRIVATE {}
+message PROTECTED {}
+message PUBLIC {}
+message REQUIRE {}
+message REQUIRE_ONCE {}
+message RETURN {}
+message STATIC {}
+message SWITCH {}
+message THROW {}
+message TRAIT {}
+message TRY {}
+message UNSET {}
+message USE {}
+message VAR {}
+message WHILE {}
+message XOR {}
+message INT {}
+message FLOAT {}
+message BOOL {}
+message STRING {}
+message TRUE {}
+message FALSE {}
+message NULL {}
+message VOID {}
+message ITERABLE {}
diff --git a/third_party/protobuf/php/tests/proto/test_service.proto b/third_party/protobuf/php/tests/proto/test_service.proto
new file mode 100644
index 0000000..a03dbc4
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_service.proto
@@ -0,0 +1,18 @@
+syntax = "proto3";
+
+package foo;
+
+option php_generic_services = true;
+
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
+}
+
+message HelloRequest {
+  string name = 1;
+}
+
+message HelloReply {
+  string message = 1;
+}
diff --git a/third_party/protobuf/php/tests/proto/test_service_namespace.proto b/third_party/protobuf/php/tests/proto/test_service_namespace.proto
new file mode 100644
index 0000000..719aa48
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_service_namespace.proto
@@ -0,0 +1,13 @@
+syntax = "proto3";
+
+import "proto/test_service.proto";
+
+package foo;
+
+option php_generic_services = true;
+option php_namespace = "Bar";
+
+service OtherGreeter {
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
+}
diff --git a/third_party/protobuf/php/tests/proto/test_wrapper_type_setters.proto b/third_party/protobuf/php/tests/proto/test_wrapper_type_setters.proto
new file mode 100644
index 0000000..41ca7f3
--- /dev/null
+++ b/third_party/protobuf/php/tests/proto/test_wrapper_type_setters.proto
@@ -0,0 +1,26 @@
+syntax = "proto3";
+
+import "google/protobuf/wrappers.proto";
+
+package foo;
+
+message TestWrapperSetters {
+  google.protobuf.DoubleValue double_value = 1;
+  google.protobuf.FloatValue float_value = 2;
+  google.protobuf.Int64Value int64_value = 3;
+  google.protobuf.UInt64Value uint64_value = 4;
+  google.protobuf.Int32Value int32_value = 5;
+  google.protobuf.UInt32Value uint32_value = 6;
+  google.protobuf.BoolValue bool_value = 7;
+  google.protobuf.StringValue string_value = 8;
+  google.protobuf.BytesValue bytes_value = 9;
+
+  oneof wrapped_oneofs {
+    google.protobuf.DoubleValue double_value_oneof = 10;
+    google.protobuf.StringValue string_value_oneof = 11;
+  }
+
+  repeated google.protobuf.StringValue repeated_string_value = 12;
+
+  map<string, google.protobuf.StringValue> map_string_value = 13;
+}
diff --git a/third_party/protobuf/php/tests/test.sh b/third_party/protobuf/php/tests/test.sh
new file mode 100755
index 0000000..be6e97f
--- /dev/null
+++ b/third_party/protobuf/php/tests/test.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+VERSION=$1
+
+export PATH=/usr/local/php-$VERSION/bin:$PATH
+export C_INCLUDE_PATH=/usr/local/php-$VERSION/include/php/main:/usr/local/php-$VERSION/include/php:$C_INCLUDE_PATH
+export CPLUS_INCLUDE_PATH=/usr/local/php-$VERSION/include/php/main:/usr/local/php-$VERSION/include/php:$CPLUS_INCLUDE_PATH
+
+# Compile c extension
+/bin/bash ./compile_extension.sh ../ext/google/protobuf
+
+tests=( array_test.php encode_decode_test.php generated_class_test.php map_field_test.php well_known_test.php descriptors_test.php wrapper_type_setters_test.php)
+
+for t in "${tests[@]}"
+do
+  echo "****************************"
+  echo "* $t"
+  echo "****************************"
+  php -dextension=../ext/google/protobuf/modules/protobuf.so `which phpunit` --bootstrap autoload.php $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
+
+export ZEND_DONT_UNLOAD_MODULES=1
+export USE_ZEND_ALLOC=0
+valgrind --leak-check=yes php -dextension=../ext/google/protobuf/modules/protobuf.so memory_leak_test.php
+
+# TODO(teboring): Only for debug (phpunit has memory leak which blocks this beging used by
+# regresssion test.)
+
+# for t in "${tests[@]}"
+# do
+#   echo "****************************"
+#   echo "* $t (memory leak)"
+#   echo "****************************"
+#   valgrind --leak-check=yes php -dextension=../ext/google/protobuf/modules/protobuf.so `which phpunit` --bootstrap autoload.php $t
+#   echo ""
+# done
diff --git a/third_party/protobuf/php/tests/test_base.php b/third_party/protobuf/php/tests/test_base.php
new file mode 100644
index 0000000..a4d951b
--- /dev/null
+++ b/third_party/protobuf/php/tests/test_base.php
@@ -0,0 +1,343 @@
+<?php
+
+use Foo\TestMessage;
+use Foo\TestEnum;
+use Foo\TestMessage\Sub;
+
+class TestBase extends \PHPUnit\Framework\TestCase
+{
+
+    public function setFields(TestMessage $m)
+    {
+        TestUtil::setTestMessage($m);
+    }
+
+    public function setFields2(TestMessage $m)
+    {
+        TestUtil::setTestMessage2($m);
+    }
+
+    public function expectFields(TestMessage $m)
+    {
+        $this->assertSame(-42,  $m->getOptionalInt32());
+        $this->assertSame(42,  $m->getOptionalUint32());
+        $this->assertSame(-44,  $m->getOptionalSint32());
+        $this->assertSame(46,   $m->getOptionalFixed32());
+        $this->assertSame(-46,  $m->getOptionalSfixed32());
+        $this->assertSame(1.5,  $m->getOptionalFloat());
+        $this->assertSame(1.6,  $m->getOptionalDouble());
+        $this->assertSame(true, $m->getOptionalBool());
+        $this->assertSame('a',  $m->getOptionalString());
+        $this->assertSame('bbbb',  $m->getOptionalBytes());
+        $this->assertSame(TestEnum::ONE, $m->getOptionalEnum());
+        $this->assertSame(33,   $m->getOptionalMessage()->getA());
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('-43',  $m->getOptionalInt64());
+            $this->assertSame('43',   $m->getOptionalUint64());
+            $this->assertSame('-45',  $m->getOptionalSint64());
+            $this->assertSame('47',   $m->getOptionalFixed64());
+            $this->assertSame('-47',  $m->getOptionalSfixed64());
+        } else {
+            $this->assertSame(-43,  $m->getOptionalInt64());
+            $this->assertSame(43,   $m->getOptionalUint64());
+            $this->assertSame(-45,  $m->getOptionalSint64());
+            $this->assertSame(47,   $m->getOptionalFixed64());
+            $this->assertSame(-47,  $m->getOptionalSfixed64());
+        }
+
+        $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('bbbb',  $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('dddd',   $m->getRepeatedBytes()[1]);
+        $this->assertEquals(35,    $m->getRepeatedMessage()[1]->GetA());
+
+        if (PHP_INT_SIZE == 4) {
+            $this->assertEquals('-63', $m->getMapInt64Int64()['-63']);
+            $this->assertEquals('63',  $m->getMapUint64Uint64()['63']);
+            $this->assertEquals('-65', $m->getMapSint64Sint64()['-65']);
+            $this->assertEquals('67',  $m->getMapFixed64Fixed64()['67']);
+            $this->assertEquals('-69',  $m->getMapSfixed64Sfixed64()['-69']);
+        } else {
+            $this->assertEquals(-63, $m->getMapInt64Int64()[-63]);
+            $this->assertEquals(63,  $m->getMapUint64Uint64()[63]);
+            $this->assertEquals(-65, $m->getMapSint64Sint64()[-65]);
+            $this->assertEquals(67,  $m->getMapFixed64Fixed64()[67]);
+            $this->assertEquals(-69,  $m->getMapSfixed64Sfixed64()[-69]);
+        }
+        $this->assertEquals(-62, $m->getMapInt32Int32()[-62]);
+        $this->assertEquals(62,  $m->getMapUint32Uint32()[62]);
+        $this->assertEquals(-64, $m->getMapSint32Sint32()[-64]);
+        $this->assertEquals(66,  $m->getMapFixed32Fixed32()[66]);
+        $this->assertEquals(-68,  $m->getMapSfixed32Sfixed32()[-68]);
+        $this->assertEquals(3.5, $m->getMapInt32Float()[1]);
+        $this->assertEquals(3.6, $m->getMapInt32Double()[1]);
+        $this->assertEquals(true , $m->getMapBoolBool()[true]);
+        $this->assertEquals('e', $m->getMapStringString()['e']);
+        $this->assertEquals('ffff', $m->getMapInt32Bytes()[1]);
+        $this->assertEquals(TestEnum::ONE, $m->getMapInt32Enum()[1]);
+        $this->assertEquals(36, $m->getMapInt32Message()[1]->GetA());
+    }
+
+    // Test message merged from setFields and setFields2.
+    public function expectFieldsMerged(TestMessage $m)
+    {
+        $this->assertSame(-144,  $m->getOptionalSint32());
+        $this->assertSame(146,   $m->getOptionalFixed32());
+        $this->assertSame(-146,  $m->getOptionalSfixed32());
+        $this->assertSame(11.5,  $m->getOptionalFloat());
+        $this->assertSame(11.6,  $m->getOptionalDouble());
+        $this->assertSame(true, $m->getOptionalBool());
+        $this->assertSame('aa',  $m->getOptionalString());
+        $this->assertSame('bb',  $m->getOptionalBytes());
+        $this->assertSame(133,   $m->getOptionalMessage()->getA());
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame('-143',  $m->getOptionalInt64());
+            $this->assertSame('143',   $m->getOptionalUint64());
+            $this->assertSame('-145',  $m->getOptionalSint64());
+            $this->assertSame('147',   $m->getOptionalFixed64());
+            $this->assertSame('-147',  $m->getOptionalSfixed64());
+        } else {
+            $this->assertSame(-143,  $m->getOptionalInt64());
+            $this->assertSame(143,   $m->getOptionalUint64());
+            $this->assertSame(-145,  $m->getOptionalSint64());
+            $this->assertSame(147,   $m->getOptionalFixed64());
+            $this->assertSame(-147,  $m->getOptionalSfixed64());
+        }
+
+        $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('bbbb',  $m->getRepeatedBytes()[0]);
+        $this->assertEquals(TestEnum::ZERO,  $m->getRepeatedEnum()[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('dddd',   $m->getRepeatedBytes()[1]);
+        $this->assertEquals(TestEnum::ONE,  $m->getRepeatedEnum()[1]);
+        $this->assertEquals(35,    $m->getRepeatedMessage()[1]->GetA());
+
+        $this->assertEquals(-142,  $m->getRepeatedInt32()[2]);
+        $this->assertEquals(142,   $m->getRepeatedUint32()[2]);
+        $this->assertEquals(-143,  $m->getRepeatedInt64()[2]);
+        $this->assertEquals(143,   $m->getRepeatedUint64()[2]);
+        $this->assertEquals(-144,  $m->getRepeatedSint32()[2]);
+        $this->assertEquals(-145,  $m->getRepeatedSint64()[2]);
+        $this->assertEquals(146,   $m->getRepeatedFixed32()[2]);
+        $this->assertEquals(147,   $m->getRepeatedFixed64()[2]);
+        $this->assertEquals(-146,  $m->getRepeatedSfixed32()[2]);
+        $this->assertEquals(-147,  $m->getRepeatedSfixed64()[2]);
+        $this->assertEquals(11.5,  $m->getRepeatedFloat()[2]);
+        $this->assertEquals(11.6,  $m->getRepeatedDouble()[2]);
+        $this->assertEquals(false, $m->getRepeatedBool()[2]);
+        $this->assertEquals('aa',  $m->getRepeatedString()[2]);
+        $this->assertEquals('bb',  $m->getRepeatedBytes()[2]);
+        $this->assertEquals(TestEnum::TWO,  $m->getRepeatedEnum()[2]);
+        $this->assertEquals(134,   $m->getRepeatedMessage()[2]->GetA());
+
+        if (PHP_INT_SIZE == 4) {
+            $this->assertEquals('-163', $m->getMapInt64Int64()['-63']);
+            $this->assertEquals('163',  $m->getMapUint64Uint64()['63']);
+            $this->assertEquals('-165', $m->getMapSint64Sint64()['-65']);
+            $this->assertEquals('167',  $m->getMapFixed64Fixed64()['67']);
+            $this->assertEquals('-169',  $m->getMapSfixed64Sfixed64()['-69']);
+        } else {
+            $this->assertEquals(-163, $m->getMapInt64Int64()[-63]);
+            $this->assertEquals(163,  $m->getMapUint64Uint64()[63]);
+            $this->assertEquals(-165, $m->getMapSint64Sint64()[-65]);
+            $this->assertEquals(167,  $m->getMapFixed64Fixed64()[67]);
+            $this->assertEquals(-169,  $m->getMapSfixed64Sfixed64()[-69]);
+        }
+        $this->assertEquals(-162, $m->getMapInt32Int32()[-62]);
+        $this->assertEquals(162,  $m->getMapUint32Uint32()[62]);
+        $this->assertEquals(-164, $m->getMapSint32Sint32()[-64]);
+        $this->assertEquals(166,  $m->getMapFixed32Fixed32()[66]);
+        $this->assertEquals(-168,  $m->getMapSfixed32Sfixed32()[-68]);
+        $this->assertEquals(13.5, $m->getMapInt32Float()[1]);
+        $this->assertEquals(13.6, $m->getMapInt32Double()[1]);
+        $this->assertEquals(false , $m->getMapBoolBool()[true]);
+        $this->assertEquals('ee', $m->getMapStringString()['e']);
+        $this->assertEquals('ff', $m->getMapInt32Bytes()[1]);
+        $this->assertEquals(TestEnum::TWO, $m->getMapInt32Enum()[1]);
+        $this->assertEquals(136, $m->getMapInt32Message()[1]->GetA());
+
+        if (PHP_INT_SIZE == 4) {
+            $this->assertEquals('-163', $m->getMapInt64Int64()['-163']);
+            $this->assertEquals('163',  $m->getMapUint64Uint64()['163']);
+            $this->assertEquals('-165', $m->getMapSint64Sint64()['-165']);
+            $this->assertEquals('167',  $m->getMapFixed64Fixed64()['167']);
+            $this->assertEquals('-169',  $m->getMapSfixed64Sfixed64()['-169']);
+        } else {
+            $this->assertEquals(-163, $m->getMapInt64Int64()[-163]);
+            $this->assertEquals(163,  $m->getMapUint64Uint64()[163]);
+            $this->assertEquals(-165, $m->getMapSint64Sint64()[-165]);
+            $this->assertEquals(167,  $m->getMapFixed64Fixed64()[167]);
+            $this->assertEquals(-169,  $m->getMapSfixed64Sfixed64()[-169]);
+        }
+        $this->assertEquals(-162, $m->getMapInt32Int32()[-162]);
+        $this->assertEquals(162,  $m->getMapUint32Uint32()[162]);
+        $this->assertEquals(-164, $m->getMapSint32Sint32()[-164]);
+        $this->assertEquals(166,  $m->getMapFixed32Fixed32()[166]);
+        $this->assertEquals(-168,  $m->getMapSfixed32Sfixed32()[-168]);
+        $this->assertEquals(13.5, $m->getMapInt32Float()[2]);
+        $this->assertEquals(13.6, $m->getMapInt32Double()[2]);
+        $this->assertEquals(false , $m->getMapBoolBool()[false]);
+        $this->assertEquals('ee', $m->getMapStringString()['ee']);
+        $this->assertEquals('ff', $m->getMapInt32Bytes()[2]);
+        $this->assertEquals(TestEnum::TWO, $m->getMapInt32Enum()[2]);
+        $this->assertEquals(136, $m->getMapInt32Message()[2]->GetA());
+    }
+
+    public function expectEmptyFields(TestMessage $m)
+    {
+        $this->assertSame(0,   $m->getOptionalInt32());
+        $this->assertSame(0,   $m->getOptionalUint32());
+        $this->assertSame(0,   $m->getOptionalSint32());
+        $this->assertSame(0,   $m->getOptionalFixed32());
+        $this->assertSame(0,   $m->getOptionalSfixed32());
+        $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->assertSame(0, $m->getOptionalEnum());
+        $this->assertNull($m->getOptionalMessage());
+        $this->assertNull($m->getOptionalIncludedMessage());
+        $this->assertNull($m->getRecursive());
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame("0", $m->getOptionalInt64());
+            $this->assertSame("0", $m->getOptionalUint64());
+            $this->assertSame("0", $m->getOptionalSint64());
+            $this->assertSame("0", $m->getOptionalFixed64());
+            $this->assertSame("0", $m->getOptionalSfixed64());
+        } else {
+            $this->assertSame(0, $m->getOptionalInt64());
+            $this->assertSame(0, $m->getOptionalUint64());
+            $this->assertSame(0, $m->getOptionalSint64());
+            $this->assertSame(0, $m->getOptionalFixed64());
+            $this->assertSame(0, $m->getOptionalSfixed64());
+        }
+
+        $this->assertEquals(0, count($m->getRepeatedInt32()));
+        $this->assertEquals(0, count($m->getRepeatedUint32()));
+        $this->assertEquals(0, count($m->getRepeatedInt64()));
+        $this->assertEquals(0, count($m->getRepeatedUint64()));
+        $this->assertEquals(0, count($m->getRepeatedSint32()));
+        $this->assertEquals(0, count($m->getRepeatedSint64()));
+        $this->assertEquals(0, count($m->getRepeatedFixed32()));
+        $this->assertEquals(0, count($m->getRepeatedFixed64()));
+        $this->assertEquals(0, count($m->getRepeatedSfixed32()));
+        $this->assertEquals(0, count($m->getRepeatedSfixed64()));
+        $this->assertEquals(0, count($m->getRepeatedFloat()));
+        $this->assertEquals(0, count($m->getRepeatedDouble()));
+        $this->assertEquals(0, count($m->getRepeatedBool()));
+        $this->assertEquals(0, count($m->getRepeatedString()));
+        $this->assertEquals(0, count($m->getRepeatedBytes()));
+        $this->assertEquals(0, count($m->getRepeatedEnum()));
+        $this->assertEquals(0, count($m->getRepeatedMessage()));
+        $this->assertEquals(0, count($m->getRepeatedRecursive()));
+
+        $this->assertSame("", $m->getMyOneof());
+        $this->assertSame(0,   $m->getOneofInt32());
+        $this->assertSame(0,   $m->getOneofUint32());
+        $this->assertSame(0,   $m->getOneofSint32());
+        $this->assertSame(0,   $m->getOneofFixed32());
+        $this->assertSame(0,   $m->getOneofSfixed32());
+        $this->assertSame(0.0, $m->getOneofFloat());
+        $this->assertSame(0.0, $m->getOneofDouble());
+        $this->assertSame(false, $m->getOneofBool());
+        $this->assertSame('',  $m->getOneofString());
+        $this->assertSame('',  $m->getOneofBytes());
+        $this->assertSame(0, $m->getOneofEnum());
+        $this->assertNull($m->getOptionalMessage());
+        if (PHP_INT_SIZE == 4) {
+            $this->assertSame("0", $m->getOneofInt64());
+            $this->assertSame("0", $m->getOneofUint64());
+            $this->assertSame("0", $m->getOneofSint64());
+            $this->assertSame("0", $m->getOneofFixed64());
+            $this->assertSame("0", $m->getOneofSfixed64());
+        } else {
+            $this->assertSame(0, $m->getOneofInt64());
+            $this->assertSame(0, $m->getOneofUint64());
+            $this->assertSame(0, $m->getOneofSint64());
+            $this->assertSame(0, $m->getOneofFixed64());
+            $this->assertSame(0, $m->getOneofSfixed64());
+        }
+
+        $this->assertEquals(0, count($m->getMapInt64Int64()));
+        $this->assertEquals(0, count($m->getMapUint64Uint64()));
+        $this->assertEquals(0, count($m->getMapSint64Sint64()));
+        $this->assertEquals(0, count($m->getMapFixed64Fixed64()));
+        $this->assertEquals(0, count($m->getMapInt32Int32()));
+        $this->assertEquals(0, count($m->getMapUint32Uint32()));
+        $this->assertEquals(0, count($m->getMapSint32Sint32()));
+        $this->assertEquals(0, count($m->getMapFixed32Fixed32()));
+        $this->assertEquals(0, count($m->getMapSfixed32Sfixed32()));
+        $this->assertEquals(0, count($m->getMapSfixed64Sfixed64()));
+        $this->assertEquals(0, count($m->getMapInt32Float()));
+        $this->assertEquals(0, count($m->getMapInt32Double()));
+        $this->assertEquals(0, count($m->getMapBoolBool()));
+        $this->assertEquals(0, count($m->getMapStringString()));
+        $this->assertEquals(0, count($m->getMapInt32Bytes()));
+        $this->assertEquals(0, count($m->getMapInt32Enum()));
+        $this->assertEquals(0, count($m->getMapInt32Message()));
+        $this->assertEquals(0, count($m->getMapRecursive()));
+    }
+
+  // This test is to avoid the warning of no test by php unit.
+  public function testNone()
+  {
+      $this->assertTrue(true);
+  }
+}
diff --git a/third_party/protobuf/php/tests/test_util.php b/third_party/protobuf/php/tests/test_util.php
new file mode 100644
index 0000000..2c5b595
--- /dev/null
+++ b/third_party/protobuf/php/tests/test_util.php
@@ -0,0 +1,547 @@
+<?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', (int)-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', (int)-2147483648);
+define('MIN_UINT32_FLOAT', -2147483648.0);
+define('MIN_UINT32_STRING', '-2147483648');
+
+define('MAX_INT64_STRING',  '9223372036854775807');
+define('MIN_INT64_STRING',  '-9223372036854775808');
+define('MAX_UINT64_STRING', '-9223372036854775808');
+
+if (PHP_INT_SIZE === 8) {
+    define('MAX_INT64',  (int)9223372036854775807);
+    define('MIN_INT64',  (int)-9223372036854775808);
+    define('MAX_UINT64', (int)-9223372036854775808);
+} else {
+    define('MAX_INT64', MAX_INT64_STRING);
+    define('MIN_INT64', MIN_INT64_STRING);
+    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('bbbb');
+        $m->setOptionalEnum(TestEnum::ONE);
+        $sub = new Sub();
+        $m->setOptionalMessage($sub);
+        $m->getOptionalMessage()->SetA(33);
+
+        self::appendHelper($m, 'RepeatedInt32',    -42);
+        self::appendHelper($m, 'RepeatedInt64',    -43);
+        self::appendHelper($m, 'RepeatedUint32',    42);
+        self::appendHelper($m, 'RepeatedUint64',    43);
+        self::appendHelper($m, 'RepeatedSint32',   -44);
+        self::appendHelper($m, 'RepeatedSint64',   -45);
+        self::appendHelper($m, 'RepeatedFixed32',   46);
+        self::appendHelper($m, 'RepeatedFixed64',   47);
+        self::appendHelper($m, 'RepeatedSfixed32', -46);
+        self::appendHelper($m, 'RepeatedSfixed64', -47);
+        self::appendHelper($m, 'RepeatedFloat',    1.5);
+        self::appendHelper($m, 'RepeatedDouble',   1.6);
+        self::appendHelper($m, 'RepeatedBool',     true);
+        self::appendHelper($m, 'RepeatedString',   'a');
+        self::appendHelper($m, 'RepeatedBytes',    'bbbb');
+        self::appendHelper($m, 'RepeatedEnum',     TestEnum::ZERO);
+        self::appendHelper($m, 'RepeatedMessage',  new Sub());
+        $m->getRepeatedMessage()[0]->setA(34);
+
+        self::appendHelper($m, 'RepeatedInt32',    -52);
+        self::appendHelper($m, 'RepeatedInt64',    -53);
+        self::appendHelper($m, 'RepeatedUint32',    52);
+        self::appendHelper($m, 'RepeatedUint64',    53);
+        self::appendHelper($m, 'RepeatedSint32',   -54);
+        self::appendHelper($m, 'RepeatedSint64',   -55);
+        self::appendHelper($m, 'RepeatedFixed32',   56);
+        self::appendHelper($m, 'RepeatedFixed64',   57);
+        self::appendHelper($m, 'RepeatedSfixed32', -56);
+        self::appendHelper($m, 'RepeatedSfixed64', -57);
+        self::appendHelper($m, 'RepeatedFloat',    2.5);
+        self::appendHelper($m, 'RepeatedDouble',   2.6);
+        self::appendHelper($m, 'RepeatedBool',     false);
+        self::appendHelper($m, 'RepeatedString',   'c');
+        self::appendHelper($m, 'RepeatedBytes',    'dddd');
+        self::appendHelper($m, 'RepeatedEnum',     TestEnum::ONE);
+        self::appendHelper($m, 'RepeatedMessage',  new Sub());
+        $m->getRepeatedMessage()[1]->SetA(35);
+
+        self::kvUpdateHelper($m, 'MapInt32Int32', -62, -62);
+        self::kvUpdateHelper($m, 'MapInt64Int64', -63, -63);
+        self::kvUpdateHelper($m, 'MapUint32Uint32', 62, 62);
+        self::kvUpdateHelper($m, 'MapUint64Uint64', 63, 63);
+        self::kvUpdateHelper($m, 'MapSint32Sint32', -64, -64);
+        self::kvUpdateHelper($m, 'MapSint64Sint64', -65, -65);
+        self::kvUpdateHelper($m, 'MapFixed32Fixed32', 66, 66);
+        self::kvUpdateHelper($m, 'MapFixed64Fixed64', 67, 67);
+        self::kvUpdateHelper($m, 'MapSfixed32Sfixed32', -68, -68);
+        self::kvUpdateHelper($m, 'MapSfixed64Sfixed64', -69, -69);
+        self::kvUpdateHelper($m, 'MapInt32Float', 1, 3.5);
+        self::kvUpdateHelper($m, 'MapInt32Double', 1, 3.6);
+        self::kvUpdateHelper($m, 'MapBoolBool', true, true);
+        self::kvUpdateHelper($m, 'MapStringString', 'e', 'e');
+        self::kvUpdateHelper($m, 'MapInt32Bytes', 1, 'ffff');
+        self::kvUpdateHelper($m, 'MapInt32Enum', 1, TestEnum::ONE);
+        self::kvUpdateHelper($m, 'MapInt32Message', 1, new Sub());
+        $m->getMapInt32Message()[1]->SetA(36);
+    }
+
+    public static function setTestMessage2(TestMessage $m)
+    {
+        $sub = new Sub();
+
+        $m->setOptionalInt32(-142);
+        $m->setOptionalInt64(-143);
+        $m->setOptionalUint32(142);
+        $m->setOptionalUint64(143);
+        $m->setOptionalSint32(-144);
+        $m->setOptionalSint64(-145);
+        $m->setOptionalFixed32(146);
+        $m->setOptionalFixed64(147);
+        $m->setOptionalSfixed32(-146);
+        $m->setOptionalSfixed64(-147);
+        $m->setOptionalFloat(11.5);
+        $m->setOptionalDouble(11.6);
+        $m->setOptionalBool(true);
+        $m->setOptionalString('aa');
+        $m->setOptionalBytes('bb');
+        $m->setOptionalEnum(TestEnum::TWO);
+        $m->setOptionalMessage($sub);
+        $m->getOptionalMessage()->SetA(133);
+
+        self::appendHelper($m, 'RepeatedInt32',    -142);
+        self::appendHelper($m, 'RepeatedInt64',    -143);
+        self::appendHelper($m, 'RepeatedUint32',    142);
+        self::appendHelper($m, 'RepeatedUint64',    143);
+        self::appendHelper($m, 'RepeatedSint32',   -144);
+        self::appendHelper($m, 'RepeatedSint64',   -145);
+        self::appendHelper($m, 'RepeatedFixed32',   146);
+        self::appendHelper($m, 'RepeatedFixed64',   147);
+        self::appendHelper($m, 'RepeatedSfixed32', -146);
+        self::appendHelper($m, 'RepeatedSfixed64', -147);
+        self::appendHelper($m, 'RepeatedFloat',    11.5);
+        self::appendHelper($m, 'RepeatedDouble',   11.6);
+        self::appendHelper($m, 'RepeatedBool',     false);
+        self::appendHelper($m, 'RepeatedString',   'aa');
+        self::appendHelper($m, 'RepeatedBytes',    'bb');
+        self::appendHelper($m, 'RepeatedEnum',     TestEnum::TWO);
+        self::appendHelper($m, 'RepeatedMessage',  new Sub());
+        $m->getRepeatedMessage()[0]->setA(134);
+
+        self::kvUpdateHelper($m, 'MapInt32Int32', -62, -162);
+        self::kvUpdateHelper($m, 'MapInt64Int64', -63, -163);
+        self::kvUpdateHelper($m, 'MapUint32Uint32', 62, 162);
+        self::kvUpdateHelper($m, 'MapUint64Uint64', 63, 163);
+        self::kvUpdateHelper($m, 'MapSint32Sint32', -64, -164);
+        self::kvUpdateHelper($m, 'MapSint64Sint64', -65, -165);
+        self::kvUpdateHelper($m, 'MapFixed32Fixed32', 66, 166);
+        self::kvUpdateHelper($m, 'MapFixed64Fixed64', 67, 167);
+        self::kvUpdateHelper($m, 'MapSfixed32Sfixed32', -68, -168);
+        self::kvUpdateHelper($m, 'MapSfixed64Sfixed64', -69, -169);
+        self::kvUpdateHelper($m, 'MapInt32Float', 1, 13.5);
+        self::kvUpdateHelper($m, 'MapInt32Double', 1, 13.6);
+        self::kvUpdateHelper($m, 'MapBoolBool', true, false);
+        self::kvUpdateHelper($m, 'MapStringString', 'e', 'ee');
+        self::kvUpdateHelper($m, 'MapInt32Bytes', 1, 'ff');
+        self::kvUpdateHelper($m, 'MapInt32Enum', 1, TestEnum::TWO);
+        self::kvUpdateHelper($m, 'MapInt32Message', 1, new Sub());
+        $m->getMapInt32Message()[1]->SetA(136);
+
+        self::kvUpdateHelper($m, 'MapInt32Int32', -162, -162);
+        self::kvUpdateHelper($m, 'MapInt64Int64', -163, -163);
+        self::kvUpdateHelper($m, 'MapUint32Uint32', 162, 162);
+        self::kvUpdateHelper($m, 'MapUint64Uint64', 163, 163);
+        self::kvUpdateHelper($m, 'MapSint32Sint32', -164, -164);
+        self::kvUpdateHelper($m, 'MapSint64Sint64', -165, -165);
+        self::kvUpdateHelper($m, 'MapFixed32Fixed32', 166, 166);
+        self::kvUpdateHelper($m, 'MapFixed64Fixed64', 167, 167);
+        self::kvUpdateHelper($m, 'MapSfixed32Sfixed32', -168, -168);
+        self::kvUpdateHelper($m, 'MapSfixed64Sfixed64', -169, -169);
+        self::kvUpdateHelper($m, 'MapInt32Float', 2, 13.5);
+        self::kvUpdateHelper($m, 'MapInt32Double', 2, 13.6);
+        self::kvUpdateHelper($m, 'MapBoolBool', false, false);
+        self::kvUpdateHelper($m, 'MapStringString', 'ee', 'ee');
+        self::kvUpdateHelper($m, 'MapInt32Bytes', 2, 'ff');
+        self::kvUpdateHelper($m, 'MapInt32Enum', 2, TestEnum::TWO);
+        self::kvUpdateHelper($m, 'MapInt32Message', 2, new Sub());
+        $m->getMapInt32Message()[2]->SetA(136);
+    }
+
+    public static function assertTestMessage(TestMessage $m)
+    {
+        if (PHP_INT_SIZE == 4) {
+            assert('-43' === $m->getOptionalInt64());
+            assert('43'  === $m->getOptionalUint64());
+            assert('-45' === $m->getOptionalSint64());
+            assert('47'  === $m->getOptionalFixed64());
+            assert('-47' === $m->getOptionalSfixed64());
+        } else {
+            assert(-43 === $m->getOptionalInt64());
+            assert(43  === $m->getOptionalUint64());
+            assert(-45 === $m->getOptionalSint64());
+            assert(47  === $m->getOptionalFixed64());
+            assert(-47 === $m->getOptionalSfixed64());
+        }
+        assert(-42 === $m->getOptionalInt32());
+        assert(42  === $m->getOptionalUint32());
+        assert(-44 === $m->getOptionalSint32());
+        assert(46  === $m->getOptionalFixed32());
+        assert(-46 === $m->getOptionalSfixed32());
+        assert(1.5 === $m->getOptionalFloat());
+        assert(1.6 === $m->getOptionalDouble());
+        assert(true=== $m->getOptionalBool());
+        assert('a' === $m->getOptionalString());
+        assert('bbbb' === $m->getOptionalBytes());
+        assert(TestEnum::ONE === $m->getOptionalEnum());
+        assert(33  === $m->getOptionalMessage()->getA());
+
+        if (PHP_INT_SIZE == 4) {
+            assert('-43' === $m->getRepeatedInt64()[0]);
+            assert('43'  === $m->getRepeatedUint64()[0]);
+            assert('-45' === $m->getRepeatedSint64()[0]);
+            assert('47'  === $m->getRepeatedFixed64()[0]);
+            assert('-47' === $m->getRepeatedSfixed64()[0]);
+        } else {
+            assert(-43 === $m->getRepeatedInt64()[0]);
+            assert(43  === $m->getRepeatedUint64()[0]);
+            assert(-45 === $m->getRepeatedSint64()[0]);
+            assert(47  === $m->getRepeatedFixed64()[0]);
+            assert(-47 === $m->getRepeatedSfixed64()[0]);
+        }
+        assert(-42 === $m->getRepeatedInt32()[0]);
+        assert(42  === $m->getRepeatedUint32()[0]);
+        assert(-44 === $m->getRepeatedSint32()[0]);
+        assert(46  === $m->getRepeatedFixed32()[0]);
+        assert(-46 === $m->getRepeatedSfixed32()[0]);
+        assert(1.5 === $m->getRepeatedFloat()[0]);
+        assert(1.6 === $m->getRepeatedDouble()[0]);
+        assert(true=== $m->getRepeatedBool()[0]);
+        assert('a' === $m->getRepeatedString()[0]);
+        assert('bbbb' === $m->getRepeatedBytes()[0]);
+        assert(TestEnum::ZERO === $m->getRepeatedEnum()[0]);
+        assert(34  === $m->getRepeatedMessage()[0]->getA());
+
+        if (PHP_INT_SIZE == 4) {
+            assert('-53' === $m->getRepeatedInt64()[1]);
+            assert('53'  === $m->getRepeatedUint64()[1]);
+            assert('-55' === $m->getRepeatedSint64()[1]);
+            assert('57'  === $m->getRepeatedFixed64()[1]);
+            assert('-57' === $m->getRepeatedSfixed64()[1]);
+        } else {
+            assert(-53 === $m->getRepeatedInt64()[1]);
+            assert(53  === $m->getRepeatedUint64()[1]);
+            assert(-55 === $m->getRepeatedSint64()[1]);
+            assert(57  === $m->getRepeatedFixed64()[1]);
+            assert(-57 === $m->getRepeatedSfixed64()[1]);
+        }
+        assert(-52 === $m->getRepeatedInt32()[1]);
+        assert(52  === $m->getRepeatedUint32()[1]);
+        assert(-54 === $m->getRepeatedSint32()[1]);
+        assert(56  === $m->getRepeatedFixed32()[1]);
+        assert(-56 === $m->getRepeatedSfixed32()[1]);
+        assert(2.5 === $m->getRepeatedFloat()[1]);
+        assert(2.6 === $m->getRepeatedDouble()[1]);
+        assert(false === $m->getRepeatedBool()[1]);
+        assert('c' === $m->getRepeatedString()[1]);
+        assert('dddd' === $m->getRepeatedBytes()[1]);
+        assert(TestEnum::ONE === $m->getRepeatedEnum()[1]);
+        assert(35  === $m->getRepeatedMessage()[1]->getA());
+
+        if (PHP_INT_SIZE == 4) {
+            assert('-63' === $m->getMapInt64Int64()['-63']);
+            assert('63'  === $m->getMapUint64Uint64()['63']);
+            assert('-65' === $m->getMapSint64Sint64()['-65']);
+            assert('67'  === $m->getMapFixed64Fixed64()['67']);
+            assert('-69'  === $m->getMapSfixed64Sfixed64()['-69']);
+        } else {
+            assert(-63 === $m->getMapInt64Int64()[-63]);
+            assert(63  === $m->getMapUint64Uint64()[63]);
+            assert(-65 === $m->getMapSint64Sint64()[-65]);
+            assert(67  === $m->getMapFixed64Fixed64()[67]);
+            assert(-69  === $m->getMapSfixed64Sfixed64()[-69]);
+        }
+        assert(-62 === $m->getMapInt32Int32()[-62]);
+        assert(62  === $m->getMapUint32Uint32()[62]);
+        assert(-64 === $m->getMapSint32Sint32()[-64]);
+        assert(66  === $m->getMapFixed32Fixed32()[66]);
+        assert(-68  === $m->getMapSfixed32Sfixed32()[-68]);
+        assert(3.5 === $m->getMapInt32Float()[1]);
+        assert(3.6 === $m->getMapInt32Double()[1]);
+        assert(true === $m->getMapBoolBool()[true]);
+        assert('e' === $m->getMapStringString()['e']);
+        assert('ffff' === $m->getMapInt32Bytes()[1]);
+        assert(TestEnum::ONE === $m->getMapInt32Enum()[1]);
+        assert(36  === $m->getMapInt32Message()[1]->GetA());
+    }
+
+    public static function getGoldenTestMessage()
+    {
+        return hex2bin(
+            "08D6FFFFFFFFFFFFFFFF01" .
+            "10D5FFFFFFFFFFFFFFFF01" .
+            "182A" .
+            "202B" .
+            "2857" .
+            "3059" .
+            "3D2E000000" .
+            "412F00000000000000" .
+            "4DD2FFFFFF" .
+            "51D1FFFFFFFFFFFFFF" .
+            "5D0000C03F" .
+            "619A9999999999F93F" .
+            "6801" .
+            "720161" .
+            "7A0462626262" .
+            "800101" .
+            "8A01020821" .
+
+            "F801D6FFFFFFFFFFFFFFFF01" .
+            "F801CCFFFFFFFFFFFFFFFF01" .
+            "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" .
+            "EA020462626262" .
+            "EA020464646464" .
+            "F00200" .
+            "F00201" .
+            "FA02020822" .
+            "FA02020823" .
+
+            "BA041608C2FFFFFFFFFFFFFFFF0110C2FFFFFFFFFFFFFFFF01" .
+            "C2041608C1FFFFFFFFFFFFFFFF0110C1FFFFFFFFFFFFFFFF01" .
+            "CA0404083E103E" .
+            "D20404083F103F" .
+            "DA0404087f107F" .
+            "E20406088101108101" .
+            "EA040A0D420000001542000000" .
+            "F20412094300000000000000114300000000000000" .
+            "FA040A0DBCFFFFFF15BCFFFFFF" .
+            "82051209BBFFFFFFFFFFFFFF11BBFFFFFFFFFFFFFF" .
+            "8A050708011500006040" .
+            "92050B080111CDCCCCCCCCCC0C40" .
+            "9A050408011001" .
+            "A205060a0165120165" .
+            "AA05080801120466666666" .
+            "B2050408011001" .
+            "Ba0506080112020824"
+        );
+    }
+
+    public static function setTestPackedMessage($m)
+    {
+        self::appendHelper($m, 'RepeatedInt32', -42);
+        self::appendHelper($m, 'RepeatedInt32', -52);
+        self::appendHelper($m, 'RepeatedInt64', -43);
+        self::appendHelper($m, 'RepeatedInt64', -53);
+        self::appendHelper($m, 'RepeatedUint32', 42);
+        self::appendHelper($m, 'RepeatedUint32', 52);
+        self::appendHelper($m, 'RepeatedUint64', 43);
+        self::appendHelper($m, 'RepeatedUint64', 53);
+        self::appendHelper($m, 'RepeatedSint32', -44);
+        self::appendHelper($m, 'RepeatedSint32', -54);
+        self::appendHelper($m, 'RepeatedSint64', -45);
+        self::appendHelper($m, 'RepeatedSint64', -55);
+        self::appendHelper($m, 'RepeatedFixed32', 46);
+        self::appendHelper($m, 'RepeatedFixed32', 56);
+        self::appendHelper($m, 'RepeatedFixed64', 47);
+        self::appendHelper($m, 'RepeatedFixed64', 57);
+        self::appendHelper($m, 'RepeatedSfixed32', -46);
+        self::appendHelper($m, 'RepeatedSfixed32', -56);
+        self::appendHelper($m, 'RepeatedSfixed64', -47);
+        self::appendHelper($m, 'RepeatedSfixed64', -57);
+        self::appendHelper($m, 'RepeatedFloat', 1.5);
+        self::appendHelper($m, 'RepeatedFloat', 2.5);
+        self::appendHelper($m, 'RepeatedDouble', 1.6);
+        self::appendHelper($m, 'RepeatedDouble', 2.6);
+        self::appendHelper($m, 'RepeatedBool', true);
+        self::appendHelper($m, 'RepeatedBool', false);
+        self::appendHelper($m, 'RepeatedEnum', TestEnum::ONE);
+        self::appendHelper($m, 'RepeatedEnum', 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(42  === $m->getRepeatedUint32()[0]);
+        assert(52  === $m->getRepeatedUint32()[1]);
+        assert(-44 === $m->getRepeatedSint32()[0]);
+        assert(-54 === $m->getRepeatedSint32()[1]);
+        assert(46  === $m->getRepeatedFixed32()[0]);
+        assert(56  === $m->getRepeatedFixed32()[1]);
+        assert(-46 === $m->getRepeatedSfixed32()[0]);
+        assert(-56 === $m->getRepeatedSfixed32()[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]);
+        if (PHP_INT_SIZE == 4) {
+            assert('-43' === $m->getRepeatedInt64()[0]);
+            assert('-53' === $m->getRepeatedInt64()[1]);
+            assert('43'  === $m->getRepeatedUint64()[0]);
+            assert('53'  === $m->getRepeatedUint64()[1]);
+            assert('-45' === $m->getRepeatedSint64()[0]);
+            assert('-55' === $m->getRepeatedSint64()[1]);
+            assert('47'  === $m->getRepeatedFixed64()[0]);
+            assert('57'  === $m->getRepeatedFixed64()[1]);
+            assert('-47' === $m->getRepeatedSfixed64()[0]);
+            assert('-57' === $m->getRepeatedSfixed64()[1]);
+        } else {
+            assert(-43 === $m->getRepeatedInt64()[0]);
+            assert(-53 === $m->getRepeatedInt64()[1]);
+            assert(43  === $m->getRepeatedUint64()[0]);
+            assert(53  === $m->getRepeatedUint64()[1]);
+            assert(-45 === $m->getRepeatedSint64()[0]);
+            assert(-55 === $m->getRepeatedSint64()[1]);
+            assert(47  === $m->getRepeatedFixed64()[0]);
+            assert(57  === $m->getRepeatedFixed64()[1]);
+            assert(-47 === $m->getRepeatedSfixed64()[0]);
+            assert(-57 === $m->getRepeatedSfixed64()[1]);
+        }
+    }
+
+    public static function getGoldenTestPackedMessage()
+    {
+        return hex2bin(
+            "D20514D6FFFFFFFFFFFFFFFF01CCFFFFFFFFFFFFFFFF01" .
+            "DA0514D5FFFFFFFFFFFFFFFF01CBFFFFFFFFFFFFFFFF01" .
+            "E205022A34" .
+            "EA05022B35" .
+            "F20502576B" .
+            "FA0502596D" .
+            "8206082E00000038000000" .
+            "8A06102F000000000000003900000000000000" .
+            "920608D2FFFFFFC8FFFFFF" .
+            "9A0610D1FFFFFFFFFFFFFFC7FFFFFFFFFFFFFF" .
+            "A206080000C03F00002040" .
+            "AA06109A9999999999F93FCDCCCCCCCCCC0440" .
+            "B206020100" .
+            "BA06020100"
+        );
+    }
+
+    public static function getGoldenTestUnpackedMessage()
+    {
+        return hex2bin(
+            "D005D6FFFFFFFFFFFFFFFF01D005CCFFFFFFFFFFFFFFFF01" .
+            "D805D5FFFFFFFFFFFFFFFF01D805CBFFFFFFFFFFFFFFFF01" .
+            "E0052AE00534" .
+            "E8052BE80535" .
+            "F00557F0056B" .
+            "F80559F8056D" .
+            "85062E000000850638000000" .
+            "89062F0000000000000089063900000000000000" .
+            "9506D2FFFFFF9506C8FFFFFF" .
+            "9906D1FFFFFFFFFFFFFF9906C7FFFFFFFFFFFFFF" .
+            "A5060000C03FA50600002040" .
+            "A9069A9999999999F93FA906CDCCCCCCCCCC0440" .
+            "B00601B00600" .
+            "B80601B80600"
+        );
+    }
+
+    private static function appendHelper($obj, $func_suffix, $value)
+    {
+        $getter_function = 'get'.$func_suffix;
+        $setter_function = 'set'.$func_suffix;
+
+        $arr = $obj->$getter_function();
+        $arr[] = $value;
+        $obj->$setter_function($arr);
+    }
+
+    private static function kvUpdateHelper($obj, $func_suffix, $key, $value)
+    {
+        $getter_function = 'get'.$func_suffix;
+        $setter_function = 'set'.$func_suffix;
+
+        $arr = $obj->$getter_function();
+        $arr[$key] = $value;
+        $obj->$setter_function($arr);
+    }
+}
diff --git a/third_party/protobuf/php/tests/undefined_test.php b/third_party/protobuf/php/tests/undefined_test.php
new file mode 100644
index 0000000..935d8be
--- /dev/null
+++ b/third_party/protobuf/php/tests/undefined_test.php
@@ -0,0 +1,920 @@
+<?php
+
+require_once('test_util.php');
+
+use Google\Protobuf\Internal\RepeatedField;
+use Google\Protobuf\Internal\GPBType;
+use Foo\TestMessage;
+use Foo\TestMessage\Sub;
+
+class UndefinedTest extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * @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 Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32SetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr[] = 0;
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32SetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT32);
+        $arr[] = 0;
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64SetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::INT64);
+        $arr[] = 0;
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64SetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::UINT64);
+        $arr[] = 0;
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testFloatSetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::FLOAT);
+        $arr[] = 0.0;
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testDoubleSetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::DOUBLE);
+        $arr[] = 0.0;
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolAppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::BOOL);
+        $arr[] = new Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolSetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::BOOL);
+        $arr[] = true;
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringAppendMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::STRING);
+        $arr[] = new Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringSetMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::STRING);
+        $arr[] = 'abc';
+        $arr[0] = new 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;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageAppendIntFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+        $arr[] = 1;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetIntFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+        $arr[] = new Sub;
+        $arr[0] = 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageAppendStringFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+        $arr[] = 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetStringFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+        $arr[] = new Sub;
+        $arr[0] = 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageAppendOtherMessageFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+        $arr[] = new TestMessage;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageAppendNullFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+        $null = null;
+        $arr[] = $null;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetNullFail()
+    {
+        $arr = new RepeatedField(GPBType::MESSAGE, Sub::class);
+        $arr[] = new Sub();
+        $null = null;
+        $arr[0] = $null;
+    }
+
+    /**
+     * @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 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;
+    }
+
+    /**
+     * @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');
+    }
+
+    /**
+     * @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');
+    }
+
+    /**
+     * @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');
+    }
+
+    /**
+     * @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');
+    }
+
+    /**
+     * @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');
+    }
+
+    /**
+     * @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');
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolFieldInvalidStringFail()
+    {
+        $m = new TestMessage();
+        $m->setOptionalBool(new TestMessage());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringFieldInvalidUTF8Fail()
+    {
+        $m = new TestMessage();
+        $hex = hex2bin("ff");
+        $m->setOptionalString($hex);
+    }
+
+    /**
+     * @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());
+    }
+
+    /**
+     * @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);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMapFieldWrongTypeFail()
+    {
+        $m = new TestMessage();
+        $a = 1;
+        $m->setMapInt32Int32($a);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMapFieldWrongObjectFail()
+    {
+        $m = new TestMessage();
+        $m->setMapInt32Int32($m);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMapFieldWrongRepeatedTypeFail()
+    {
+        $m = new TestMessage();
+
+        $map_uint32_uint32 = new MapField(GPBType::UINT32, GPBType::UINT32);
+        $m->setMapInt32Int32($map_uint32_uint32);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMapFieldWrongRepeatedMessageClassFail()
+    {
+        $m = new TestMessage();
+
+        $map_int32_message = new MapField(GPBType::INT32,
+                                          GPBType::MESSAGE,
+                                          TestMessage::class);
+        $m->setMapInt32Message($map_int32_message);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageMergeFromInvalidTypeFail()
+    {
+        $m = new TestMessage();
+        $n = new Sub();
+        $m->mergeFrom($n);
+    }
+
+    /**
+     * @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 Sub()] = 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt32SetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::INT32, GPBType::INT32);
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub()] = 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint32SetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::UINT32, GPBType::UINT32);
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub()] = 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInt64SetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::INT64, GPBType::INT64);
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub()] = 0;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testUint64SetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::UINT64, GPBType::UINT64);
+        $arr[0] = new Sub();
+    }
+
+    /**
+     * @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 Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolSetMessageKeyFail()
+    {
+        $arr = new MapField(GPBType::BOOL, GPBType::BOOL);
+        $arr[new Sub()] = true;
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testBoolSetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::BOOL, GPBType::BOOL);
+        $arr[true] = new Sub();
+    }
+
+    /**
+     * @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 Sub()] = 'abc';
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testStringSetMessageValueFail()
+    {
+        $arr = new MapField(GPBType::STRING, GPBType::STRING);
+        $arr['abc'] = new Sub();
+    }
+
+    /**
+     * @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 Sub();
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testMessageSetNullFailMap()
+    {
+       $arr =
+           new MapField(GPBType::INT32, GPBType::MESSAGE, TestMessage::class);
+       $null = NULL;
+       $arr[0] = $null;
+    }
+
+}
diff --git a/third_party/protobuf/php/tests/well_known_test.php b/third_party/protobuf/php/tests/well_known_test.php
new file mode 100644
index 0000000..a16e070
--- /dev/null
+++ b/third_party/protobuf/php/tests/well_known_test.php
@@ -0,0 +1,419 @@
+<?php
+
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Foo\TestMessage;
+use Google\Protobuf\Any;
+use Google\Protobuf\Api;
+use Google\Protobuf\BoolValue;
+use Google\Protobuf\BytesValue;
+use Google\Protobuf\DoubleValue;
+use Google\Protobuf\Duration;
+use Google\Protobuf\Enum;
+use Google\Protobuf\EnumValue;
+use Google\Protobuf\Field;
+use Google\Protobuf\FieldMask;
+use Google\Protobuf\Field\Cardinality;
+use Google\Protobuf\Field\Kind;
+use Google\Protobuf\FloatValue;
+use Google\Protobuf\GPBEmpty;
+use Google\Protobuf\Int32Value;
+use Google\Protobuf\Int64Value;
+use Google\Protobuf\ListValue;
+use Google\Protobuf\Method;
+use Google\Protobuf\Mixin;
+use Google\Protobuf\NullValue;
+use Google\Protobuf\Option;
+use Google\Protobuf\SourceContext;
+use Google\Protobuf\StringValue;
+use Google\Protobuf\Struct;
+use Google\Protobuf\Syntax;
+use Google\Protobuf\Timestamp;
+use Google\Protobuf\Type;
+use Google\Protobuf\UInt32Value;
+use Google\Protobuf\UInt64Value;
+use Google\Protobuf\Value;
+
+class NotMessage {}
+
+class WellKnownTest extends TestBase {
+
+    public function testEmpty()
+    {
+        $msg = new GPBEmpty();
+        $this->assertTrue($msg instanceof \Google\Protobuf\Internal\Message);
+    }
+
+    public function testImportDescriptorProto()
+    {
+        $msg = new TestImportDescriptorProto();
+        $this->assertTrue(true);
+    }
+
+    public function testAny()
+    {
+        // Create embed message
+        $embed = new TestMessage();
+        $this->setFields($embed);
+        $data = $embed->serializeToString();
+
+        // Set any via normal setter.
+        $any = new Any();
+
+        $this->assertSame(
+            $any, $any->setTypeUrl("type.googleapis.com/foo.TestMessage"));
+        $this->assertSame("type.googleapis.com/foo.TestMessage",
+                          $any->getTypeUrl());
+
+        $this->assertSame($any, $any->setValue($data));
+        $this->assertSame($data, $any->getValue());
+
+        // Test unpack.
+        $msg = $any->unpack();
+        $this->assertTrue($msg instanceof TestMessage);
+        $this->expectFields($msg);
+
+        // Test pack.
+        $any = new Any();
+        $any->pack($embed);
+        $this->assertSame($data, $any->getValue());
+        $this->assertSame("type.googleapis.com/foo.TestMessage", $any->getTypeUrl());
+
+        // Test is.
+        $this->assertTrue($any->is(TestMessage::class));
+        $this->assertFalse($any->is(Any::class));
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testAnyUnpackInvalidTypeUrl()
+    {
+        $any = new Any();
+        $any->setTypeUrl("invalid");
+        $any->unpack();
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testAnyUnpackMessageNotAdded()
+    {
+        $any = new Any();
+        $any->setTypeUrl("type.googleapis.com/MessageNotAdded");
+        $any->unpack();
+    }
+
+    /**
+     * @expectedException Exception
+     */
+    public function testAnyUnpackDecodeError()
+    {
+        $any = new Any();
+        $any->setTypeUrl("type.googleapis.com/foo.TestMessage");
+        $any->setValue("abc");
+        $any->unpack();
+    }
+
+    public function testApi()
+    {
+        $m = new Api();
+
+        $m->setName("a");
+        $this->assertSame("a", $m->getName());
+
+        $m->setMethods([new Method()]);
+        $this->assertSame(1, count($m->getMethods()));
+
+        $m->setOptions([new Option()]);
+        $this->assertSame(1, count($m->getOptions()));
+
+        $m->setVersion("a");
+        $this->assertSame("a", $m->getVersion());
+
+        $m->setSourceContext(new SourceContext());
+        $this->assertFalse(is_null($m->getSourceContext()));
+
+        $m->setMixins([new Mixin()]);
+        $this->assertSame(1, count($m->getMixins()));
+
+        $m->setSyntax(Syntax::SYNTAX_PROTO2);
+        $this->assertSame(Syntax::SYNTAX_PROTO2, $m->getSyntax());
+
+        $m = new Method();
+
+        $m->setName("a");
+        $this->assertSame("a", $m->getName());
+
+        $m->setRequestTypeUrl("a");
+        $this->assertSame("a", $m->getRequestTypeUrl());
+
+        $m->setRequestStreaming(true);
+        $this->assertSame(true, $m->getRequestStreaming());
+
+        $m->setResponseTypeUrl("a");
+        $this->assertSame("a", $m->getResponseTypeUrl());
+
+        $m->setResponseStreaming(true);
+        $this->assertSame(true, $m->getResponseStreaming());
+
+        $m->setOptions([new Option()]);
+        $this->assertSame(1, count($m->getOptions()));
+
+        $m = new Mixin();
+
+        $m->setName("a");
+        $this->assertSame("a", $m->getName());
+
+        $m->setRoot("a");
+        $this->assertSame("a", $m->getRoot());
+    }
+
+    public function testEnum()
+    {
+        $m = new Enum();
+
+        $m->setName("a");
+        $this->assertSame("a", $m->getName());
+
+        $m->setEnumvalue([new EnumValue()]);
+        $this->assertSame(1, count($m->getEnumvalue()));
+
+        $m->setOptions([new Option()]);
+        $this->assertSame(1, count($m->getOptions()));
+
+        $m->setSourceContext(new SourceContext());
+        $this->assertFalse(is_null($m->getSourceContext()));
+
+        $m->setSyntax(Syntax::SYNTAX_PROTO2);
+        $this->assertSame(Syntax::SYNTAX_PROTO2, $m->getSyntax());
+    }
+
+    public function testEnumValue()
+    {
+        $m = new EnumValue();
+
+        $m->setName("a");
+        $this->assertSame("a", $m->getName());
+
+        $m->setNumber(1);
+        $this->assertSame(1, $m->getNumber());
+
+        $m->setOptions([new Option()]);
+        $this->assertSame(1, count($m->getOptions()));
+    }
+
+    public function testField()
+    {
+        $m = new Field();
+
+        $m->setKind(Kind::TYPE_DOUBLE);
+        $this->assertSame(Kind::TYPE_DOUBLE, $m->getKind());
+
+        $m->setCardinality(Cardinality::CARDINALITY_OPTIONAL);
+        $this->assertSame(Cardinality::CARDINALITY_OPTIONAL, $m->getCardinality());
+
+        $m->setNumber(1);
+        $this->assertSame(1, $m->getNumber());
+
+        $m->setName("a");
+        $this->assertSame("a", $m->getName());
+
+        $m->setTypeUrl("a");
+        $this->assertSame("a", $m->getTypeUrl());
+
+        $m->setOneofIndex(1);
+        $this->assertSame(1, $m->getOneofIndex());
+
+        $m->setPacked(true);
+        $this->assertSame(true, $m->getPacked());
+
+        $m->setOptions([new Option()]);
+        $this->assertSame(1, count($m->getOptions()));
+
+        $m->setJsonName("a");
+        $this->assertSame("a", $m->getJsonName());
+
+        $m->setDefaultValue("a");
+        $this->assertSame("a", $m->getDefaultValue());
+    }
+
+    public function testFieldMask()
+    {
+        $m = new FieldMask();
+        $m->setPaths(["a"]);
+        $this->assertSame(1, count($m->getPaths()));
+    }
+
+    public function testOption()
+    {
+        $m = new Option();
+
+        $m->setName("a");
+        $this->assertSame("a", $m->getName());
+
+        $m->setValue(new Any());
+        $this->assertFalse(is_null($m->getValue()));
+    }
+
+    public function testSourceContext()
+    {
+        $m = new SourceContext();
+        $m->setFileName("a");
+        $this->assertSame("a", $m->getFileName());
+    }
+
+    public function testStruct()
+    {
+        $m = new ListValue();
+        $m->setValues([new Value()]);
+        $this->assertSame(1, count($m->getValues()));
+
+        $m = new Value();
+
+        $m->setNullValue(NullValue::NULL_VALUE);
+        $this->assertSame(NullValue::NULL_VALUE, $m->getNullValue());
+        $this->assertSame("null_value", $m->getKind());
+
+        $m->setNumberValue(1.0);
+        $this->assertSame(1.0, $m->getNumberValue());
+        $this->assertSame("number_value", $m->getKind());
+
+        $m->setStringValue("a");
+        $this->assertSame("a", $m->getStringValue());
+        $this->assertSame("string_value", $m->getKind());
+
+        $m->setBoolValue(true);
+        $this->assertSame(true, $m->getBoolValue());
+        $this->assertSame("bool_value", $m->getKind());
+
+        $m->setStructValue(new Struct());
+        $this->assertFalse(is_null($m->getStructValue()));
+        $this->assertSame("struct_value", $m->getKind());
+
+        $m->setListValue(new ListValue());
+        $this->assertFalse(is_null($m->getListValue()));
+        $this->assertSame("list_value", $m->getKind());
+
+        $m = new Struct();
+        $m->setFields(array("a"=>new Value()));
+        $this->assertSame(1, count($m->getFields()));
+    }
+
+    public function testTimestamp()
+    {
+        $timestamp = new Timestamp();
+
+        $timestamp->setSeconds(1);
+        $timestamp->setNanos(2);
+        $this->assertEquals(1, $timestamp->getSeconds());
+        $this->assertSame(2, $timestamp->getNanos());
+
+        date_default_timezone_set('UTC');
+        $from = new DateTime('2011-01-01T15:03:01.012345UTC');
+        $timestamp->fromDateTime($from);
+        $this->assertEquals($from->format('U'), $timestamp->getSeconds());
+        $this->assertEquals(1000 * $from->format('u'), $timestamp->getNanos());
+
+        $to = $timestamp->toDateTime();
+        $this->assertSame(\DateTime::class, get_class($to));
+        $this->assertSame($from->format('U'), $to->format('U'));
+        $this->assertSame($from->format('u'), $to->format('u'));
+    }
+
+    public function testType()
+    {
+        $m = new Type();
+
+        $m->setName("a");
+        $this->assertSame("a", $m->getName());
+
+        $m->setFields([new Field()]);
+        $this->assertSame(1, count($m->getFields()));
+
+        $m->setOneofs(["a"]);
+        $this->assertSame(1, count($m->getOneofs()));
+
+        $m->setOptions([new Option()]);
+        $this->assertSame(1, count($m->getOptions()));
+
+        $m->setSourceContext(new SourceContext());
+        $this->assertFalse(is_null($m->getSourceContext()));
+
+        $m->setSyntax(Syntax::SYNTAX_PROTO2);
+        $this->assertSame(Syntax::SYNTAX_PROTO2, $m->getSyntax());
+    }
+
+    public function testDuration()
+    {
+        $duration = new Duration();
+
+        $duration->setSeconds(1);
+        $duration->setNanos(2);
+        $this->assertEquals(1, $duration->getSeconds());
+        $this->assertSame(2, $duration->getNanos());
+    }
+
+    public function testWrappers()
+    {
+        $m = new DoubleValue();
+        $m->setValue(1.0);
+        $this->assertSame(1.0, $m->getValue());
+
+        $m = new FloatValue();
+        $m->setValue(1.0);
+        $this->assertSame(1.0, $m->getValue());
+
+        $m = new Int64Value();
+        $m->setValue(1);
+        $this->assertEquals(1, $m->getValue());
+
+        $m = new UInt64Value();
+        $m->setValue(1);
+        $this->assertEquals(1, $m->getValue());
+
+        $m = new Int32Value();
+        $m->setValue(1);
+        $this->assertSame(1, $m->getValue());
+
+        $m = new UInt32Value();
+        $m->setValue(1);
+        $this->assertSame(1, $m->getValue());
+
+        $m = new BoolValue();
+        $m->setValue(true);
+        $this->assertSame(true, $m->getValue());
+
+        $m = new StringValue();
+        $m->setValue("a");
+        $this->assertSame("a", $m->getValue());
+
+        $m = new BytesValue();
+        $m->setValue("a");
+        $this->assertSame("a", $m->getValue());
+    }
+
+    /**
+     * @dataProvider enumNameValueConversionDataProvider
+     */
+    public function testEnumNameValueConversion($class)
+    {
+        $reflectionClass = new ReflectionClass($class);
+        $constants = $reflectionClass->getConstants();
+        foreach ($constants as $k => $v) {
+            $this->assertSame($k, $class::name($v));
+            $this->assertSame($v, $class::value($k));
+        }
+    }
+
+    public function enumNameValueConversionDataProvider()
+    {
+        return [
+            ['\Google\Protobuf\Field\Cardinality'],
+            ['\Google\Protobuf\Field\Kind'],
+            ['\Google\Protobuf\NullValue'],
+            ['\Google\Protobuf\Syntax'],
+        ];
+    }
+}
diff --git a/third_party/protobuf/php/tests/wrapper_type_setters_test.php b/third_party/protobuf/php/tests/wrapper_type_setters_test.php
new file mode 100644
index 0000000..ad9f718
--- /dev/null
+++ b/third_party/protobuf/php/tests/wrapper_type_setters_test.php
@@ -0,0 +1,312 @@
+<?php
+
+require_once('test_base.php');
+require_once('test_util.php');
+
+use Foo\TestWrapperSetters;
+use Google\Protobuf\BoolValue;
+use Google\Protobuf\BytesValue;
+use Google\Protobuf\DoubleValue;
+use Google\Protobuf\FloatValue;
+use Google\Protobuf\Int32Value;
+use Google\Protobuf\Int64Value;
+use Google\Protobuf\StringValue;
+use Google\Protobuf\UInt32Value;
+use Google\Protobuf\UInt64Value;
+
+class WrapperTypeSettersTest extends TestBase
+{
+    /**
+     * @dataProvider gettersAndSettersDataProvider
+     */
+    public function testGettersAndSetters(
+        $class,
+        $wrapperClass,
+        $setter,
+        $valueSetter,
+        $getter,
+        $valueGetter,
+        $sequence
+    ) {
+        $oldSetterMsg = new $class();
+        $newSetterMsg = new $class();
+        foreach ($sequence as list($value, $expectedValue)) {
+            // Manually wrap the value to pass to the old setter
+            $wrappedValue = is_null($value) ? $value : new $wrapperClass(['value' => $value]);
+
+            // Set values using new and old setters
+            $oldSetterMsg->$setter($wrappedValue);
+            $newSetterMsg->$valueSetter($value);
+
+            // Get expected values old getter
+            $expectedValue = $oldSetterMsg->$getter();
+
+            // Check that old getter returns the same value after using the
+            // new setter
+            $actualValue = $newSetterMsg->$getter();
+            $this->assertEquals($expectedValue, $actualValue);
+
+            // Check that new getter returns the unwrapped value from
+            // $expectedValue
+            $actualValueNewGetter = $newSetterMsg->$valueGetter();
+            if (is_null($expectedValue)) {
+                $this->assertNull($actualValueNewGetter);
+            } else {
+                $this->assertEquals($expectedValue->getValue(), $actualValueNewGetter);
+            }
+        }
+    }
+
+    public function gettersAndSettersDataProvider()
+    {
+        return [
+            [TestWrapperSetters::class, DoubleValue::class, "setDoubleValue", "setDoubleValueUnwrapped", "getDoubleValue", "getDoubleValueUnwrapped", [
+                [1.1, new DoubleValue(["value" => 1.1])],
+                [2.2, new DoubleValue(["value" => 2.2])],
+                [null, null],
+                [0, new DoubleValue()],
+            ]],
+            [TestWrapperSetters::class, FloatValue::class, "setFloatValue", "setFloatValueUnwrapped", "getFloatValue", "getFloatValueUnwrapped", [
+                [1.1, new FloatValue(["value" => 1.1])],
+                [2.2, new FloatValue(["value" => 2.2])],
+                [null, null],
+                [0, new FloatValue()],
+            ]],
+            [TestWrapperSetters::class, Int64Value::class, "setInt64Value", "setInt64ValueUnwrapped", "getInt64Value", "getInt64ValueUnwrapped", [
+                [123, new Int64Value(["value" => 123])],
+                [-789, new Int64Value(["value" => -789])],
+                [null, null],
+                [0, new Int64Value()],
+                [5.5, new Int64Value(["value" => 5])], // Test conversion from float to int
+            ]],
+            [TestWrapperSetters::class, UInt64Value::class, "setUInt64Value", "setUInt64ValueUnwrapped", "getUInt64Value", "getUInt64ValueUnwrapped", [
+                [123, new UInt64Value(["value" => 123])],
+                [789, new UInt64Value(["value" => 789])],
+                [null, null],
+                [0, new UInt64Value()],
+                [5.5, new UInt64Value(["value" => 5])], // Test conversion from float to int
+                [-7, new UInt64Value(["value" => -7])], // Test conversion from -ve to +ve
+            ]],
+            [TestWrapperSetters::class, Int32Value::class, "setInt32Value", "setInt32ValueUnwrapped", "getInt32Value", "getInt32ValueUnwrapped", [
+                [123, new Int32Value(["value" => 123])],
+                [-789, new Int32Value(["value" => -789])],
+                [null, null],
+                [0, new Int32Value()],
+                [5.5, new Int32Value(["value" => 5])], // Test conversion from float to int
+            ]],
+            [TestWrapperSetters::class, UInt32Value::class, "setUInt32Value", "setUInt32ValueUnwrapped", "getUInt32Value", "getUInt32ValueUnwrapped", [
+                [123, new UInt32Value(["value" => 123])],
+                [789, new UInt32Value(["value" => 789])],
+                [null, null],
+                [0, new UInt32Value()],
+                [5.5, new UInt32Value(["value" => 5])], // Test conversion from float to int
+                [-7, new UInt32Value(["value" => -7])], // Test conversion from -ve to +ve
+            ]],
+            [TestWrapperSetters::class, BoolValue::class, "setBoolValue", "setBoolValueUnwrapped", "getBoolValue", "getBoolValueUnwrapped", [
+                [true, new BoolValue(["value" => true])],
+                [false, new BoolValue(["value" => false])],
+                [null, null],
+            ]],
+            [TestWrapperSetters::class, StringValue::class, "setStringValue", "setStringValueUnwrapped", "getStringValue", "getStringValueUnwrapped", [
+                ["asdf", new StringValue(["value" => "asdf"])],
+                ["", new StringValue(["value" => ""])],
+                [null, null],
+                ["", new StringValue()],
+                [5, new StringValue(["value" => "5"])], // Test conversion from number to string
+                [5.5, new StringValue(["value" => "5.5"])], // Test conversion from number to string
+                [-7, new StringValue(["value" => "-7"])], // Test conversion from number to string
+                [-7.5, new StringValue(["value" => "-7.5"])], // Test conversion from number to string
+            ]],
+            [TestWrapperSetters::class, BytesValue::class, "setBytesValue", "setBytesValueUnwrapped", "getBytesValue", "getBytesValueUnwrapped", [
+                ["asdf", new BytesValue(["value" => "asdf"])],
+                ["", new BytesValue(["value" => ""])],
+                [null, null],
+                ["", new BytesValue()],
+                [5, new BytesValue(["value" => "5"])], // Test conversion from number to bytes
+                [5.5, new BytesValue(["value" => "5.5"])], // Test conversion from number to bytes
+                [-7, new BytesValue(["value" => "-7"])], // Test conversion from number to bytes
+                [-7.5, new BytesValue(["value" => "-7.5"])], // Test conversion from number to bytes
+            ]],
+            [TestWrapperSetters::class, DoubleValue::class, "setDoubleValueOneof", "setDoubleValueOneofUnwrapped", "getDoubleValueOneof", "getDoubleValueOneofUnwrapped", [
+                [1.1, new DoubleValue(["value" => 1.1])],
+                [2.2, new DoubleValue(["value" => 2.2])],
+                [null, null],
+                [0, new DoubleValue()],
+            ]],[TestWrapperSetters::class, StringValue::class, "setStringValueOneof", "setStringValueOneofUnwrapped", "getStringValueOneof", "getStringValueOneofUnwrapped", [
+                ["asdf", new StringValue(["value" => "asdf"])],
+                ["", new StringValue(["value" => ""])],
+                [null, null],
+                ["", new StringValue()],
+                [5, new StringValue(["value" => "5"])], // Test conversion from number to string
+                [5.5, new StringValue(["value" => "5.5"])], // Test conversion from number to string
+                [-7, new StringValue(["value" => "-7"])], // Test conversion from number to string
+                [-7.5, new StringValue(["value" => "-7.5"])], // Test conversion from number to string
+            ]],
+        ];
+    }
+
+    /**
+     * @dataProvider invalidSettersDataProvider
+     * @expectedException \Exception
+     */
+    public function testInvalidSetters($class, $setter, $value)
+    {
+        (new $class())->$setter($value);
+    }
+
+    public function invalidSettersDataProvider()
+    {
+        return [
+            [TestWrapperSetters::class, "setDoubleValueUnwrapped", "abc"],
+            [TestWrapperSetters::class, "setDoubleValueUnwrapped", []],
+            [TestWrapperSetters::class, "setDoubleValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setDoubleValueUnwrapped", new DoubleValue()],
+
+            [TestWrapperSetters::class, "setFloatValueUnwrapped", "abc"],
+            [TestWrapperSetters::class, "setFloatValueUnwrapped", []],
+            [TestWrapperSetters::class, "setFloatValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setFloatValueUnwrapped", new FloatValue()],
+
+            [TestWrapperSetters::class, "setInt64ValueUnwrapped", "abc"],
+            [TestWrapperSetters::class, "setInt64ValueUnwrapped", []],
+            [TestWrapperSetters::class, "setInt64ValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setInt64ValueUnwrapped", new Int64Value()],
+
+            [TestWrapperSetters::class, "setUInt64ValueUnwrapped", "abc"],
+            [TestWrapperSetters::class, "setUInt64ValueUnwrapped", []],
+            [TestWrapperSetters::class, "setUInt64ValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setUInt64ValueUnwrapped", new UInt64Value()],
+
+            [TestWrapperSetters::class, "setInt32ValueUnwrapped", "abc"],
+            [TestWrapperSetters::class, "setInt32ValueUnwrapped", []],
+            [TestWrapperSetters::class, "setInt32ValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setInt32ValueUnwrapped", new Int32Value()],
+
+            [TestWrapperSetters::class, "setUInt32ValueUnwrapped", "abc"],
+            [TestWrapperSetters::class, "setUInt32ValueUnwrapped", []],
+            [TestWrapperSetters::class, "setUInt32ValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setUInt32ValueUnwrapped", new UInt32Value()],
+
+            [TestWrapperSetters::class, "setBoolValueUnwrapped", []],
+            [TestWrapperSetters::class, "setBoolValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setBoolValueUnwrapped", new BoolValue()],
+
+            [TestWrapperSetters::class, "setStringValueUnwrapped", []],
+            [TestWrapperSetters::class, "setStringValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setStringValueUnwrapped", new StringValue()],
+
+            [TestWrapperSetters::class, "setBytesValueUnwrapped", []],
+            [TestWrapperSetters::class, "setBytesValueUnwrapped", new stdClass()],
+            [TestWrapperSetters::class, "setBytesValueUnwrapped", new BytesValue()],
+        ];
+    }
+
+    /**
+     * @dataProvider constructorWithWrapperTypeDataProvider
+     */
+    public function testConstructorWithWrapperType($class, $wrapperClass, $wrapperField, $getter, $value)
+    {
+        $actualInstance = new $class([$wrapperField => $value]);
+        $expectedInstance = new $class([$wrapperField => new $wrapperClass(['value' => $value])]);
+        $this->assertEquals($expectedInstance->$getter()->getValue(), $actualInstance->$getter()->getValue());
+    }
+
+    public function constructorWithWrapperTypeDataProvider()
+    {
+        return [
+            [TestWrapperSetters::class, DoubleValue::class, 'double_value', 'getDoubleValue', 1.1],
+            [TestWrapperSetters::class, FloatValue::class, 'float_value', 'getFloatValue', 2.2],
+            [TestWrapperSetters::class, Int64Value::class, 'int64_value', 'getInt64Value', 3],
+            [TestWrapperSetters::class, UInt64Value::class, 'uint64_value', 'getUInt64Value', 4],
+            [TestWrapperSetters::class, Int32Value::class, 'int32_value', 'getInt32Value', 5],
+            [TestWrapperSetters::class, UInt32Value::class, 'uint32_value', 'getUInt32Value', 6],
+            [TestWrapperSetters::class, BoolValue::class, 'bool_value', 'getBoolValue', true],
+            [TestWrapperSetters::class, StringValue::class, 'string_value', 'getStringValue', "eight"],
+            [TestWrapperSetters::class, BytesValue::class, 'bytes_value', 'getBytesValue', "nine"],
+        ];
+    }
+
+    /**
+     * @dataProvider constructorWithRepeatedWrapperTypeDataProvider
+     */
+    public function testConstructorWithRepeatedWrapperType($wrapperField, $getter, $value)
+    {
+        $actualInstance = new TestWrapperSetters([$wrapperField => $value]);
+        foreach ($actualInstance->$getter() as $key => $actualWrapperValue) {
+            $actualInnerValue = $actualWrapperValue->getValue();
+            $expectedElement = $value[$key];
+            if (is_object($expectedElement) && is_a($expectedElement, '\Google\Protobuf\StringValue')) {
+                $expectedInnerValue = $expectedElement->getValue();
+            } else {
+                $expectedInnerValue = $expectedElement;
+            }
+            $this->assertEquals($expectedInnerValue, $actualInnerValue);
+        }
+    }
+
+    public function constructorWithRepeatedWrapperTypeDataProvider()
+    {
+        $sv7 = new StringValue(['value' => 'seven']);
+        $sv8 = new StringValue(['value' => 'eight']);
+
+        $testWrapperSetters = new TestWrapperSetters();
+        $testWrapperSetters->setRepeatedStringValue([$sv7, $sv8]);
+        $repeatedField = $testWrapperSetters->getRepeatedStringValue();
+
+        return [
+            ['repeated_string_value', 'getRepeatedStringValue', []],
+            ['repeated_string_value', 'getRepeatedStringValue', [$sv7]],
+            ['repeated_string_value', 'getRepeatedStringValue', [$sv7, $sv8]],
+            ['repeated_string_value', 'getRepeatedStringValue', ['seven']],
+            ['repeated_string_value', 'getRepeatedStringValue', [7]],
+            ['repeated_string_value', 'getRepeatedStringValue', [7.7]],
+            ['repeated_string_value', 'getRepeatedStringValue', ['seven', 'eight']],
+            ['repeated_string_value', 'getRepeatedStringValue', [$sv7, 'eight']],
+            ['repeated_string_value', 'getRepeatedStringValue', ['seven', $sv8]],
+            ['repeated_string_value', 'getRepeatedStringValue', $repeatedField],
+        ];
+    }
+
+    /**
+     * @dataProvider constructorWithMapWrapperTypeDataProvider
+     */
+    public function testConstructorWithMapWrapperType($wrapperField, $getter, $value)
+    {
+        $actualInstance = new TestWrapperSetters([$wrapperField => $value]);
+        foreach ($actualInstance->$getter() as $key => $actualWrapperValue) {
+            $actualInnerValue = $actualWrapperValue->getValue();
+            $expectedElement = $value[$key];
+            if (is_object($expectedElement) && is_a($expectedElement, '\Google\Protobuf\StringValue')) {
+                $expectedInnerValue = $expectedElement->getValue();
+            } elseif (is_object($expectedElement) && is_a($expectedElement, '\Google\Protobuf\Internal\MapEntry')) {
+                $expectedInnerValue = $expectedElement->getValue()->getValue();
+            } else {
+                $expectedInnerValue = $expectedElement;
+            }
+            $this->assertEquals($expectedInnerValue, $actualInnerValue);
+        }
+    }
+
+    public function constructorWithMapWrapperTypeDataProvider()
+    {
+        $sv7 = new StringValue(['value' => 'seven']);
+        $sv8 = new StringValue(['value' => 'eight']);
+
+        $testWrapperSetters = new TestWrapperSetters();
+        $testWrapperSetters->setMapStringValue(['key' => $sv7, 'key2' => $sv8]);
+        $mapField = $testWrapperSetters->getMapStringValue();
+
+        return [
+            ['map_string_value', 'getMapStringValue', []],
+            ['map_string_value', 'getMapStringValue', ['key' => $sv7]],
+            ['map_string_value', 'getMapStringValue', ['key' => $sv7, 'key2' => $sv8]],
+            ['map_string_value', 'getMapStringValue', ['key' => 'seven']],
+            ['map_string_value', 'getMapStringValue', ['key' => 7]],
+            ['map_string_value', 'getMapStringValue', ['key' => 7.7]],
+            ['map_string_value', 'getMapStringValue', ['key' => 'seven', 'key2' => 'eight']],
+            ['map_string_value', 'getMapStringValue', ['key' => $sv7, 'key2' => 'eight']],
+            ['map_string_value', 'getMapStringValue', ['key' => 'seven', 'key2' => $sv8]],
+            ['map_string_value', 'getMapStringValue', $mapField],
+        ];
+    }
+}