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: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665328 13f79535-47bb-0310-9956-ffa450edef68
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 @@
   // Include other Thrift includes
   const vector<t_program*>& 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 @@
   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<t_field*>& members = tstruct->get_members();
+  vector<t_field*>::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 @@
   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 @@
     }
 
     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 @@
   vector<t_field*>::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 @@
   }
   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 @@
   }
   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 @@
       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 @@
   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 @@
    */
 
   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 @@
    */
   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_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 @@
   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 @@
     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 @@
 
     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 @@
         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 @@
         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 @@
     base_(base),
     string_list_(false),
     string_enum_(false) {}
-    
+
   t_base get_base() const {
     return base_;
   }
@@ -49,7 +49,11 @@
   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 @@
   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 @@
   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,14 +43,711 @@
   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);
+    }
+  }
+
+  static $tmethod = array(TType::BOOL   => 'Bool',
+                          TType::BYTE   => 'Byte',
+                          TType::I16    => 'I16',
+                          TType::I32    => 'I32',
+                          TType::I64    => 'I64',
+                          TType::DOUBLE => 'Double',
+                          TType::STRING => 'String');
+
+  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;
+  }
+
+  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();
+    }
+    $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 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;
+        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();
+    }
+    $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 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;
@@ -62,55 +759,21 @@
     parent::__construct($message, $code);
   }
 
-  public function read($input) {
-    $xfer = 0;
-    $fname = null;
-    $ftype = 0;
-    $fid = 0;
-    $xfer += $input->readStructBegin($fname);
-    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);
-          } else {
-            $xfer += $input->skip($ftype);
-          }
-          break;
-        case 2:
-          if ($ftype == TType::I32) {
-            $xfer += $input->readI32($this->code);
-          } else {
-            $xfer += $input->skip($ftype);
-          }
-          break;
-        default:
-          $xfer += $input->skip($ftype);
-          break;
-      }
-      $xfer += $input->readFieldEnd();
-    }
-    $xfer += $input->readStructEnd();
-    return $xfer;
+  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 <mcslee@facebook.com>
 
@@ -16,7 +16,7 @@
 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 @@
 /** 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("testString(\"Test\")");
 $s = $testClient->testString("Test");
 print_r(" = \"$s\"\n");
-   
+
 /**
  * BYTE TEST
  */
@@ -296,7 +304,7 @@
       }
     }
     print_r("}, ");
-    
+
     $xtructs = $v2->xtructs;
     print_r("{");
     if (is_array($xtructs)) {
@@ -306,7 +314,7 @@
       }
     }
     print_r("}");
-    
+
     print_r("}, ");
   }
   print_r("}, ");