Fix Any json encoding/decoding in php (#5496)

* Fix Any json encoding/decoding in php

* Fix comments
diff --git a/php/tests/encode_decode_test.php b/php/tests/encode_decode_test.php
index 06f9a90..8018f97 100644
--- a/php/tests/encode_decode_test.php
+++ b/php/tests/encode_decode_test.php
@@ -5,12 +5,14 @@
 
 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\FloatValue;
 use Google\Protobuf\Int32Value;
@@ -915,4 +917,143 @@
         $this->assertSame("{\"a\":1.5}", $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());
+    }
+
 }