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/.gitignore b/.gitignore
index f072ade..de55b3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -168,6 +168,7 @@
/lib/js/test/build
/lib/nodejs/node_modules/
/lib/perl/MANIFEST
+/lib/perl/MYMETA.json
/lib/perl/MYMETA.yml
/lib/perl/Makefile-perl.mk
/lib/perl/blib
@@ -199,7 +200,7 @@
/lib/php/src/ext/thrift_protocol/thrift_protocol.la
/lib/php/src/ext/thrift_protocol/tmp-php.ini
/lib/php/src/packages/
-/lib/php/test/phpunit.xml
+/lib/php/test/TEST-*.xml
/lib/php/test/packages/
/lib/py/dist/
/lib/erl/logs/
diff --git a/compiler/cpp/src/generate/t_php_generator.cc b/compiler/cpp/src/generate/t_php_generator.cc
index a787f70..0c34a13 100644
--- a/compiler/cpp/src/generate/t_php_generator.cc
+++ b/compiler/cpp/src/generate/t_php_generator.cc
@@ -73,6 +73,9 @@
iter = parsed_options.find("validate");
validate_ = (iter != parsed_options.end());
+ iter = parsed_options.find("json");
+ json_serializable_ = (iter != parsed_options.end());
+
iter = parsed_options.find("nsglobal");
if(iter != parsed_options.end()) {
nsglobal_ = iter->second;
@@ -124,6 +127,7 @@
void generate_php_struct_required_validator(ofstream& out, t_struct* tstruct, std::string method_name, bool write_mode);
void generate_php_struct_read_validator(ofstream& out, t_struct* tstruct);
void generate_php_struct_write_validator(ofstream& out, t_struct* tstruct);
+ void generate_php_struct_json_serialize(ofstream& out, t_struct* tstruct, bool is_result);
bool needs_php_write_validator(t_struct* tstruct, bool is_result);
bool needs_php_read_validator(t_struct* tstruct, bool is_result);
int get_php_num_required_fields(const vector<t_field*>& fields, bool write_mode);
@@ -378,6 +382,11 @@
bool validate_;
/**
+ * Whether to generate JsonSerializable classes
+ */
+ bool json_serializable_;
+
+ /**
* Global namespace for PHP 5.3
*/
std::string nsglobal_;
@@ -425,16 +434,23 @@
* Prints standard php includes
*/
string t_php_generator::php_includes() {
- string TBase = "use Thrift\\Base\\TBase;\n";
- string TType = "use Thrift\\Type\\TType;\n";
- string TMessageType = "use Thrift\\Type\\TMessageType;\n";
- string TException = "use Thrift\\Exception\\TException;\n";
- string TProtocolException = "use Thrift\\Exception\\TProtocolException;\n";
- string TProtocol = "use Thrift\\Protocol\\TProtocol;\n";
- string TBinaryProtocolAccelerated = "use Thrift\\Protocol\\TBinaryProtocolAccelerated;\n";
- string TApplicationException = "use Thrift\\Exception\\TApplicationException;\n\n";
+ string includes =
+ "use Thrift\\Base\\TBase;\n"
+ "use Thrift\\Type\\TType;\n"
+ "use Thrift\\Type\\TMessageType;\n"
+ "use Thrift\\Exception\\TException;\n"
+ "use Thrift\\Exception\\TProtocolException;\n"
+ "use Thrift\\Protocol\\TProtocol;\n"
+ "use Thrift\\Protocol\\TBinaryProtocolAccelerated;\n"
+ "use Thrift\\Exception\\TApplicationException;\n";
- return TBase + TType + TMessageType + TException + TProtocolException + TProtocol + TBinaryProtocolAccelerated + TApplicationException;
+ if (json_serializable_) {
+ includes +=
+ "use JsonSerializable;\n"
+ "use stdClass;\n";
+ }
+
+ return includes + "\n";
}
/**
@@ -770,6 +786,9 @@
} else if (oop_) {
out << " extends " << "TBase";
}
+ if (json_serializable_) {
+ out << " implements JsonSerializable";
+ }
out <<
" {" << endl;
indent_up();
@@ -838,6 +857,9 @@
if (needs_php_write_validator(tstruct, is_result)) {
generate_php_struct_write_validator(out, tstruct);
}
+ if (json_serializable_) {
+ generate_php_struct_json_serialize(out, tstruct, is_result);
+ }
indent_down();
out <<
@@ -1125,6 +1147,47 @@
indent(out) << "}" << endl << endl;
}
+
+
+void t_php_generator::generate_php_struct_json_serialize(ofstream& out,
+ t_struct* tstruct,
+ bool is_result) {
+ indent(out) <<
+ "public function jsonSerialize() {" << endl;
+ indent_up();
+
+ if (needs_php_write_validator(tstruct, is_result)) {
+ indent(out) << "$this->_validateForWrite();" << endl;
+ }
+
+ indent(out) << "$json = new stdClass;" << endl;
+
+ const vector<t_field*>& fields = tstruct->get_members();
+
+ if (fields.size() > 0) {
+ vector<t_field*>::const_iterator f_iter;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ t_field* field = (*f_iter);
+ t_type* type = field->get_type();
+ const string& name = field->get_name();
+ if (type->is_map() && !((t_map*)type)->get_key_type()->is_string()) {
+ // JSON object keys must be strings
+ continue;
+ }
+ indent(out) << "if ($this->" << name << " !== null) {" << endl;
+ indent_up();
+ indent(out) << "$json->" << name << " = $this->" << name << ";" << endl;
+ indent_down();
+ indent(out) << "}" << endl;
+ }
+ }
+
+ indent(out) << "return $json;" << endl;
+ indent_down();
+
+ indent(out) << "}" << endl << endl;
+}
+
int t_php_generator::get_php_num_required_fields(const vector<t_field*>& fields,
bool write_mode) {
int num_req = 0;
@@ -2695,5 +2758,6 @@
" rest: Generate PHP REST processors\n"
" nsglobal=NAME: Set global namespace\n"
" validate: Generate PHP validator methods\n"
+" json: Generate JsonSerializable classes (requires PHP >= 5.4)\n"
)
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)));
+ }
+
+}