From ad5ddb5563f8175d42a510a8e92d1a92880a1261 Mon Sep 17 00:00:00 2001 From: "(no author)" <(no author)@41a61cd8-c433-0410-bb1c-e256eeef9e11> Date: Tue, 13 Nov 2007 04:02:11 +0000 Subject: [PATCH] r1275@opsdev009 (orig r69342): mcslee | 2007-11-12 20:00:29 -0800 Thrift PHP generation Redux Summary: Chopping the amount of code generated by Thrift for PHP services by two orders of magnitude (approx 25% of the previous size). This is done via putting more logic in a dynamic base class and taking it out of the generated code. Hopefully this wins back the CPU cycles paid just to load code from APC at the cost of a marginal increase in dynamic execution runtime. Reviewed By: sgrimm, dreiss Test Plan: Ran all the tests in trunk/test/php, also tested the API generate code and Falcon, etc. in my sandbox git-svn-id: http://svn.facebook.com/svnroot/thrift/trunk@656 41a61cd8-c433-0410-bb1c-e256eeef9e11 --- compiler/cpp/src/generate/t_php_generator.cc | 123 ++++- compiler/cpp/src/generate/t_php_generator.h | 23 +- compiler/cpp/src/main.cc | 19 +- compiler/cpp/src/parse/t_base_type.h | 10 +- compiler/cpp/src/parse/t_type.h | 1 + lib/php/src/Thrift.php | 729 +++++++++++++++++++++++++-- test/php/Makefile | 4 +- test/php/TestClient.php | 14 +- 8 files changed, 852 insertions(+), 71 deletions(-) diff --git a/compiler/cpp/src/generate/t_php_generator.cc b/compiler/cpp/src/generate/t_php_generator.cc index 2fb4e18..848e917 100644 --- a/compiler/cpp/src/generate/t_php_generator.cc +++ b/compiler/cpp/src/generate/t_php_generator.cc @@ -35,8 +35,9 @@ void t_php_generator::init_generator() { // Include other Thrift includes const vector& includes = program_->get_includes(); for (size_t i = 0; i < includes.size(); ++i) { + string package = includes[i]->get_name(); f_types_ << - "include_once $GLOBALS['THRIFT_ROOT'].'/packages/" + includes[i]->get_name() << "_types.php';" << endl; + "include_once $GLOBALS['THRIFT_ROOT'].'/packages/" << package << "/" << package << "_types.php';" << endl; } f_types_ << endl; @@ -283,6 +284,76 @@ void t_php_generator::generate_php_struct(t_struct* tstruct, generate_php_struct_definition(f_types_, tstruct, is_exception); } +void t_php_generator::generate_php_type_spec(ofstream& out, + t_type* t) { + t = get_true_type(t); + indent(out) << "'type' => " << type_to_enum(t) << "," << endl; + + if (t->is_base_type() || t->is_enum()) { + // Noop, type is all we need + } else if (t->is_struct() || t->is_xception()) { + indent(out) << "'class' => '" << t->get_name() <<"'," << endl; + } else if (t->is_map()) { + t_type* ktype = get_true_type(((t_map*)t)->get_key_type()); + t_type* vtype = get_true_type(((t_map*)t)->get_val_type()); + indent(out) << "'ktype' => " << type_to_enum(ktype) << "," << endl; + indent(out) << "'vtype' => " << type_to_enum(vtype) << "," << endl; + indent(out) << "'key' => array(" << endl; + indent_up(); + generate_php_type_spec(out, ktype); + indent_down(); + indent(out) << ")," << endl; + indent(out) << "'val' => array(" << endl; + indent_up(); + generate_php_type_spec(out, vtype); + indent(out) << ")," << endl; + indent_down(); + } else if (t->is_list() || t->is_set()) { + t_type* etype; + if (t->is_list()) { + etype = get_true_type(((t_list*)t)->get_elem_type()); + } else { + etype = get_true_type(((t_set*)t)->get_elem_type()); + } + indent(out) << "'etype' => " << type_to_enum(etype) <<"," << endl; + indent(out) << "'elem' => array(" << endl; + indent_up(); + generate_php_type_spec(out, etype); + indent(out) << ")," << endl; + indent_down(); + } else { + throw "compiler error: no type for php struct spec field"; + } + +} + +/** + * Generates the struct specification structure, which fully qualifies enough + * type information to generalize serialization routines. + */ +void t_php_generator::generate_php_struct_spec(ofstream& out, + t_struct* tstruct) { + indent(out) << "static $_TSPEC = array(" << endl; + indent_up(); + + const vector& members = tstruct->get_members(); + vector::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + indent(out) << (*m_iter)->get_key() << " => array(" << endl; + indent_up(); + out << + indent() << "'var' => '" << (*m_iter)->get_name() << "'," << endl; + generate_php_type_spec(out, t); + indent(out) << ")," << endl; + indent_down(); + } + + indent_down(); + indent(out) << " );" << endl; +} + + /** * Generates a struct definition for a thrift data type. This is nothing in PHP * where the objects are all just associative arrays (unless of course we @@ -299,12 +370,16 @@ void t_php_generator::generate_php_struct_definition(ofstream& out, out << "class " << php_namespace(tstruct->get_program()) << tstruct->get_name(); if (is_exception) { - out << " extends Exception"; + out << " extends TException"; + } else { + out << " extends TBase"; } out << " {" << endl; indent_up(); + generate_php_struct_spec(out, tstruct); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { string dval = "null"; t_type* t = get_true_type((*m_iter)->get_type()); @@ -331,21 +406,12 @@ void t_php_generator::generate_php_struct_definition(ofstream& out, } out << - indent() << "if (is_array($vals)) {" << endl; - indent_up(); - for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { - out << - indent() << "if (isset($vals['" << (*m_iter)->get_name() << "'])) {" << endl << - indent() << " $this->" << (*m_iter)->get_name() << " = $vals['" << (*m_iter)->get_name() << "'];" << endl << - indent() << "}" << endl; - } - indent_down(); - out << + indent() << "if (is_array($vals)) {" << endl << + indent() << " parent::construct(self::$_TSPEC, $vals);" << endl << indent() << "}" << endl; - indent_down(); - out << - indent() << "}" << endl << - endl; + scope_down(out); + + out << endl; } out << @@ -372,9 +438,14 @@ void t_php_generator::generate_php_struct_reader(ofstream& out, vector::const_iterator f_iter; indent(out) << - "public function read($input) " << endl; + "public function read($input)" << endl; scope_up(out); + // TODO(mcslee): Testing this new jonx! + indent(out) << "return $this->_read('" << tstruct->get_name() << "', self::$_TSPEC, $input);" << endl; + scope_down(out); + return; + out << indent() << "$xfer = 0;" << endl << indent() << "$fname = null;" << endl << @@ -496,6 +567,11 @@ void t_php_generator::generate_php_struct_writer(ofstream& out, } indent_up(); + // TODO(mcslee): Testing this new j0nx + indent(out) << "return $this->_write('" << tstruct->get_name() << "', self::$_TSPEC, $output);" << endl; + scope_down(out); + return; + indent(out) << "$xfer = 0;" << endl; @@ -586,7 +662,9 @@ void t_php_generator::generate_service(t_service* tservice) { } generate_service_client(tservice); generate_service_helpers(tservice); - generate_service_processor(tservice); + if (phps_) { + generate_service_processor(tservice); + } // Close service file f_service_ << "?>" << endl; @@ -914,8 +992,13 @@ void t_php_generator::generate_service_rest(t_service* tservice) { t_type* atype = get_true_type((*a_iter)->get_type()); string cast = type_to_cast(atype); string req = "$request['" + (*a_iter)->get_name() + "']"; - f_service_ << - indent() << "$" << (*a_iter)->get_name() << " = isset(" << req << ") ? " << cast << req << " : null;" << endl; + if (atype->is_bool()) { + f_service_ << + indent() << "$" << (*a_iter)->get_name() << " = " << cast << "(!empty(" << req << ") && (" << req << " !== 'false'));" << endl; + } else { + f_service_ << + indent() << "$" << (*a_iter)->get_name() << " = isset(" << req << ") ? " << cast << req << " : null;" << endl; + } if (atype->is_string() && ((t_base_type*)atype)->is_string_list()) { f_service_ << diff --git a/compiler/cpp/src/generate/t_php_generator.h b/compiler/cpp/src/generate/t_php_generator.h index 1f2e258..5742747 100644 --- a/compiler/cpp/src/generate/t_php_generator.h +++ b/compiler/cpp/src/generate/t_php_generator.h @@ -21,13 +21,14 @@ */ class t_php_generator : public t_oop_generator { public: - t_php_generator(t_program* program, bool binary_inline=false, bool rest=false) : + t_php_generator(t_program* program, bool binary_inline=false, bool rest=false, bool phps=false) : t_oop_generator(program), binary_inline_(binary_inline), - rest_(rest) { + rest_(rest), + phps_(phps) { out_dir_base_ = (binary_inline_ ? "gen-phpi" : "gen-php"); } - + /** * Init and close methods */ @@ -58,6 +59,9 @@ class t_php_generator : public t_oop_generator { void generate_php_struct_writer(std::ofstream& out, t_struct* tstruct); void generate_php_function_helpers(t_function* tfunction); + void generate_php_type_spec(std::ofstream &out, t_type* t); + void generate_php_struct_spec(std::ofstream &out, t_struct* tstruct); + /** * Service-level generation functions */ @@ -74,18 +78,18 @@ class t_php_generator : public t_oop_generator { */ void generate_deserialize_field (std::ofstream &out, - t_field* tfield, + t_field* tfield, std::string prefix="", bool inclass=false); - + void generate_deserialize_struct (std::ofstream &out, t_struct* tstruct, std::string prefix=""); - + void generate_deserialize_container (std::ofstream &out, t_type* ttype, std::string prefix=""); - + void generate_deserialize_set_element (std::ofstream &out, t_set* tset, std::string prefix=""); @@ -159,6 +163,11 @@ class t_php_generator : public t_oop_generator { */ bool rest_; + /** + * Generate stubs for a PHP server + */ + bool phps_; + }; #endif diff --git a/compiler/cpp/src/main.cc b/compiler/cpp/src/main.cc index 468a7b0..c633fac 100644 --- a/compiler/cpp/src/main.cc +++ b/compiler/cpp/src/main.cc @@ -139,6 +139,7 @@ bool gen_py = false; bool gen_xsd = false; bool gen_php = false; bool gen_phpi = false; +bool gen_phps = true; bool gen_rest = false; bool gen_perl = false; bool gen_erl = false; @@ -559,6 +560,8 @@ void usage() { fprintf(stderr, " -javabean Generate Java bean-style output files\n"); fprintf(stderr, " -php Generate PHP output files\n"); fprintf(stderr, " -phpi Generate PHP inlined files\n"); + fprintf(stderr, " -phps Generate PHP server stubs (with -php)\n"); + fprintf(stderr, " -phpl Generate PHP-lite (with -php)\n"); fprintf(stderr, " -py Generate Python output files\n"); fprintf(stderr, " -rb Generate Ruby output files\n"); fprintf(stderr, " -xsd Generate XSD output files\n"); @@ -775,7 +778,7 @@ void generate(t_program* program) { for (size_t i = 0; i < includes.size(); ++i) { // Propogate output path from parent to child programs includes[i]->set_out_path(program->get_out_path()); - + generate(includes[i]); } } @@ -810,7 +813,7 @@ void generate(t_program* program) { if (gen_php) { pverbose("Generating PHP\n"); - t_php_generator* php = new t_php_generator(program, false, gen_rest); + t_php_generator* php = new t_php_generator(program, false, gen_rest, gen_phps); php->generate_program(); delete php; } @@ -940,6 +943,16 @@ int main(int argc, char** argv) { gen_php = true; } else if (strcmp(arg, "-phpi") == 0) { gen_phpi = true; + } else if (strcmp(arg, "-phps") == 0) { + if (!gen_php) { + gen_php = true; + } + gen_phps = true; + } else if (strcmp(arg, "-phpl") == 0) { + if (!gen_php) { + gen_php = true; + } + gen_phps = false; } else if (strcmp(arg, "-rest") == 0) { gen_rest = true; } else if (strcmp(arg, "-py") == 0) { @@ -972,7 +985,7 @@ int main(int argc, char** argv) { if (arg == NULL) { fprintf(stderr, "-o: missing output directory"); usage(); - } + } out_path = arg; struct stat sb; if (stat(out_path.c_str(), &sb) < 0) { diff --git a/compiler/cpp/src/parse/t_base_type.h b/compiler/cpp/src/parse/t_base_type.h index 929bf74..a440380 100644 --- a/compiler/cpp/src/parse/t_base_type.h +++ b/compiler/cpp/src/parse/t_base_type.h @@ -37,7 +37,7 @@ class t_base_type : public t_type { base_(base), string_list_(false), string_enum_(false) {} - + t_base get_base() const { return base_; } @@ -49,7 +49,11 @@ class t_base_type : public t_type { bool is_string() const { return base_ == TYPE_STRING; } - + + bool is_bool() const { + return base_ == TYPE_BOOL; + } + void set_string_list(bool val) { string_list_ = val; } @@ -61,7 +65,7 @@ class t_base_type : public t_type { void set_binary(bool val) { binary_ = val; } - + bool is_binary() const { return (base_ == TYPE_STRING) && binary_; } diff --git a/compiler/cpp/src/parse/t_type.h b/compiler/cpp/src/parse/t_type.h index 3144356..b9da4fd 100644 --- a/compiler/cpp/src/parse/t_type.h +++ b/compiler/cpp/src/parse/t_type.h @@ -40,6 +40,7 @@ class t_type : public t_doc { virtual bool is_void() const { return false; } virtual bool is_base_type() const { return false; } virtual bool is_string() const { return false; } + virtual bool is_bool() const { return false; } virtual bool is_typedef() const { return false; } virtual bool is_enum() const { return false; } virtual bool is_struct() const { return false; } diff --git a/lib/php/src/Thrift.php b/lib/php/src/Thrift.php index 21a175c..9c7b4e6 100644 --- a/lib/php/src/Thrift.php +++ b/lib/php/src/Thrift.php @@ -43,56 +43,560 @@ class TMessageType { const EXCEPTION = 3; } +/** + * NOTE(mcslee): This currently contains a ton of duplicated code from TBase + * because we need to save CPU cycles and this is not yet in an extension. + * Ideally we'd multiply-inherit TException from both Exception and Base, but + * that's not possible in PHP and there are no modules either, so for now we + * apologetically take a trip to HackTown. + * + * Can be called with standard Exception constructor (message, code) or with + * Thrift Base object constructor (spec, vals). + * + * @param mixed $p1 Message (string) or type-spec (array) + * @param mixed $p2 Code (integer) or values (array) + */ class TException extends Exception { - function __construct($message=null, $code=0) { - parent::__construct($message, $code); + function __construct($p1=null, $p2=0) { + if (is_array($p1) && is_array($p2)) { + $spec = $p1; + $vals = $p2; + foreach ($spec as $fid => $fspec) { + $var = $fspec['var']; + if (isset($vals[$var])) { + $this->$var = $vals[$var]; + } + } + } else { + parent::__construct($p1, $p2); + } } -} -class TApplicationException extends TException { - - const UNKNOWN = 0; - const UNKNOWN_METHOD = 1; - const INVALID_MESSAGE_TYPE = 2; - const WRONG_METHOD_NAME = 3; - const BAD_SEQUENCE_ID = 4; - const MISSING_RESULT = 5; + static $tmethod = array(TType::BOOL => 'Bool', + TType::BYTE => 'Byte', + TType::I16 => 'I16', + TType::I32 => 'I32', + TType::I64 => 'I64', + TType::DOUBLE => 'Double', + TType::STRING => 'String'); - function __construct($message=null, $code=0) { - parent::__construct($message, $code); + private function _readMap(&$var, $spec, $input) { + $xfer = 0; + $ktype = $spec['ktype']; + $vtype = $spec['vtype']; + $kread = $vread = null; + if (isset(TBase::$tmethod[$ktype])) { + $kread = 'read'.TBase::$tmethod[$ktype]; + } else { + $kspec = $spec['key']; + } + if (isset(TBase::$tmethod[$vtype])) { + $vread = 'read'.TBase::$tmethod[$vtype]; + } else { + $vspec = $spec['val']; + } + $var = array(); + $_ktype = $_vtype = $size = 0; + $xfer += $input->readMapBegin($_ktype, $_vtype, $size); + for ($i = 0; $i < $size; ++$i) { + $key = $val = null; + if ($kread !== null) { + $xfer += $input->$kread($key); + } else { + switch ($ktype) { + case TType::STRUCT: + $class = $kspec['class']; + $key = new $class(); + $xfer += $key->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($key, $kspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($key, $kspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($key, $kspec, $input, true); + break; + } + } + if ($vread !== null) { + $xfer += $input->$vread($val); + } else { + switch ($vtype) { + case TType::STRUCT: + $class = $vspec['class']; + $val = new $class(); + $xfer += $val->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($val, $vspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($val, $vspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($val, $vspec, $input, true); + break; + } + } + $var[$key] = $val; + } + $xfer += $input->readMapEnd(); + return $xfer; } - public function read($input) { + private function _readList(&$var, $spec, $input, $set=false) { + $xfer = 0; + $etype = $spec['etype']; + $eread = $vread = null; + if (isset(TBase::$tmethod[$etype])) { + $eread = 'read'.TBase::$tmethod[$etype]; + } else { + $espec = $spec['elem']; + } + $var = array(); + $_etype = $size = 0; + if ($set) { + $xfer += $input->readSetBegin($_etype, $size); + } else { + $xfer += $input->readListBegin($_etype, $size); + } + for ($i = 0; $i < $size; ++$i) { + $elem = null; + if ($eread !== null) { + $xfer += $input->$eread($elem); + } else { + $espec = $spec['elem']; + switch ($etype) { + case TType::STRUCT: + $class = $espec['class']; + $elem = new $class(); + $xfer += $elem->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($key, $espec, $input); + break; + case TType::LST: + $xfer += $this->_readList($key, $espec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($key, $espec, $input, true); + break; + } + } + if ($set) { + $var[$elem] = true; + } else { + $var []= $elem; + } + } + if ($set) { + $xfer += $input->readSetEnd(); + } else { + $xfer += $input->readListEnd(); + } + return $xfer; + } + + protected function _read($class, $spec, $input) { $xfer = 0; $fname = null; $ftype = 0; $fid = 0; $xfer += $input->readStructBegin($fname); - while (true) - { + $fast_binary = (bool) + is_a($input, 'TBinaryProtocolAccelerated') && + function_exists('thrift_protocol_binary_deserialize'); + + while (true) { $xfer += $input->readFieldBegin($fname, $ftype, $fid); if ($ftype == TType::STOP) { break; } - switch ($fid) - { - case 1: - if ($ftype == TType::STRING) { - $xfer += $input->readString($this->message); + if (isset($spec[$fid])) { + $fspec = $spec[$fid]; + $var = $fspec['var']; + if ($ftype == $fspec['type']) { + $xfer = 0; + if ($fast_binary) { + $class = isset($fspec['class']) ? $fspec['class'] : null; + $this->$var = thrift_protocol_binary_deserialize($ftype, $input, $class); } else { - $xfer += $input->skip($ftype); + if (isset(TBase::$tmethod[$ftype])) { + $func = 'read'.TBase::$tmethod[$ftype]; + $xter += $input->$func($this->$var); + } else { + switch ($ftype) { + case TType::STRUCT: + $class = $fspec['class']; + $this->$var = new $class(); + $xfer += $this->$var->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($this->$var, $fspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($this->$var, $fspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($this->$var, $fspec, $input, true); + break; + } + } } + } else { + $xfer += $input->skip($ftype); + } + } else { + $xfer += $input->skip($ftype); + } + $xfer += $input->readFieldEnd(); + } + $xfer += $input->readStructEnd(); + return $xfer; + } + + private function _writeMap($var, $spec, $output) { + $xfer = 0; + $ktype = $spec['ktype']; + $vtype = $spec['vtype']; + $kwrite = $vwrite = null; + if (isset(TBase::$tmethod[$ktype])) { + $kwrite = 'write'.TBase::$tmethod[$ktype]; + } else { + $kspec = $spec['key']; + } + if (isset(TBase::$tmethod[$vtype])) { + $vwrite = 'write'.TBase::$tmethod[$vtype]; + } else { + $vspec = $spec['val']; + } + $xfer += $output->writeMapBegin($ktype, $vtype, count($var)); + foreach ($var as $key => $val) { + if (isset($kwrite)) { + $xfer += $output->$kwrite($key); + } else { + switch ($ktype) { + case TType::STRUCT: + $xfer += $key->write($output); break; - case 2: - if ($ftype == TType::I32) { - $xfer += $input->readI32($this->code); - } else { - $xfer += $input->skip($ftype); + case TType::MAP: + $xfer += $this->_writeMap($key, $kspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($key, $kspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($key, $kspec, $output, true); + break; + } + } + if (isset($vwrite)) { + $xfer += $output->$vwrite($val); + } else { + switch ($vtype) { + case TType::STRUCT: + $xfer += $val->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($val, $vspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($val, $vspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($val, $vspec, $output, true); + break; + } + } + } + $xfer += $output->writeMapEnd(); + return $xfer; + } + + private function _writeList($var, $spec, $output, $set=false) { + $xfer = 0; + $etype = $spec['etype']; + $ewrite = null; + if (isset(TBase::$tmethod[$etype])) { + $ewrite = 'write'.TBase::$tmethod[$etype]; + } else { + $espec = $spec['elem']; + } + if ($set) { + $xfer += $output->writeSetBegin($etype, count($var)); + } else { + $xfer += $output->writeListBegin($etype, count($var)); + } + foreach ($var as $key => $val) { + $elem = $set ? $key : $val; + if (isset($ewrite)) { + $xfer += $output->$ewrite($elem); + } else { + switch ($etype) { + case TType::STRUCT: + $xfer += $elem->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($elem, $espec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($elem, $espec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($elem, $espec, $output, true); + break; + } + } + } + if ($set) { + $xfer += $output->writeSetEnd(); + } else { + $xfer += $output->writeListEnd(); + } + return $xfer; + } + + protected function _write($class, $spec, $output) { + $xfer = 0; + $xfer += $output->writeStructBegin($class); + foreach ($spec as $fid => $fspec) { + $var = $fspec['var']; + if ($this->$var !== null) { + $ftype = $fspec['type']; + $xfer += $output->writeFieldBegin($var, $ftype, $fid); + if (isset(TBase::$tmethod[$ftype])) { + $func = 'write'.TBase::$tmethod[$ftype]; + $xter += $output->$func($this->$var); + } else { + switch ($ftype) { + case TType::STRUCT: + $xfer += $this->$var->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($this->$var, $fspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($this->$var, $fspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($this->$var, $fspec, $output, true); + break; } + } + $xfer += $output->writeFieldEnd(); + } + } + $xfer += $output->writeFieldStop(); + $xfer += $output->writeStructEnd(); + return $xfer; + } + +} + +/** + * Base class from which other Thrift structs extend. This is so that we can + * cut back on the size of the generated code which is turning out to have a + * nontrivial cost just to load thanks to the wondrously abysmal implementation + * of PHP. Note that code is intentionally duplicated in here to avoid making + * function calls for every field or member of a container.. + */ +abstract class TBase { + + static $tmethod = array(TType::BOOL => 'Bool', + TType::BYTE => 'Byte', + TType::I16 => 'I16', + TType::I32 => 'I32', + TType::I64 => 'I64', + TType::DOUBLE => 'Double', + TType::STRING => 'String'); + + abstract function read($input); + + abstract function write($output); + + public function __construct($spec=null, $vals=null) { + if (is_array($spec) && is_array($vals)) { + foreach ($spec as $fid => $fspec) { + $var = $fspec['var']; + if (isset($vals[$var])) { + $this->$var = $vals[$var]; + } + } + } + } + + private function _readMap(&$var, $spec, $input) { + $xfer = 0; + $ktype = $spec['ktype']; + $vtype = $spec['vtype']; + $kread = $vread = null; + if (isset(TBase::$tmethod[$ktype])) { + $kread = 'read'.TBase::$tmethod[$ktype]; + } else { + $kspec = $spec['key']; + } + if (isset(TBase::$tmethod[$vtype])) { + $vread = 'read'.TBase::$tmethod[$vtype]; + } else { + $vspec = $spec['val']; + } + $var = array(); + $_ktype = $_vtype = $size = 0; + $xfer += $input->readMapBegin($_ktype, $_vtype, $size); + for ($i = 0; $i < $size; ++$i) { + $key = $val = null; + if ($kread !== null) { + $xfer += $input->$kread($key); + } else { + switch ($ktype) { + case TType::STRUCT: + $class = $kspec['class']; + $key = new $class(); + $xfer += $key->read($input); break; - default: - $xfer += $input->skip($ftype); + case TType::MAP: + $xfer += $this->_readMap($key, $kspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($key, $kspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($key, $kspec, $input, true); + break; + } + } + if ($vread !== null) { + $xfer += $input->$vread($val); + } else { + switch ($vtype) { + case TType::STRUCT: + $class = $vspec['class']; + $val = new $class(); + $xfer += $val->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($val, $vspec, $input); break; + case TType::LST: + $xfer += $this->_readList($val, $vspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($val, $vspec, $input, true); + break; + } + } + $var[$key] = $val; + } + $xfer += $input->readMapEnd(); + return $xfer; + } + + private function _readList(&$var, $spec, $input, $set=false) { + $xfer = 0; + $etype = $spec['etype']; + $eread = $vread = null; + if (isset(TBase::$tmethod[$etype])) { + $eread = 'read'.TBase::$tmethod[$etype]; + } else { + $espec = $spec['elem']; + } + $var = array(); + $_etype = $size = 0; + if ($set) { + $xfer += $input->readSetBegin($_etype, $size); + } else { + $xfer += $input->readListBegin($_etype, $size); + } + for ($i = 0; $i < $size; ++$i) { + $elem = null; + if ($eread !== null) { + $xfer += $input->$eread($elem); + } else { + $espec = $spec['elem']; + switch ($etype) { + case TType::STRUCT: + $class = $espec['class']; + $elem = new $class(); + $xfer += $elem->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($key, $espec, $input); + break; + case TType::LST: + $xfer += $this->_readList($key, $espec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($key, $espec, $input, true); + break; + } + } + if ($set) { + $var[$elem] = true; + } else { + $var []= $elem; + } + } + if ($set) { + $xfer += $input->readSetEnd(); + } else { + $xfer += $input->readListEnd(); + } + return $xfer; + } + + protected function _read($class, $spec, $input) { + $xfer = 0; + $fname = null; + $ftype = 0; + $fid = 0; + $xfer += $input->readStructBegin($fname); + $fast_binary = (bool) + is_a($input, 'TBinaryProtocolAccelerated') && + function_exists('thrift_protocol_binary_deserialize'); + + while (true) { + $xfer += $input->readFieldBegin($fname, $ftype, $fid); + if ($ftype == TType::STOP) { + break; + } + if (isset($spec[$fid])) { + $fspec = $spec[$fid]; + $var = $fspec['var']; + if ($ftype == $fspec['type']) { + $xfer = 0; + if ($fast_binary) { + $class = isset($fspec['class']) ? $fspec['class'] : null; + $this->$var = thrift_protocol_binary_deserialize($ftype, $input, $class); + } else { + if (isset(TBase::$tmethod[$ftype])) { + $func = 'read'.TBase::$tmethod[$ftype]; + $xter += $input->$func($this->$var); + } else { + switch ($ftype) { + case TType::STRUCT: + $class = $fspec['class']; + $this->$var = new $class(); + $xfer += $this->$var->read($input); + break; + case TType::MAP: + $xfer += $this->_readMap($this->$var, $fspec, $input); + break; + case TType::LST: + $xfer += $this->_readList($this->$var, $fspec, $input, false); + break; + case TType::SET: + $xfer += $this->_readList($this->$var, $fspec, $input, true); + break; + } + } + } + } else { + $xfer += $input->skip($ftype); + } + } else { + $xfer += $input->skip($ftype); } $xfer += $input->readFieldEnd(); } @@ -100,17 +604,176 @@ class TApplicationException extends TException { return $xfer; } + private function _writeMap($var, $spec, $output) { + $xfer = 0; + $ktype = $spec['ktype']; + $vtype = $spec['vtype']; + $kwrite = $vwrite = null; + if (isset(TBase::$tmethod[$ktype])) { + $kwrite = 'write'.TBase::$tmethod[$ktype]; + } else { + $kspec = $spec['key']; + } + if (isset(TBase::$tmethod[$vtype])) { + $vwrite = 'write'.TBase::$tmethod[$vtype]; + } else { + $vspec = $spec['val']; + } + $xfer += $output->writeMapBegin($ktype, $vtype, count($var)); + foreach ($var as $key => $val) { + if (isset($kwrite)) { + $xfer += $output->$kwrite($key); + } else { + switch ($ktype) { + case TType::STRUCT: + $xfer += $key->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($key, $kspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($key, $kspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($key, $kspec, $output, true); + break; + } + } + if (isset($vwrite)) { + $xfer += $output->$vwrite($val); + } else { + switch ($vtype) { + case TType::STRUCT: + $xfer += $val->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($val, $vspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($val, $vspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($val, $vspec, $output, true); + break; + } + } + } + $xfer += $output->writeMapEnd(); + return $xfer; + } + + private function _writeList($var, $spec, $output, $set=false) { + $xfer = 0; + $etype = $spec['etype']; + $ewrite = null; + if (isset(TBase::$tmethod[$etype])) { + $ewrite = 'write'.TBase::$tmethod[$etype]; + } else { + $espec = $spec['elem']; + } + if ($set) { + $xfer += $output->writeSetBegin($etype, count($var)); + } else { + $xfer += $output->writeListBegin($etype, count($var)); + } + foreach ($var as $key => $val) { + $elem = $set ? $key : $val; + if (isset($ewrite)) { + $xfer += $output->$ewrite($elem); + } else { + switch ($etype) { + case TType::STRUCT: + $xfer += $elem->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($elem, $espec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($elem, $espec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($elem, $espec, $output, true); + break; + } + } + } + if ($set) { + $xfer += $output->writeSetEnd(); + } else { + $xfer += $output->writeListEnd(); + } + return $xfer; + } + + protected function _write($class, $spec, $output) { + $xfer = 0; + $xfer += $output->writeStructBegin($class); + foreach ($spec as $fid => $fspec) { + $var = $fspec['var']; + if ($this->$var !== null) { + $ftype = $fspec['type']; + $xfer += $output->writeFieldBegin($var, $ftype, $fid); + if (isset(TBase::$tmethod[$ftype])) { + $func = 'write'.TBase::$tmethod[$ftype]; + $xter += $output->$func($this->$var); + } else { + switch ($ftype) { + case TType::STRUCT: + $xfer += $this->$var->write($output); + break; + case TType::MAP: + $xfer += $this->_writeMap($this->$var, $fspec, $output); + break; + case TType::LST: + $xfer += $this->_writeList($this->$var, $fspec, $output, false); + break; + case TType::SET: + $xfer += $this->_writeList($this->$var, $fspec, $output, true); + break; + } + } + $xfer += $output->writeFieldEnd(); + } + } + $xfer += $output->writeFieldStop(); + $xfer += $output->writeStructEnd(); + return $xfer; + } +} + +class TApplicationException extends TException { + static $_TSPEC = + array(1 => array('var' => 'message', + 'type' => TType::STRING), + 2 => array('var' => 'code', + 'type' => TType::I32)); + + const UNKNOWN = 0; + const UNKNOWN_METHOD = 1; + const INVALID_MESSAGE_TYPE = 2; + const WRONG_METHOD_NAME = 3; + const BAD_SEQUENCE_ID = 4; + const MISSING_RESULT = 5; + + function __construct($message=null, $code=0) { + parent::__construct($message, $code); + } + + public function read($output) { + return $this->_read('TApplicationException', self::$_TSPEC, $output); + } + public function write($output) { $xfer = 0; $xfer += $output->writeStructBegin('TApplicationException'); - if ($this->getMessage()) { + if ($message = $this->getMessage()) { $xfer += $output->writeFieldBegin('message', TType::STRING, 1); - $xfer += $output->writeString($this->getMessage()); + $xfer += $output->writeString($message); $xfer += $output->writeFieldEnd(); } - if ($this->getCode()) { + if ($code = $this->getCode()) { $xfer += $output->writeFieldBegin('type', TType::I32, 2); - $xfer += $output->writeI32($this->getCode()); + $xfer += $output->writeI32($code); $xfer += $output->writeFieldEnd(); } $xfer += $output->writeFieldStop(); diff --git a/test/php/Makefile b/test/php/Makefile index 57c621c..6cc5233 100644 --- a/test/php/Makefile +++ b/test/php/Makefile @@ -1,5 +1,5 @@ # Makefile for Thrift test project. -# +# # Author: # Mark Slee @@ -16,7 +16,7 @@ normal: stubs inline: stubs-inline stubs: ../ThriftTest.thrift - $(THRIFT) --php ../ThriftTest.thrift + $(THRIFT) --phpl ../ThriftTest.thrift stubs-inline: ../ThriftTest.thrift $(THRIFT) --phpi ../ThriftTest.thrift diff --git a/test/php/TestClient.php b/test/php/TestClient.php index 07068e4..a508512 100644 --- a/test/php/TestClient.php +++ b/test/php/TestClient.php @@ -22,10 +22,18 @@ require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocketPool.php'; /** Include the socket layer */ require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php'; +echo '==============================='."\n"; +echo ' SAFE TO IGNORE THESE IN TEST'."\n"; +echo '==============================='."\n"; + /** Include the generated code */ require_once $GEN_DIR.'/ThriftTest.php'; require_once $GEN_DIR.'/ThriftTest_types.php'; +echo '==============================='."\n"; +echo ' END OF SAFE ERRORS SECTION'."\n"; +echo '==============================='."\n\n"; + $host = 'localhost'; $port = 9090; @@ -70,7 +78,7 @@ print_r(" = void\n"); print_r("testString(\"Test\")"); $s = $testClient->testString("Test"); print_r(" = \"$s\"\n"); - + /** * BYTE TEST */ @@ -296,7 +304,7 @@ foreach ($whoa as $key => $val) { } } print_r("}, "); - + $xtructs = $v2->xtructs; print_r("{"); if (is_array($xtructs)) { @@ -306,7 +314,7 @@ foreach ($whoa as $key => $val) { } } print_r("}"); - + print_r("}, "); } print_r("}, "); -- 2.11.4.GIT