THRIFT-2712 PHP: add "json" option to create JsonSerializable code

This patch adds a "json" option to PHP code generation that will
make generated classes implement JsonSerializable, so thrift
objects may be converted to json using json_encode() easily.
If the "validate" option is enabled, the object's write validator
will be called, beyond that the jsonSerialize() method only outputs
non-null fields in the JSON object (JSON parsers get grumpy if you
send them null where they expect to see a number).

Patch: Stig Bakken

Github Pull Request: This closes #219
diff --git a/lib/php/test/Makefile.am b/lib/php/test/Makefile.am
index a529d8c..869c544 100755
--- a/lib/php/test/Makefile.am
+++ b/lib/php/test/Makefile.am
@@ -24,25 +24,34 @@
 	$(THRIFT) --gen php -r --out ./packages ../../../test/ThriftTest.thrift
 	mkdir -p ./packages/phpv
 	mkdir -p ./packages/phpvo
-	$(THRIFT) --gen php:validate     -r --out ./packages/phpv   TestValidators.thrift 
-	$(THRIFT) --gen php:validate,oop -r --out ./packages/phpvo  TestValidators.thrift 
-	
+	mkdir -p ./packages/phpjs
+	$(THRIFT) --gen php:validate     -r --out ./packages/phpv   TestValidators.thrift
+	$(THRIFT) --gen php:validate,oop -r --out ./packages/phpvo  TestValidators.thrift
+	$(THRIFT) --gen php:json         -r --out ./packages/phpjs  TestValidators.thrift
+
+check-json-serializer: stubs
+if HAVE_PHPUNIT
+	$(PHPUNIT) --log-junit=TEST-json-serializer.xml Test/Thrift/JsonSerialize/
+endif
+
 check-validator: stubs 
 	php Test/Thrift/TestValidators.php 
 	php Test/Thrift/TestValidators.php -oop 
 
 check-protocol:	stubs
 if HAVE_PHPUNIT
-	$(PHPUNIT) --log-junit=phpunit.xml Test/Thrift/Protocol/TestTJSONProtocol.php
-	$(PHPUNIT) --log-junit=phpunit.xml Test/Thrift/Protocol/TestBinarySerializer.php
+	$(PHPUNIT) --log-junit=TEST-log-json-protocol.xml Test/Thrift/Protocol/TestTJSONProtocol.php
+	$(PHPUNIT) --log-junit=TEST-binary-serializer.xml Test/Thrift/Protocol/TestBinarySerializer.php
 endif
-	
+
 check: stubs \
   check-protocol \
-  check-validator
+  check-validator \
+  check-json-serializer
 
 clean-local:
 	$(RM) -r ./packages
+	$(RM) TEST-*.xml
 
 EXTRA_DIST = Test
 
diff --git a/lib/php/test/Test/Thrift/JsonSerialize/JsonSerializeTest.php b/lib/php/test/Test/Thrift/JsonSerialize/JsonSerializeTest.php
new file mode 100644
index 0000000..7e324f0
--- /dev/null
+++ b/lib/php/test/Test/Thrift/JsonSerialize/JsonSerializeTest.php
@@ -0,0 +1,92 @@
+<?php
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Test\Thrift\JsonSerialize;
+
+use stdClass;
+use Test\Thrift\Fixtures;
+use Thrift\ClassLoader\ThriftClassLoader;
+use Thrift\Serializer\TBinarySerializer;
+
+require_once __DIR__.'/../../../../lib/Thrift/ClassLoader/ThriftClassLoader.php';
+
+$loader = new ThriftClassLoader();
+$loader->registerNamespace('Thrift', __DIR__ . '/../../../../lib');
+$loader->registerNamespace('Test', __DIR__ . '/../../..');
+$loader->registerDefinition('ThriftTest', __DIR__ . '/../../../packages/phpjs');
+$loader->register();
+
+class JsonSerializeTest extends \PHPUnit_Framework_TestCase
+{
+
+  public function testEmptyStruct() {
+    $empty = new \ThriftTest\EmptyStruct(array('non_existing_key' => 'bar'));
+    $this->assertEquals(new stdClass, json_decode(json_encode($empty)));
+  }
+
+  public function testStringsAndInts() {
+    $input = array(
+      'string_thing' => 'foo',
+      'i64_thing' => 1234567890,
+    );
+    $xtruct = new \ThriftTest\Xtruct($input);
+
+    // Xtruct's 'i32_thing' and 'byte_thing' fields should not be present here!
+    $expected = new stdClass;
+    $expected->string_thing = $input['string_thing'];
+    $expected->i64_thing = $input['i64_thing'];
+    $this->assertEquals($expected, json_decode(json_encode($xtruct)));
+  }
+
+  public function testNestedStructs() {
+    $xtruct2 = new \ThriftTest\Xtruct2(array(
+      'byte_thing' => 42,
+      'struct_thing' => new \ThriftTest\Xtruct(array(
+        'i32_thing' => 123456,
+      )),
+    ));
+
+    $expected = new stdClass;
+    $expected->byte_thing = $xtruct2->byte_thing;
+    $expected->struct_thing = new stdClass;
+    $expected->struct_thing->i32_thing = $xtruct2->struct_thing->i32_thing;
+    $this->assertEquals($expected, json_decode(json_encode($xtruct2)));
+  }
+
+  public function testInsanity() {
+    $xinput = array('string_thing' => 'foo');
+    $xtruct = new \ThriftTest\Xtruct($xinput);
+    $insanity = new \ThriftTest\Insanity(array(
+      'xtructs' => array($xtruct, $xtruct, $xtruct)
+    ));
+    $expected = new stdClass;
+    $expected->xtructs = array((object)$xinput, (object)$xinput, (object)$xinput);
+    $this->assertEquals($expected, json_decode(json_encode($insanity)));
+  }
+
+  public function testNestedLists() {
+    $bonk = new \ThriftTest\Bonk(array('message' => 'foo'));
+    $nested = new \ThriftTest\NestedListsBonk(array('bonk' => array(array(array($bonk)))));
+    $expected = new stdClass;
+    $expected->bonk = array(array(array((object)array('message' => 'foo'))));
+    $this->assertEquals($expected, json_decode(json_encode($nested)));
+  }
+
+}